Skip to content

Commit e1f2ee0

Browse files
authored
Merge pull request #1593 from unxed/kittys-keys
added support for kovidgoyal's kitty keyboard protocol
2 parents a5d10ac + cf6721b commit e1f2ee0

File tree

3 files changed

+281
-0
lines changed

3 files changed

+281
-0
lines changed

WinPort/src/Backend/TTY/TTYInputSequenceParser.cpp

+278
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,276 @@ void TTYInputSequenceParser::ParseAPC(const char *s, size_t l)
309309
}
310310
}
311311

312+
bool right_ctrl_down = 0;
313+
314+
size_t TTYInputSequenceParser::TryParseAsKittyEscapeSequence(const char *s, size_t l)
315+
{
316+
// kovidgoyal's kitty keyboard protocol (progressive enhancement flags 15) support
317+
// CSI [ XXX : XXX : XXX ; XXX : XXX [u~ABCDEFHPQRS]
318+
// some parts sometimes ommitted, see docs
319+
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/
320+
321+
// todo: enhanced key flag now set for essential keys only, should be set for more ones
322+
323+
// todo: add more keys. all needed by far2l seem to be here, but kitty supports much more
324+
325+
#define KITTY_MOD_SHIFT 1
326+
#define KITTY_MOD_ALT 2
327+
#define KITTY_MOD_CONTROL 4
328+
#define KITTY_MOD_CAPSLOCK 64
329+
#define KITTY_MOD_NUMLOCK 128
330+
#define KITTY_EVT_KEYUP 3
331+
332+
/** 32 is enough without "text-as-code points" mode, but should be increased if this mode is enabled */
333+
const int max_kitty_esc_size = 32;
334+
335+
/** first_limit should be set to 3 if "text-as-code points" mode is on */
336+
/** also second_limit should be increased to maximum # of code points per key in "text-as-code points" mode */
337+
const char first_limit = 2;
338+
const char second_limit = 3;
339+
int params[first_limit][second_limit] = {0};
340+
int first_count = 0;
341+
int second_count = 0;
342+
bool end_found = 0;
343+
size_t i;
344+
345+
for (i = 1;; i++) {
346+
if (i >= l) {
347+
return LIKELY(l < max_kitty_esc_size) ? TTY_PARSED_WANTMORE : TTY_PARSED_BADSEQUENCE;
348+
}
349+
if (s[i] == ';') {
350+
second_count = 0;
351+
first_count++;
352+
if (first_count >= first_limit) {
353+
return TTY_PARSED_BADSEQUENCE;
354+
}
355+
} else if (s[i] == ':') {
356+
second_count++;
357+
if (second_count >= second_limit) {
358+
return TTY_PARSED_BADSEQUENCE;
359+
}
360+
} else if (!isdigit(s[i])) {
361+
end_found = true;
362+
break;
363+
} else { // digit
364+
params[first_count][second_count] = atoi(&s[i]);
365+
while (i < l && isdigit(s[i])) {
366+
++i;
367+
}
368+
i--;
369+
}
370+
}
371+
372+
// check for correct sequence ending
373+
end_found = end_found && (
374+
(s[i] == 'u') || (s[i] == '~') ||
375+
(s[i] == 'A') || (s[i] == 'B') ||
376+
(s[i] == 'C') || (s[i] == 'D') ||
377+
(s[i] == 'E') || (s[i] == 'F') ||
378+
(s[i] == 'H') || (s[i] == 'P') ||
379+
(s[i] == 'Q') ||
380+
(s[i] == 'R') || // "R" is still vaild here in old kitty versions
381+
(s[i] == 'S')
382+
);
383+
384+
if (!end_found) {
385+
return TTY_PARSED_BADSEQUENCE;
386+
}
387+
388+
/*
389+
fprintf(stderr, "%i %i %i %i %i \n", params[0][0], params[0][1], params[0][2], params[0][3], params[0][4]);
390+
fprintf(stderr, "%i %i %i %i %i \n", params[1][0], params[1][1], params[1][2], params[1][3], params[1][4]);
391+
fprintf(stderr, "%i %i %i %i %i \n", params[2][0], params[2][1], params[2][2], params[2][3], params[2][4]);
392+
fprintf(stderr, "%i %i\n", first_count, second_count);
393+
*/
394+
395+
int event_type = params[1][1];
396+
int modif_state = params[1][0];
397+
398+
INPUT_RECORD ir = {0};
399+
ir.EventType = KEY_EVENT;
400+
401+
if (modif_state) {
402+
modif_state -= 1;
403+
404+
if (modif_state & KITTY_MOD_SHIFT) { ir.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED; }
405+
if (modif_state & KITTY_MOD_ALT) { ir.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED; }
406+
if (modif_state & KITTY_MOD_CONTROL) { ir.Event.KeyEvent.dwControlKeyState |=
407+
right_ctrl_down ? RIGHT_CTRL_PRESSED : LEFT_CTRL_PRESSED; }
408+
if (modif_state & KITTY_MOD_CAPSLOCK) { ir.Event.KeyEvent.dwControlKeyState |= CAPSLOCK_ON; }
409+
if (modif_state & KITTY_MOD_NUMLOCK) { ir.Event.KeyEvent.dwControlKeyState |= NUMLOCK_ON; }
410+
}
411+
412+
int base_char = params[0][2] ? params[0][2] : params[0][0];
413+
if (base_char <= UCHAR_MAX && isalpha(base_char)) {
414+
ir.Event.KeyEvent.wVirtualKeyCode = (base_char - 'a') + 0x41;
415+
}
416+
if (base_char <= UCHAR_MAX && isdigit(base_char)) {
417+
ir.Event.KeyEvent.wVirtualKeyCode = (base_char - '0') + 0x30;
418+
}
419+
switch (base_char) {
420+
case '`' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_3; break;
421+
case '-' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_MINUS; break;
422+
case '=' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_PLUS; break;
423+
case '[' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_4; break;
424+
case ']' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_6; break;
425+
case '\\' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_5; break;
426+
case ';' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_1; break;
427+
case '\'' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_7; break;
428+
case ',' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_COMMA; break;
429+
case '.' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_PERIOD; break;
430+
case '/' : ir.Event.KeyEvent.wVirtualKeyCode = VK_OEM_2; break;
431+
case 9 : ir.Event.KeyEvent.wVirtualKeyCode = VK_TAB; break;
432+
case 27 : ir.Event.KeyEvent.wVirtualKeyCode = VK_ESCAPE; break;
433+
case 13 : if (s[i] == '~') {
434+
ir.Event.KeyEvent.wVirtualKeyCode = VK_F3;
435+
} else {
436+
ir.Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
437+
}
438+
break;
439+
case 127 : ir.Event.KeyEvent.wVirtualKeyCode = VK_BACK; break;
440+
case 2 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_INSERT; break;
441+
case 3 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_DELETE; break;
442+
case 5 : if (s[i] == '~') { ir.Event.KeyEvent.wVirtualKeyCode = VK_PRIOR;
443+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; } break;
444+
case 6 : if (s[i] == '~') { ir.Event.KeyEvent.wVirtualKeyCode = VK_NEXT;
445+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; } break;
446+
case 15 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F5; break;
447+
case 17 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F6; break;
448+
case 18 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F7; break;
449+
case 19 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F8; break;
450+
case 20 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F9; break;
451+
case 21 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F10; break;
452+
case 23 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F11; break;
453+
case 24 : if (s[i] == '~') ir.Event.KeyEvent.wVirtualKeyCode = VK_F12; break;
454+
case 57399 : case 57425 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD0; break;
455+
case 57400 : case 57424 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD1; break;
456+
case 57401 : case 57420 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD2; break;
457+
case 57402 : case 57422 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD3; break;
458+
case 57403 : case 57417 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD4; break;
459+
case 57404 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD5; break;
460+
case 57405 : case 57418 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD6; break;
461+
case 57406 : case 57423 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD7; break;
462+
case 57407 : case 57419 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD8; break;
463+
case 57408 : case 57421 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD9; break;
464+
case 57409 : case 57426 : ir.Event.KeyEvent.wVirtualKeyCode = VK_DECIMAL; break;
465+
466+
case 57410 : ir.Event.KeyEvent.wVirtualKeyCode = VK_DIVIDE; break;
467+
case 57411 : ir.Event.KeyEvent.wVirtualKeyCode = VK_MULTIPLY; break;
468+
case 57412 : ir.Event.KeyEvent.wVirtualKeyCode = VK_SUBTRACT; break;
469+
case 57413 : ir.Event.KeyEvent.wVirtualKeyCode = VK_ADD; break;
470+
case 57414 : ir.Event.KeyEvent.wVirtualKeyCode = VK_RETURN; break;
471+
472+
case 57444 : ir.Event.KeyEvent.wVirtualKeyCode = VK_LWIN; break;
473+
case 57450 : ir.Event.KeyEvent.wVirtualKeyCode = VK_RWIN; break;
474+
case 57363 : ir.Event.KeyEvent.wVirtualKeyCode = VK_APPS; break;
475+
476+
case 57448 : ir.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
477+
if (event_type != KITTY_EVT_KEYUP) {
478+
right_ctrl_down = 1;
479+
ir.Event.KeyEvent.dwControlKeyState |= RIGHT_CTRL_PRESSED;
480+
} else {
481+
right_ctrl_down = 0;
482+
ir.Event.KeyEvent.dwControlKeyState &= ~RIGHT_CTRL_PRESSED;
483+
}
484+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY;
485+
break;
486+
case 57442 : ir.Event.KeyEvent.wVirtualKeyCode = VK_CONTROL;
487+
if (event_type != KITTY_EVT_KEYUP) {
488+
ir.Event.KeyEvent.dwControlKeyState |= LEFT_CTRL_PRESSED;
489+
} else {
490+
ir.Event.KeyEvent.dwControlKeyState &= ~LEFT_CTRL_PRESSED;
491+
}
492+
break;
493+
case 57443 : ir.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
494+
if (event_type != KITTY_EVT_KEYUP) {
495+
ir.Event.KeyEvent.dwControlKeyState |= LEFT_ALT_PRESSED;
496+
} else {
497+
ir.Event.KeyEvent.dwControlKeyState &= ~LEFT_ALT_PRESSED;
498+
}
499+
break;
500+
case 57449 : ir.Event.KeyEvent.wVirtualKeyCode = VK_MENU;
501+
if (event_type != KITTY_EVT_KEYUP) {
502+
ir.Event.KeyEvent.dwControlKeyState |= RIGHT_ALT_PRESSED;
503+
} else {
504+
ir.Event.KeyEvent.dwControlKeyState &= ~RIGHT_ALT_PRESSED;
505+
}
506+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY;
507+
break;
508+
case 57441 : ir.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
509+
// todo: add LEFT_SHIFT_PRESSED / RIGHT_SHIFT_PRESSED
510+
// see https://github.com/microsoft/terminal/issues/337
511+
if (event_type != KITTY_EVT_KEYUP) {
512+
ir.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
513+
} else {
514+
ir.Event.KeyEvent.dwControlKeyState &= ~SHIFT_PRESSED;
515+
}
516+
break;
517+
case 57447 : ir.Event.KeyEvent.wVirtualKeyCode = VK_SHIFT;
518+
// todo: add LEFT_SHIFT_PRESSED / RIGHT_SHIFT_PRESSED
519+
// see https://github.com/microsoft/terminal/issues/337
520+
if (event_type != KITTY_EVT_KEYUP) {
521+
ir.Event.KeyEvent.dwControlKeyState |= SHIFT_PRESSED;
522+
} else {
523+
ir.Event.KeyEvent.dwControlKeyState &= ~SHIFT_PRESSED;
524+
}
525+
ir.Event.KeyEvent.wVirtualScanCode = RIGHT_SHIFT_VSC;
526+
break;
527+
528+
case 57360 : ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMLOCK; break;
529+
case 57358 : ir.Event.KeyEvent.wVirtualKeyCode = VK_CAPITAL; break;
530+
531+
}
532+
switch (s[i]) {
533+
case 'A': ir.Event.KeyEvent.wVirtualKeyCode = VK_UP;
534+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; break;
535+
case 'B': ir.Event.KeyEvent.wVirtualKeyCode = VK_DOWN;
536+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; break;
537+
case 'C': ir.Event.KeyEvent.wVirtualKeyCode = VK_RIGHT;
538+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; break;
539+
case 'D': ir.Event.KeyEvent.wVirtualKeyCode = VK_LEFT;
540+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; break;
541+
case 'E': ir.Event.KeyEvent.wVirtualKeyCode = VK_NUMPAD5; break;
542+
case 'H': ir.Event.KeyEvent.wVirtualKeyCode = VK_HOME;
543+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; break;
544+
case 'F': ir.Event.KeyEvent.wVirtualKeyCode = VK_END;
545+
ir.Event.KeyEvent.dwControlKeyState |= ENHANCED_KEY; break;
546+
case 'P': ir.Event.KeyEvent.wVirtualKeyCode = VK_F1; break;
547+
case 'Q': ir.Event.KeyEvent.wVirtualKeyCode = VK_F2; break;
548+
case 'R': ir.Event.KeyEvent.wVirtualKeyCode = VK_F3; break;
549+
case 'S': ir.Event.KeyEvent.wVirtualKeyCode = VK_F4; break;
550+
}
551+
552+
if (ir.Event.KeyEvent.wVirtualScanCode == 0) {
553+
ir.Event.KeyEvent.wVirtualScanCode =
554+
WINPORT(MapVirtualKey)(ir.Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC);
555+
}
556+
557+
ir.Event.KeyEvent.uChar.UnicodeChar = params[0][1] ? params[0][1] : params[0][0];
558+
if (
559+
(ir.Event.KeyEvent.uChar.UnicodeChar < 32) ||
560+
(ir.Event.KeyEvent.uChar.UnicodeChar == 127) ||
561+
((ir.Event.KeyEvent.uChar.UnicodeChar >= 57358) && (ir.Event.KeyEvent.uChar.UnicodeChar <= 57454)) ||
562+
!(WCHAR_IS_VALID(ir.Event.KeyEvent.uChar.UnicodeChar))
563+
) {
564+
// those are special values, should not be used as unicode char
565+
ir.Event.KeyEvent.uChar.UnicodeChar = 0;
566+
}
567+
if ((modif_state & KITTY_MOD_CAPSLOCK) && !(modif_state & KITTY_MOD_SHIFT)) {
568+
// it's weird, but kitty can not give us uppercase utf8 in caps lock mode
569+
// ("text-as-codepoints" mode should solve it, but it is not working for cyrillic chars for unknown reason)
570+
ir.Event.KeyEvent.uChar.UnicodeChar = towupper(ir.Event.KeyEvent.uChar.UnicodeChar);
571+
}
572+
573+
ir.Event.KeyEvent.bKeyDown = (event_type != KITTY_EVT_KEYUP) ? 1 : 0;
574+
575+
ir.Event.KeyEvent.wRepeatCount = 0;
576+
577+
_ir_pending.emplace_back(ir);
578+
579+
return i+1;
580+
}
581+
312582
size_t TTYInputSequenceParser::TryParseAsWinTermEscapeSequence(const char *s, size_t l)
313583
{
314584
// check for nasty win32-input-mode sequence: as described in
@@ -359,6 +629,7 @@ size_t TTYInputSequenceParser::TryParseAsWinTermEscapeSequence(const char *s, si
359629

360630
size_t TTYInputSequenceParser::ParseEscapeSequence(const char *s, size_t l)
361631
{
632+
362633
if (l > 2 && s[0] == '[' && s[2] == 'n') {
363634
return 3;
364635
}
@@ -390,6 +661,13 @@ size_t TTYInputSequenceParser::ParseEscapeSequence(const char *s, size_t l)
390661
if (r != 0)
391662
return r;
392663

664+
if (l > 1 && s[0] == '[') {
665+
r = TryParseAsKittyEscapeSequence(s, l);
666+
if (r != TTY_PARSED_BADSEQUENCE) {
667+
return r;
668+
}
669+
}
670+
393671
if (l > 1 && s[0] == '[') {
394672
r = TryParseAsWinTermEscapeSequence(s, l);
395673
if (r != TTY_PARSED_BADSEQUENCE)

WinPort/src/Backend/TTY/TTYInputSequenceParser.h

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class TTYInputSequenceParser
112112
void ParseMouse(char action, char col, char raw);
113113
void ParseAPC(const char *s, size_t l);
114114
size_t TryParseAsWinTermEscapeSequence(const char *s, size_t l);
115+
size_t TryParseAsKittyEscapeSequence(const char *s, size_t l);
115116
size_t ParseEscapeSequence(const char *s, size_t l);
116117
void OnBracketedPaste(bool start);
117118

WinPort/src/Backend/TTY/TTYOutput.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ TTYOutput::TTYOutput(int out, bool far2l_tty)
163163

164164
Format(ESC "7" ESC "[?47h" ESC "[?1049h" ESC "[?2004h");
165165
Format(ESC "[?9001h"); // win32-input-mode on
166+
Format(ESC "[=15;1u"); // kovidgoyal's kitty mode on
166167
ChangeKeypad(true);
167168
ChangeMouse(true);
168169

@@ -186,6 +187,7 @@ TTYOutput::~TTYOutput()
186187
if (!_kernel_tty) {
187188
Format(ESC "[0 q");
188189
}
190+
Format(ESC "[=0;1u" "\r"); // kovidgoyal's kitty mode off
189191
Format(ESC "[0m" ESC "[?1049l" ESC "[?47l" ESC "8" ESC "[?2004l" "\r\n");
190192
Format(ESC "[?9001l"); // win32-input-mode off
191193
TTYBasePalette def_palette;

0 commit comments

Comments
 (0)