I've been using Claude Code's fullscreen TUI, and the thing that kept annoying me was losing mouse selection in Wezterm the moment it starts up. Here's the config that gets it back, mostly for potential googlers (and future me) hitting the same wall.
The problem
Claude Code's fullscreen TUI turns on full mouse tracking (normal, button-event,
any-motion and SGR), so Wezterm forwards every drag straight to the app and never
builds a selection of its own. You can get selection back by holding shift, which
is the default of bypass_mouse_reporting_modifiers, but then shift is spent and
clicks in the TUI have nowhere to go.
The keyboard half of this runs on the kitty keyboard protocol, which is what lets the TUI tell shift+enter from a plain enter and read modifier combinations a terminal wouldn't normally pass through. It's the same bargain as the mouse tracking: the terminal hands its input over to the app, keys and mouse both, so the defaults you lean on in an ordinary pane go quiet while you're in there.
The fix
Drop this into your ~/.wezterm.lua:
-- Keep SHIFT off the bypass so shift+click still reaches apps that grab the
-- mouse. Claude Code masks the modifier bits off the button byte, so it arrives
-- there as a plain click.
config.bypass_mouse_reporting_modifiers = "CTRL|ALT"
config.mouse_bindings = {
-- A mouse binding is only considered when the pane's mouse reporting state
-- matches, so ordinary bindings go dead inside an app that grabs the mouse.
-- These claim the left button back for selection while it does.
{
event = { Down = { streak = 1, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.SelectTextAtMouseCursor("Cell"),
},
{
event = { Drag = { streak = 1, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.ExtendSelectionToMouseCursor("Cell"),
},
{
event = { Down = { streak = 2, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.SelectTextAtMouseCursor("Word"),
},
{
event = { Drag = { streak = 2, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.ExtendSelectionToMouseCursor("Word"),
},
{
event = { Down = { streak = 3, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.SelectTextAtMouseCursor("Line"),
},
{
event = { Drag = { streak = 3, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.ExtendSelectionToMouseCursor("Line"),
},
{
event = { Up = { streak = 1, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.CompleteSelection("ClipboardAndPrimarySelection"),
},
{
event = { Up = { streak = 2, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.CompleteSelection("ClipboardAndPrimarySelection"),
},
{
event = { Up = { streak = 3, button = "Left" } },
mods = "NONE",
mouse_reporting = true,
action = wezterm.action.CompleteSelection("ClipboardAndPrimarySelection"),
},
-- CTRL+click to open a link needs claiming back as well, otherwise it reaches
-- the app as an ordinary click. Down and Drag go to Nop so the press, and any
-- wobble before the release, don't get through either.
{
event = { Down = { streak = 1, button = "Left" } },
mods = "CTRL",
mouse_reporting = true,
action = wezterm.action.Nop,
},
{
event = { Drag = { streak = 1, button = "Left" } },
mods = "CTRL",
mouse_reporting = true,
action = wezterm.action.Nop,
},
{
event = { Up = { streak = 1, button = "Left" } },
mods = "CTRL",
mouse_reporting = true,
action = wezterm.action.OpenLinkAtMouseCursor,
},
}
If you already have a mouse_bindings table, add these entries to it rather than
replacing it. Setting mouse_bindings at all does not remove Wezterm's defaults,
your entries are merged over the top of them, so the bindings for panes that
aren't grabbing the mouse carry on working.
What you get inside the TUI
- drag: selects, and
CompleteSelectionputs it on the clipboard on release. ctrl+shift+c copies too, that's a Wezterm default. - double or triple click: word or line, dragging after either extends by that unit.
- ctrl+click: opens a link, same as it does in an ordinary pane.
- shift+click: goes through to Claude Code, e.g. expanding a "Running shell commands" block.
- ctrl+alt+click: the new bypass, if you want Wezterm's non-reporting behaviour back.
Every modifier combination works this way, so anything you rely on in a normal
pane needs an entry with mouse_reporting = true or it falls through to the app.
Bind the Down and Drag events alongside the Up one when you do, since a
matched binding is what stops Wezterm forwarding the event, and a click that
drifts a pixel between press and release sends motion events too.
Why shift+click survives the trip
Wezterm sends the real modifier state in the SGR report, and Claude Code masks
the modifier bits off the button byte (button & 3) before it decides which
button you pressed. A shift+left press is button 4, and 4 & 3 is 0, so it lands
as a plain left click. This is an implementation detail of the TUI (read out of
2.1.220), so it could change.
The cost is that a plain left click no longer reaches the TUI, it sets Wezterm's selection anchor instead. Everything else, wheel included, still goes to the app.
Checking it works
wezterm show-keys prints the resolved tables. The one you care about is
Mouse: mouse_reporting + alt_screen, which is the state a fullscreen TUI puts
the pane in:
Mouse: mouse_reporting + alt_screen
-----------------------------------
Down { streak: 1, button: Left } -> SelectTextAtMouseCursor(Cell)
Down { streak: 2, button: Left } -> SelectTextAtMouseCursor(Word)
Down { streak: 3, button: Left } -> SelectTextAtMouseCursor(Line)
Drag { streak: 1, button: Left } -> ExtendSelectionToMouseCursor(Cell)
...
The mouse_reporting field on mouse bindings needs Wezterm
20220807-113146-c2fee766 or newer. I tested this on 20240203-110809-5046fc22.
One thing to watch on Linux: your window manager may grab ctrl+alt+click before Wezterm sees it, so pick a different bypass combo if you need it and it does nothing.
The blunter option
CLAUDE_CODE_DISABLE_MOUSE=1 makes Claude Code emit no mouse tracking sequences
at all, so the terminal keeps the mouse and none of the above is needed. You lose
wheel scrolling in the TUI, since Wezterm maps the wheel to arrow keys in alt
screen. There's also CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1, which drops it to
normal plus SGR tracking, but reporting is still on so the terminal still won't
select for you.