Skip to content

Implement the Kitty Keyboard Protocol#19817

Open
lhecker wants to merge 5 commits intomainfrom
dev/lhecker/11509-kitty-keyboard-protocol
Open

Implement the Kitty Keyboard Protocol#19817
lhecker wants to merge 5 commits intomainfrom
dev/lhecker/11509-kitty-keyboard-protocol

Conversation

@lhecker
Copy link
Member

@lhecker lhecker commented Jan 29, 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 #11509

Validation Steps Performed

  • kitten show-key -m kitty
  • 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\)
{
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
Copy link
Member

@DHowett DHowett left a comment

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.

@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
Member

@DHowett DHowett left a comment

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?

@lhecker lhecker marked this pull request as draft January 30, 2026 14:38
@lhecker
Copy link
Member Author

lhecker commented Jan 30, 2026

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
Copy link
Member Author

lhecker commented Feb 3, 2026

(cc: @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
Collaborator

j4james commented Feb 4, 2026

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 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 lhecker marked this pull request as ready for review February 4, 2026 22:10
@lhecker
Copy link
Member Author

lhecker commented Feb 4, 2026

@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
Collaborator

j4james commented Feb 5, 2026

@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
Copy link
Member Author

lhecker commented Feb 5, 2026

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
Member

@DHowett DHowett left a comment

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

CATCH_RETURN();
}

// TODO: Avoid translating win32im sequences to Kitty Keyboard Protocol temporarily.
Copy link
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?

// 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
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
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.

@lhecker
Copy link
Member Author

lhecker commented Feb 11, 2026

@j4james FWIW I made a small test utility for this: https://github.com/lhecker/keyboard-layout-switcher

Copy link
Member

@DHowett DHowett left a comment

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
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

if (key.virtualKey == VK_PACKET || key.virtualKey == 0)
{
_lastVirtualKeyCode = std::nullopt;
return _makeCharOutput(key.codepoint);
Copy link
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
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?

@DHowett
Copy link
Member

DHowett commented Feb 14, 2026

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!

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. zBugBash-Consider

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add keyboard protocol support (the kitty one)

4 participants