Skip to content

zellij: tmux-faithful keybinds (clear-defaults; only Ctrl-\ global)#21

Open
bddap-bot wants to merge 3 commits into
bddap:masterfrom
bddap-bot:zellij-tmux-keybinds
Open

zellij: tmux-faithful keybinds (clear-defaults; only Ctrl-\ global)#21
bddap-bot wants to merge 3 commits into
bddap:masterfrom
bddap-bot:zellij-tmux-keybinds

Conversation

@bddap-bot

@bddap-bot bddap-bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #19 (merged). Goal: make Ctrl \ behave like tmux's prefix in zellij, and keep Ctrl \ usable at both layers when tmux is nested inside zellij (or vice-versa). Three commits.

1. zellij: tmux-faithful keybinds (config.kdl) — the actual fix

Fixes the owner complaint: "zellij hijacks my Ctrl-p/n, Ctrl-f and friends." Stock zellij binds ~a dozen Ctrl/Alt chords globally (Ctrl-p/t/n/h/s/o/g/q, plus Alt-f/n/…) as mode-switches, stealing them from the shell. (Ctrl-f isn't itself a stock bind — Alt-f is — but clear-defaults frees every Ctrl/Alt chord regardless, so Ctrl-f falls through too.) The previous config rebound the prefix to Ctrl \ but left clear-defaults=false, so all the stock global binds still stole keys.

  • clear-defaults=true — nuke every stock bind.
  • One global capture: Ctrl \ → tmux mode (shared_except "tmux" "scroll" "renametab", so the prefix can send-prefix inside tmux mode and doesn't yank you out of the text/scroll sub-modes). Every other Ctrl/Alt key now falls through to the running program.
  • tmux-mode table modeled on tmux's prefix + ~/.tmux.conf: %/" splits, hjkl+arrows focus, o/z/x, Space=next-layout, c/n/p/&+1-9 tabs, d detach, , rename, [ scroll. scroll/renametab get explicit escape binds (clear-defaults wiped the stock ones).
  • send-prefix passthrough (the nested-multiplexer pattern): in tmux mode, Ctrl \ Ctrl \Write 28 (literal Ctrl-) to the pane, matching .tmux.conf's bind "C-\\" send-prefix. So a tmux running inside a zellij pane still gets its own Ctrl \ prefix.

Why this gives you Ctrl \ in both: zellij defaults support_kitty_keyboard_protocol true, so in a kitty-capable terminal (Alacritty) it receives Ctrl \ as a kitty CSI-u sequence and the prefix fires. tmux doesn't request the kitty protocol, so it keeps receiving Ctrl \ as the legacy 0x1c byte and its prefix fires too. The two coexist.

2. zellij: Esc / Ctrl-c aborts the tmux-mode prefix (config.kdl)

Caught in the pre-merge review. With clear-defaults=true an unbound key in tmux mode is swallowed (you stay in-mode), so a stray prefix used to strand you in tmux mode with only the action keys as a way out. Now Esc/Ctrl c drop back to normal, like tmux after a stray prefix.

3. alacritty: drop an inert ReceiveChar binding on Ctrl-Backslash (alacritty.toml)

Cruft cleanup, not a functional fix — flagging the honest story because my first pass got it wrong. I initially thought this binding forced the legacy 0x1c byte and starved zellij of the CSI-u sequence. It doesn't: Alacritty's keyboard handler makes a ReceiveChar binding a no-op for byte output (*suppress_chars.get_or_insert(true) &= action != ReceiveChar leaves it unsuppressed = same as no binding), and the legacy-vs-CSI-u choice is taken purely from terminal keyboard mode, never from a binding (verified against Alacritty source v0.13–v0.15/master). So it neither blocked zellij nor was needed for tmux. Removed as dead config; no behavior change.

Testing — verified vs not

  • Verified here: zellij setup --check passes on the rendered config.kdl (real zellij 0.43.1; confirmed it actually parses via a broken-KDL negative control that exits 1). alacritty.toml parses clean (python tomllib). Write 28 == 0x1c == Ctrl-. The Alacritty ReceiveChar no-op claim is read straight from Alacritty source; the zellij CSI-u requirement is zellij-org/zellij#4509. Reviewed by a fan-out reviewer loop (correctness/byte-protocol, KDL, taste) — that loop is what caught both the missing Esc escape and the Alacritty misdiagnosis.
  • NOT verified live: no headless terminal here, so I could not confirm on your actual Alacritty that the zellij prefix fires (it should, given default kitty protocol) or press-test tmux nesting. The keybind change is the substance; commit 3 is inert by source analysis.
  • Caveat for the owner: the zellij prefix works only while support_kitty_keyboard_protocol stays at its default true. If it's ever set false, zellij would get the 0x1c byte, which its input parser doesn't decode as Ctrl \ (the #4509 failure) — and no Alacritty setting fixes that.

bddap-bot and others added 3 commits June 19, 2026 18:43
Owner: "zellij hijacks my Ctrl-p/n, Ctrl-f and friends." Stock zellij binds a
dozen Ctrl/Alt chords GLOBALLY (Ctrl-p/n/f/t/h/s/o/g/q, Alt-…) as mode-switches,
stealing them from the shell. The previous config rebound the prefix to C-\ but
left clear-defaults=false, so all those stock global binds still stole keys.

Switch to clear-defaults=true (nukes every stock bind, global and in-mode) and
rebind, tmux-style:
  - ONE global capture: `Ctrl \` -> tmux mode (shared_except tmux/scroll/renametab,
    so the prefix is free to send-prefix inside tmux mode and doesn't yank you out
    of the text/scroll sub-modes). Every other Ctrl/Alt key now falls through to
    the running program — verified with a headless pty: Ctrl-f/p/n/t reach a shell
    verbatim while `Ctrl \` enters tmux mode.
  - tmux-mode bindings modeled on tmux's default prefix table + ~/.tmux.conf:
    % / " splits, hjkl+arrows focus, o next-pane, z zoom, x close, Space
    next-layout, c/n/p/& tab new/next/prev/close, 1-9 tab jump, d detach,
    , rename, [ copy-mode/scroll. Single keys (post-prefix). One-shots return to
    normal; scroll/renametab get escape binds (clear-defaults wiped the stock ones,
    so without them you'd be stuck in-mode).
  - prefix prefix -> Write 28 (literal Ctrl-\) for send-prefix.

The spiral plugin block + layout are untouched (the 4e7076c re-tile fix stays
intact; its headless test still passes on the production artifacts).

IMPORTANT (separate from this file): zellij only captures `Ctrl \` when the
terminal delivers it via the kitty keyboard protocol (CSI-u). zellij's input
parser (vendored termwiz) has no decode mapping for the legacy bare 0x1c byte, so
a terminal that sends Ctrl-\ as 0x1c won't trigger the prefix. Our Alacritty has a
`ReceiveChar` override on Ctrl-Backslash (added for tmux) that forces exactly that
legacy byte — so until that's reconsidered, the prefix won't fire in Alacritty
(the Ctrl-p/n/f passthrough fix works regardless). Flagged to the owner.

Verified: `zellij setup --check` clean; home-manager build RC=0; headless pty
proves prefix capture (prefix c/1/3 create+jump tabs), passthrough of freed Ctrl
keys, and o/Space return-to-normal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clear-defaults=true wiped zellij's stock mode escapes, and an unbound key in tmux
mode is swallowed (you stay in-mode). So a stray prefix `Ctrl \` left you in tmux
mode able to leave only by triggering one of the action keys. tmux drops back to
normal on Esc / C-c after a stray prefix; this matches that.

Found in the pre-merge review of bddap#21. Config parses (`zellij setup --check`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… config)

This binding looked load-bearing for the `Ctrl \` story — as if it forced the
legacy 0x1c byte and starved zellij of the kitty CSI-u sequence its prefix needs.
It does not. Alacritty's keyboard handler makes a `ReceiveChar` binding a no-op for
byte output (`*suppress_chars.get_or_insert(true) &= action != ReceiveChar` leaves
it unsuppressed, i.e. identical to no binding), and the legacy-vs-CSI-u choice is
taken purely from terminal keyboard mode, never from a binding (verified against
alacritty src v0.13–v0.15/master). So it neither blocked zellij's prefix nor was
needed for tmux (Ctrl-\ produces 0x1c natively). Removed as cruft — no behavior
change either way.

The actual `Ctrl \`-in-both mechanism lives in the zellij keybinds + zellij's
default `support_kitty_keyboard_protocol true`, not here.

NOT live-tested (no headless terminal); the no-op claim is from Alacritty source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bddap-bot bddap-bot force-pushed the zellij-tmux-keybinds branch from 3bdd5d6 to 2a6cfae Compare June 22, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant