Repository navigation
Encode modified character keys through the kitty keyboard protocol - #583
Merged
Merged
Conversation
dakra
force-pushed
the
fix/kitty-modified-char-keys
branch
from
July 29, 2026 14:25
418635c to
88f08f3
Compare
The native encoder built libghostty key events with only key and mods. libghostty resolves character keys through the unshifted codepoint and generated text, so every modified character key produced no output in both kitty and legacy modes and fell through to the kitty-unaware elisp fallback: M-<letter> went out as legacy ESC <letter> even when the program had pushed kitty flags, and C-M-<letter> lost meta entirely, sending the bare control byte. - Build key events in input.keyEvent: single printable-ASCII keys carry their unshifted codepoint and the character as generated text; uppercase names fold to the lowercase codepoint with shift as a consumed modifier. Named/functional keys are unchanged. - Set macos_option_as_alt: Emacs resolves option-vs-meta before the event reaches the module, so meta always means alt. - Route C-<letter>, C-@, C-SPC, C-\ and ghostel-send-C-g through the encoder instead of raw-byte closures; legacy bytes are identical, kitty mode now gets CSI-u. - Remap raw C0 events 13/9/27 in ghostel--send-event to return/tab/escape and drop the artifact ctrl modifier: a TTY delivers RET as C-m, which the encoder would otherwise fixterms-encode as CSI-u and break Enter/Tab. - Restore case for uppercase chords (lowercase base + shift): M-T sends ESC T in legacy mode and reports the shifted alternate under kitty flags. - Keep ghostel-send-C-c/-C-z/-C-d as raw bytes so SIGINT/SIGTSTP/EOF reach the line discipline even while a hung program holds kitty mode. - ESC-prefix the elisp fallback's C0 byte when meta is set so that path no longer drops the modifier either. API note: ghostel-send-key with "m"/"i"/"[" plus ctrl now yields the fixterms CSI-u sequence (matching ghostty) instead of a C0 byte; live keystrokes are unaffected thanks to the C0 remap above. Closes #579
dakra
force-pushed
the
fix/kitty-modified-char-keys
branch
from
July 29, 2026 15:18
88f08f3 to
fbadad2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #579
Problem
When a TUI enables the kitty keyboard protocol, ghostel still encoded modified character keys with legacy sequences:
M-<letter>went out asESC <letter>(rejected as ambiguous by kitty-mode programs) andC-M-<letter>dropped meta entirely, sending the bare control byte. Modified functional keys (backspace, return, arrows) already encoded correctly.Root cause
The native encoder built libghostty key events with only
keyandmods. libghostty resolves character keys throughunshifted_codepointand the generated text (utf8), so every modified character key produced no output at all — in both kitty and legacy modes — and fell through to the kitty-unaware elisp fallbackghostel--raw-key-sequence, whose ctrl branch also drops meta. Additionally,macos_option_as_altdefaulted to false on macOS builds, suppressing alt handling even with the codepoint present.Fix
input.keyEventbuilder: single printable-ASCII keys carry their unshifted codepoint and the character as generated text; uppercase names fold to the lowercase codepoint with shift as a consumed modifier. Named/functional keys are untouched.macos_option_as_alt = .true: Emacs resolves option-vs-meta before the event reaches the module, so meta always means alt.C-<letter>,C-@,C-SPC,C-\andghostel-send-C-gnow route through the encoder instead of raw-byte closures — legacy bytes are provably identical (libghostty'sctrlSeqyields the same C0 codes), kitty mode gets CSI-u.return/tab/escapeinghostel--send-event(with the artifact ctrl modifier stripped): a TTY delivers RET asC-m, which the encoder would otherwise fixterms-encode as CSI-u and break Enter/Tab.M-TsendsESC Tin legacy mode and reports the shifted alternate (\e[116:84;4u) under kitty flags=5.ghostel-send-C-c/-C-z/-C-ddeliberately stay raw bytes so SIGINT/SIGTSTP/EOF reach the tty line discipline even while a hung program holds kitty mode.API note:
ghostel-send-keywith"m"/"i"/"["plus ctrl now yields the fixterms CSI-u sequence (matching upstream ghostty) instead of a C0 byte; live keystrokes are unaffected thanks to the C0 remap.Testing
src/input.zigis now emacs-free and part ofzig build test.printf '\e[>1u'; cat -v), TTY Enter/Tab/arrows, ESC-prefixed meta chords, readline editing, less/vi, and the rescue hatches interrupting a program that holds kitty mode.make -j8 allgreen.