Repository navigation
Implement the Kitty Keyboard Protocol - #19817
Conversation
| 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
| { | ||
| 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
| case CsiActionCodes::ANSISYSRC_CursorRestore: | ||
| _dispatch->CursorRestoreState(); | ||
| break; | ||
| case CsiActionCodes::KKP_KittyKeyboardSet: |
Check failure
Code scanning / check-spelling
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
| case CsiActionCodes::KKP_KittyKeyboardQuery: | ||
| _dispatch->QueryKittyKeyboardProtocol(); | ||
| break; | ||
| case CsiActionCodes::KKP_KittyKeyboardPush: |
Check failure
Code scanning / check-spelling
Unrecognized Spelling
| case CsiActionCodes::KKP_KittyKeyboardPush: | ||
| _dispatch->PushKittyKeyboardProtocol(parameters.at(0)); | ||
| break; | ||
| case CsiActionCodes::KKP_KittyKeyboardPop: |
Check failure
Code scanning / check-spelling
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
Dustin L. Howett (DHowett)
left a comment
There was a problem hiding this comment.
we are unfortunately going to need some tests. or something. of the weird corner cases.
Dustin L. Howett (DHowett)
left a comment
There was a problem hiding this comment.
Almost all the way done. Can you explain the conhost win32im thing? Should we actually use Kitty as our internal protocol?
|
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. |
|
(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. |
This comment has been minimized.
This comment has been minimized.
|
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. |
a4df1f4 to
4f86d4c
Compare
This comment has been minimized.
This comment has been minimized.
|
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...) |
|
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. |
|
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. 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. |
Dustin L. Howett (DHowett)
left a comment
There was a problem hiding this comment.
reviewed everything except terminalInput itself
| CATCH_RETURN(); | ||
| } | ||
|
|
||
| // TODO: Avoid translating win32im sequences to Kitty Keyboard Protocol temporarily. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
if KKP-specific, we could call it kkpAltKey...?
There was a problem hiding this comment.
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.
Dustin L. Howett (DHowett)
left a comment
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Yeah, but we should keep it that way right?
| if (key.virtualKey == VK_PACKET || key.virtualKey == 0) | ||
| { | ||
| _lastVirtualKeyCode = std::nullopt; | ||
| return _makeCharOutput(key.codepoint); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
How does the new implementation handle dead keys?
There was a problem hiding this comment.
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.
|
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! |
|
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. |
…ty-keyboard-protocol
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>
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>
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.
This essentially rewrites
TerminalInputfrom 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✅\'✅