Skip to content

Implement the Kitty Keyboard Protocol - #19817

Merged
Dustin L. Howett (DHowett) merged 7 commits into
mainfrom
dev/lhecker/11509-kitty-keyboard-protocol
Feb 17, 2026
Merged

Dustin L. Howett (DHowett) merged 7 commits into
mainfrom
dev/lhecker/11509-kitty-keyboard-protocol

Conversation

@lhecker

@lhecker Leonard Hecker (lhecker) commented Jan 29, 2026 •

Copy link
Copy Markdown
Member

This essentially rewrites TerminalInput from scratch.
There's significant overlap between what kind of information
the Kitty protocol needs from the OS and our existing code.
The rewrite allows us to share large parts of the implementation.

Closes #11509

Validation Steps Performed

  • kitten show-key -m kitty ✅
  • US Intern. " + ' produces \' ✅
  • Hebrew base keys produce Unicode ✅
  • Hebrew AltGr combinations produce Unicode ✅
  • French AltGr+Space produces U+00A0 ✅
  • German AltGr+Decimals produce []{}... ✅

uint32_t TerminalInput::_codepointToLower(uint32_t cp) noexcept
{
auto cb = _codepointToBuffer(cp);
// NOTE: MSDN states that `lpSrcStr == lpDestStr` is valid for LCMAP_LOWERCASE.

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[LCMAP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
Comment thread src/terminal/input/terminalInput.cpp Outdated
{
auto cb = _codepointToBuffer(cp);
// NOTE: MSDN states that `lpSrcStr == lpDestStr` is valid for LCMAP_LOWERCASE.
const auto len = LCMapStringW(LOCALE_INVARIANT, LCMAP_LOWERCASE, cb.buf, cb.len, cb.buf, gsl::narrow_cast<int>(std::size(cb.buf)));

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[LCMAP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
case CsiActionCodes::ANSISYSRC_CursorRestore:
_dispatch->CursorRestoreState();
break;
case CsiActionCodes::KKP_KittyKeyboardSet:

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[KKP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
case CsiActionCodes::KKP_KittyKeyboardSet:
_dispatch->SetKittyKeyboardProtocol(parameters.at(0), parameters.at(1));
break;
case CsiActionCodes::KKP_KittyKeyboardQuery:

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[KKP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
case CsiActionCodes::KKP_KittyKeyboardQuery:
_dispatch->QueryKittyKeyboardProtocol();
break;
case CsiActionCodes::KKP_KittyKeyboardPush:

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[KKP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
case CsiActionCodes::KKP_KittyKeyboardPush:
_dispatch->PushKittyKeyboardProtocol(parameters.at(0));
break;
case CsiActionCodes::KKP_KittyKeyboardPop:

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[KKP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
DECSLRM_SetLeftRightMargins = VTID("s"),
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
ANSISYSRC_CursorRestore = VTID("u"),
KKP_KittyKeyboardSet = VTID("=u"),

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[KKP](#security-tab) is not a recognized word. \(unrecognized-spelling\)
@microsoft-github-policy-service microsoft-github-policy-service Bot added Issue-Task It's a feature request, but it doesn't really need a major design. Area-Input Related to input processing (key presses, mouse, etc.) Product-Conhost For issues in the Console codebase Product-Terminal The new Windows Terminal. labels Jan 29, 2026
Comment thread src/common.build.pre.props Resolved

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are unfortunately going to need some tests. or something. of the weird corner cases.

Comment thread src/terminal/input/mouseInputState.cpp Resolved
@microsoft-github-policy-service microsoft-github-policy-service Bot added Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Jan 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost all the way done. Can you explain the conhost win32im thing? Should we actually use Kitty as our internal protocol?

Comment thread src/terminal/input/terminalInput.cpp Resolved
Comment thread src/terminal/input/terminalInput.cpp Resolved
@lhecker
Leonard Hecker (lhecker) marked this pull request as draft January 30, 2026 14:38
@lhecker

Copy link
Copy Markdown
Member Author

I'm working on a version that's a lot easier to read. The problem is that it also doesn't quite work. 😄 Still working on that.

@lhecker

Leonard Hecker (lhecker) commented Feb 3, 2026 •

Copy link
Copy Markdown
Member Author

(cc: James Holderness (@j4james), as this completely restructures your code to fit in Kitty's event-type field into the classic CSI sequences.)

I think the logic and "idea of a structure" is solid now. If you want to test it, I think now it's ready for that. 🙂 The code is not final, however. I'm trying to further improve its structure, because this is still beyond ugly and unmaintainable.

@github-actions

This comment has been minimized.

@j4james

Copy link
Copy Markdown
Collaborator

I'm sorry, I never got around to writing unit tests for all the keyboard mapping edge cases, and I know from last time that there's a lot of testing that's required. I can't do a build myself, so I can't test an in-progress PR anyway, but even if I could, I just wouldn't have the time to do that level of testing.

My suggestion would be to go through all the linked issues on #16511, and also look for follow up issues like #16641 and #16642. You're going to want to test with lots of international keyboard layouts, looking out for things like dead keys, and AltGr key combos, especially when mixed all the Ctrl/Alt/Shift modifiers. Make sure you test Hebrew and Cyrillic.

The various touch-screen/on-screen keyboards can also cause issues - I think some things are not expected to work, but they should at least work as well as they did before. I also vaguely remember remote desktop introducing some complications, so that's possibly worth testing too.

@lhecker
Leonard Hecker (lhecker) force-pushed the dev/lhecker/11509-kitty-keyboard-protocol branch from a4df1f4 to 4f86d4c Compare February 4, 2026 22:02
@github-actions

This comment has been minimized.

@lhecker
Leonard Hecker (lhecker) marked this pull request as ready for review February 4, 2026 22:10
@lhecker

Copy link
Copy Markdown
Member Author

James Holderness (@j4james) Thanks for the pointers! I hope I'll do your implementation justice. I wouldn't say that I'm happy with my new implementation, but I also hope it's not too awful.

(One thing I'm still uncertain about is my decision to let Kitty's encoding use your encoding table as a fallback. This is what required the rewrite from a hashmap --> a switch case in the first place. I could've also just duplicated the table into the Kitty path. Not sure what decision would've been better...)

@j4james

Copy link
Copy Markdown
Collaborator

Leonard Hecker (@lhecker) I had hoped you would just be able to add additional mappings to my encoding table for kitty's disambiguate mode, and then the more advanced "report everything" modes would get their own code path the same as win32 input mode. If that wasn't feasible, I would probably be inclined to implement all of the kitty functionality in its own code path.

I have to say I really don't like the switch/case approach - the whole reason for my mapping table refactor was to avoid that sort of thing. But maybe you can still come up with a better solution. I don't know enough about the kitty protocol to know what complications you need to deal with, but I can understand that it's probably a bit of a pain.

@lhecker

Leonard Hecker (lhecker) commented Feb 5, 2026 •

Copy link
Copy Markdown
Member Author

I started with splitting up your encoding table into a switch case when I started implementing the "event type" enhancement flag. It requires reporting key up and repeat events as a sub-parameter to the modifier field, e.g. CSI 1 ; 2:3 A (key-repeat arrow up).

Looking at your encoding table for such CSI sequences, it looked very daunting. It gave me the impression that I may have to add complex encoding logic into the Kitty path. That sprouted the idea of turning your encoding table into a switch case and re-using it. At the time I didn't realize that Kitty only requires about a dozen or so "legacy" CSI sequences.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed everything except terminalInput itself

Comment thread src/host/VtIo.cpp Outdated
CATCH_RETURN();
}

// TODO: Avoid translating win32im sequences to Kitty Keyboard Protocol temporarily.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo - file a bug?

Comment thread src/terminal/adapter/ITermDispatch.hpp Resolved
// The KKP CSI u sequence is a superset of other CSI sequences:
// CSI unicode-key-code:alternate-key-code-shift:alternate-key-code-base ; modifiers:event-type ; text-as-codepoint u
uint32_t csiUnicodeKeyCode;
uint32_t csiAltKeyCodeShifted; // KKP-specific

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if KKP-specific, we could call it kkpAltKey...?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm no I'd like everything to have the same csi prefix. Makes it easier in my mind since all of them logically belong together.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty much ready to sign off, having read the implementations side by side, but I have just a few questions.

// it must be the generated character from an Alt-Numpad composition.
if (WI_IsFlagSet(key.controlKeyState, NUMLOCK_ON) && key.virtualKey == VK_MENU && key.codepoint != 0)
{
return _makeCharOutput(key.codepoint);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that alt numpad (raw Unicode) inputs will not be encoded in kitty's "everything is escape sequences and (optionally) includes text/codepoint" mode

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but we should keep it that way right?

if (key.virtualKey == VK_PACKET || key.virtualKey == 0)
{
_lastVirtualKeyCode = std::nullopt;
return _makeCharOutput(key.codepoint);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note as for numpad input

// of length two, with two identical characters. This is because the system
// sees this as a second press of the dead key, which would typically result
// in the combining character representation being transmit twice.
auto length = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer.data(), bufferSize, flags, hkl);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does the new implementation handle dead keys?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is for dead keys that produce two codepoints (e.g. ^^ for the German caret dead key). But the new implementation only supports single-codepoint keys, so it's implicitly rejected. I did this intentionally because it simplifies the design: A single input still only produces a single output.

I'll add a note for this, since it's very implicit.

@DHowett

Copy link
Copy Markdown
Member

I did appreciate the simplicity of the defineKey lambdas, but I also get that there's a lot of complexity here. I don't mind the switch/case but getting to the point where I was reading it was hard!

@lhecker

Leonard Hecker (lhecker) commented Feb 16, 2026 •

Copy link
Copy Markdown
Member Author

Yeah, the old approach has its benefits and I'm glad its in the git history as backup. In fact I wouldn't necessarily mind reintroducing it and only using the switch case for the kitty mode.

However, the lack of clarity in the KKP specification makes me wary. For instance, it doesn't say what to do if the Ansi mode is disabled. You can also enable key-up notifications in KKP while the disambiguate-esc enhancement is disabled. Then we have logic like the keypad mode feature on our side.

I found it very annoying to deal with the complexity of this.

@DHowett
Dustin L. Howett (DHowett) merged commit c81c66b into main Feb 17, 2026
18 of 20 checks passed
@DHowett
Dustin L. Howett (DHowett) deleted the dev/lhecker/11509-kitty-keyboard-protocol branch February 17, 2026 01:53
Dargon789 (Dargon789) added a commit to Dargon789/terminal that referenced this pull request Feb 17, 2026
This essentially rewrites `TerminalInput` from scratch.
There's significant overlap between what kind of information
the Kitty protocol needs from the OS and our existing code.
The rewrite allows us to share large parts of the implementation.

Closes microsoft#11509

## Validation Steps Performed
* `kitten show-key -m kitty` ✅
* US Intern. " + ' produces `\'` ✅
* Hebrew base keys produce Unicode ✅
* Hebrew AltGr combinations produce Unicode ✅
* French AltGr+Space produces U+00A0 ✅
* German AltGr+Decimals produce []{}... ✅

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
Dargon789 (Dargon789) added a commit to Dargon789/terminal that referenced this pull request Feb 17, 2026
This essentially rewrites `TerminalInput` from scratch.
There's significant overlap between what kind of information
the Kitty protocol needs from the OS and our existing code.
The rewrite allows us to share large parts of the implementation.

Closes microsoft#11509

## Validation Steps Performed
* `kitten show-key -m kitty` ✅
* US Intern. " + ' produces `\'` ✅
* Hebrew base keys produce Unicode ✅
* Hebrew AltGr combinations produce Unicode ✅
* French AltGr+Space produces U+00A0 ✅
* German AltGr+Decimals produce []{}... ✅

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
@lhecker Leonard Hecker (lhecker) added Help Wanted We encourage anyone to jump in on these. In-PR This issue has a related PR Needs-Tag-Fix Doesn't match tag requirements and removed Needs-Tag-Fix Doesn't match tag requirements zBugBash-Consider Help Wanted We encourage anyone to jump in on these. In-PR This issue has a related PR labels May 7, 2026
Ioannis Makris (imakris) added a commit to Varinomics/vnm_terminal_surface that referenced this pull request Oct 5, 2026
The surface sends typed text to ConPTY as win32-input-mode VK_PACKET
strokes, one per UTF-16 unit: a key-down carrying the unit and a key-up
carrying none. A supplementary character such as an emoji therefore
reaches ConPTY as leading-surrogate down, empty up, trailing-surrogate
down, empty up.

For a child that reads VT input, ConPTY converts those records into text
itself. ConPTY 1.25 (microsoft/terminal#19817, "Implement the Kitty
Keyboard Protocol") moved that conversion's surrogate bookkeeping ahead of
its key-down check, so any record that is not a surrogate, including an
empty key-up, forgets a pending leading surrogate. Such a child received
only the trailing surrogate, which most readers turn into U+FFFD. ConPTY
1.24 is unaffected, and classic readers of input records receive the
records unchanged on both.

A VK_PACKET key-up now carries the same unit as its key-down, which is how
ConPTY builds the records for typed text itself. ConPTY pairs the
surrogates on both versions and, with the Kitty protocol off, emits
nothing for a key-up, so no text is doubled. Keys with a real virtual key
still release with no character.

The escape-transport suite gains a VT-input case that reads UTF-16 with
ReadConsoleW. A byte reader would also see the console's own UTF-8
conversion, which splits the pair on 1.24 whatever the records are. Before
this change the case fails on 1.25.260930003 and passes on 1.24.260710001.
After it, it passes on both. The packet stroke expectations in the encoder
and escape-transport tests now include the key-up unit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Input Related to input processing (key presses, mouse, etc.) Issue-Task It's a feature request, but it doesn't really need a major design. Product-Conhost For issues in the Console codebase Product-Terminal The new Windows Terminal.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add keyboard protocol support (the kitty one)

5 participants