Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iterm2 rich keyboard protocol support #1610

Merged
merged 40 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b2b8cd3
highly experimental support for iTerm2 input mode
unxed Apr 9, 2023
16ba0fc
Merge branch 'elfmz:master' into iterm2
unxed Apr 9, 2023
0843c59
merge kittys-keys fix
unxed Apr 9, 2023
04f3cbe
add more sequences (fixes F1, F2, F4 under WezTerm)
unxed Apr 9, 2023
eb9c84f
behave better on terminal focus lose
unxed Apr 10, 2023
3ca3f49
formatting
unxed Apr 10, 2023
5e459d3
Merge branch 'elfmz:master' into iterm2
unxed Apr 10, 2023
2ae121f
formatting
unxed Apr 10, 2023
d8b3e98
Merge branch 'iterm2' of https://github.com/unxed/far2l into iterm2
unxed Apr 10, 2023
c2f90f0
Merge branch 'elfmz:master' into iterm2
unxed Apr 10, 2023
7492f08
ignore unsupported esc sequences
unxed Apr 10, 2023
948c65a
Merge branch 'iterm2' of https://github.com/unxed/far2l into iterm2
unxed Apr 10, 2023
c4198fa
add flags change seqs support; debug output added
unxed Apr 10, 2023
bd9fe7a
map forwarddelete to backspace
unxed Apr 10, 2023
bb725b7
better del handling
unxed Apr 10, 2023
0e9b133
trying to fix ctrl+numbers
unxed Apr 10, 2023
45e3b42
workaround keydown events does not arrive sometimes in iterm2
unxed Apr 10, 2023
f153da0
fix a typo
unxed Apr 10, 2023
29263d0
Merge branch 'elfmz:master' into iterm2
unxed Apr 10, 2023
f0923ac
map right Command to right Control
unxed Apr 10, 2023
7a1eb8c
Merge branch 'iterm2' of https://github.com/unxed/far2l into iterm2
unxed Apr 10, 2023
6742418
Merge branch 'elfmz:master' into iterm2
unxed Apr 10, 2023
7484c5c
more logging
unxed Apr 10, 2023
3cce5fc
instead of mapping right Command to right Control, use right Option f…
unxed Apr 11, 2023
a0c74c3
cmd+v not working in iterm2 in 1337 mode (looks like iterm2's bug). w…
unxed Apr 11, 2023
ac36104
fix some problems in iterm2 cmd+v workaround
unxed Apr 11, 2023
98737ca
naming
unxed Apr 11, 2023
593b53a
Merge branch 'elfmz:master' into iterm2
unxed Apr 11, 2023
9418d2d
cmd key state change is reproted by iterm2 not as keydown by as flags…
unxed Apr 12, 2023
837a43d
make iterm2 cmd+v hack more reliable
unxed Apr 12, 2023
cb72b6a
another try to fix iterm2 workaround
unxed Apr 12, 2023
138b9f7
add some logging
unxed Apr 12, 2023
3e59007
forgot to set go variable
unxed Apr 12, 2023
273bc92
debugging...
unxed Apr 12, 2023
07d1d72
another try to fix iterm2 workaround
unxed Apr 12, 2023
8b56ed0
remove debug stuff
unxed Apr 12, 2023
3c7e8ba
comment out debug stuff
unxed Apr 12, 2023
6437a8f
Merge branch 'elfmz:master' into iterm2
unxed Apr 12, 2023
a23df17
comments
unxed Apr 13, 2023
36a3354
Merge branch 'iterm2' of https://github.com/unxed/far2l into iterm2
unxed Apr 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions WinPort/src/Backend/TTY/TTYBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
static volatile long s_terminal_size_change_id = 0;
static TTYBackend * g_vtb = nullptr;

long _iterm2_cmd_ts = 0;
bool _iterm2_cmd_state = 0;

static void OnSigHup(int signo);

static bool IsEnhancedKey(WORD code)
Expand Down Expand Up @@ -290,6 +293,13 @@ void TTYBackend::ReaderLoop()
}
//fprintf(stderr, "ReaderThread: CHAR 0x%x\n", (unsigned char)c);
tty_in->OnInput(buf, (size_t)rd);

// iTerm2 cmd+v workaround
if (_iterm2_cmd_state || _iterm2_cmd_ts) {
std::unique_lock<std::mutex> lock(_async_mutex);
_ae.flags.output = true;
_async_cond.notify_all();
}
}

if (FD_ISSET(_stdin, &fde)) {
Expand Down Expand Up @@ -361,6 +371,11 @@ void TTYBackend::WriterThread()
DispatchOSC52ClipSet(tty_out);
}

// iTerm2 cmd+v workaround
if (_iterm2_cmd_state || _iterm2_cmd_ts) {
tty_out.CheckiTerm2Hack();
}

tty_out.Flush();
tcdrain(_stdout);

Expand Down
307 changes: 306 additions & 1 deletion WinPort/src/Backend/TTY/TTYInputSequenceParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,304 @@ size_t TTYInputSequenceParser::TryParseAsWinTermEscapeSequence(const char *s, si
return n;
}

int flags_track = 0;

size_t TTYInputSequenceParser::TryParseAsiTerm2EscapeSequence(const char *s, size_t l)
{
/*
fprintf(stderr, "iTerm2 parsing: ");
for (size_t i = 0; i < l && s[i] != '\0'; i++) {
fprintf(stderr, "%c", s[i]);
}
fprintf(stderr, "\n");
*/

// protocol spec:
// https://gitlab.com/gnachman/iterm2/-/issues/7440#note_129307021

size_t len = 0;
while (1) {
if (len >= l) return TTY_PARSED_WANTMORE;
if (s[len] == 7) break;
len++;
}
len++;

unsigned int flags = 0;
unsigned int flags_length = 0;
sscanf(s + 8, "%i%n", &flags, &flags_length); // 8 is a fixed length of "]1337;d;"

flags -= 1;
unsigned int flags_win = 0;

// Flags changed event: esc ] 1337 ; f ; flags ^G
if (s[6] == 'f') {

bool go = false;
int vkc = 0, vsc = 0, kd = 0, cks = 0;

if ((flags & 1) && !(flags_track & 1)) { go = 1; vkc = VK_SHIFT; kd = 1; cks |= SHIFT_PRESSED; }
if (!(flags & 1) && (flags_track & 1)) { go = 1; vkc = VK_SHIFT; kd = 0; }
if ((flags & 2) && !(flags_track & 2)) { go = 1; vkc = VK_SHIFT; kd = 1; cks |= SHIFT_PRESSED;
vsc = RIGHT_SHIFT_VSC; }
if (!(flags & 2) && (flags_track & 2)) { go = 1; vkc = VK_SHIFT; kd = 0; vsc = RIGHT_SHIFT_VSC; }

if ((flags & 4) && !(flags_track & 4)) { go = 1; vkc = VK_MENU; kd = 1; cks |= LEFT_ALT_PRESSED; }
if (!(flags & 4) && (flags_track & 4)) { go = 1; vkc = VK_MENU; kd = 0; }
/*
if ((flags & 8) && !(flags_track & 8)) { go = 1; vkc = VK_MENU; kd = 1; cks |= RIGHT_ALT_PRESSED;
cks |= ENHANCED_KEY; }
if (!(flags & 8) && (flags_track & 8)) { go = 1; vkc = VK_MENU; kd = 0; cks |= ENHANCED_KEY; }
*/

if ((flags & 16) && !(flags_track & 16)) { go = 1; vkc = VK_CONTROL; kd = 1; cks |= LEFT_CTRL_PRESSED; }
if (!(flags & 16) && (flags_track & 16)) { go = 1; vkc = VK_CONTROL; kd = 0; }
if ((flags & 32) && !(flags_track & 32)) { go = 1; vkc = VK_CONTROL; kd = 1; cks |= RIGHT_CTRL_PRESSED;
cks |= ENHANCED_KEY; }
if (!(flags & 32) && (flags_track & 32)) { go = 1; vkc = VK_CONTROL; kd = 0; cks |= ENHANCED_KEY; }

// map right Option to right Control
if ((flags & 8) && !(flags_track & 8)) { go = 1; vkc = VK_CONTROL; kd = 1; cks |= RIGHT_CTRL_PRESSED;
cks |= ENHANCED_KEY; }
if (!(flags & 8) && (flags_track & 8)) { go = 1; vkc = VK_CONTROL; kd = 0; cks |= ENHANCED_KEY; }

// iTerm2 cmd+v workaround
if (((flags & 64) && !(flags_track & 64)) ||
((flags & 128) && !(flags_track & 128))) {
_iterm2_cmd_state = 1;
_iterm2_cmd_ts = time(NULL);
}

if (go) {
INPUT_RECORD ir = {0};
ir.EventType = KEY_EVENT;
ir.Event.KeyEvent.wVirtualKeyCode = vkc;
ir.Event.KeyEvent.wVirtualScanCode = vsc ? vsc :
WINPORT(MapVirtualKey)(ir.Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC);
ir.Event.KeyEvent.bKeyDown = kd;
ir.Event.KeyEvent.dwControlKeyState = cks;
ir.Event.KeyEvent.wRepeatCount = 1;

_ir_pending.emplace_back(ir);
}

flags_track = flags;
return len;
}


wchar_t uni_char = 0;
unsigned char bytes[4] = {0};
int num_bytes = 0;
int i;
for (i = (8 + flags_length + 1);; i += 2) { // 8 is a fixed length of "]1337;d;"
if (!isdigit(s[i]) && (s[i] < 'a' || s[i] > 'f')) break;
if (!isdigit(s[i + 1]) && (s[i + 1] < 'a' || s[i + 1] > 'f')) break;
sscanf(s + i, "%2hhx", &bytes[num_bytes]);
num_bytes++;
}

if (num_bytes == 1) {
uni_char = bytes[0];
} else if (num_bytes == 2) {
uni_char = ((bytes[0] & 0x1F) << 6) | (bytes[1] & 0x3F);
} else if (num_bytes == 3) {
uni_char = ((bytes[0] & 0x0F) << 12) | ((bytes[1] & 0x3F) << 6) | (bytes[2] & 0x3F);
} else if (num_bytes == 4) {
uni_char = ((bytes[0] & 0x07) << 18) | ((bytes[1] & 0x3F) << 12) | ((bytes[2] & 0x3F) << 6) | (bytes[3] & 0x3F);
}

unsigned int keycode = 0;
sscanf(s + i + 1, "%i", &keycode);

unsigned int vkc = 0;

// MacOS key codes:
// https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h

switch (keycode) {
case 0x00: vkc = 0x41; break; // A
case 0x01: vkc = 0x53; break; // S
case 0x02: vkc = 0x44; break; // D
case 0x03: vkc = 0x46; break; // F
case 0x04: vkc = 0x48; break; // H
case 0x05: vkc = 0x47; break; // G
case 0x06: vkc = 0x5A; break; // Z
case 0x07: vkc = 0x58; break; // X
case 0x08: vkc = 0x43; break; // C
case 0x09: vkc = 0x56; break; // V
case 0x0B: vkc = 0x42; break; // B
case 0x0C: vkc = 0x51; break; // Q
case 0x0D: vkc = 0x57; break; // W
case 0x0E: vkc = 0x45; break; // E
case 0x0F: vkc = 0x52; break; // R
case 0x10: vkc = 0x59; break; // Y
case 0x11: vkc = 0x54; break; // T
case 0x12: vkc = 0x31; break; // 1
case 0x13: vkc = 0x32; break; // 2
case 0x14: vkc = 0x33; break; // 3
case 0x15: vkc = 0x34; break; // 4
case 0x16: vkc = 0x36; break; // 6
case 0x17: vkc = 0x35; break; // 5
case 0x18: vkc = VK_OEM_PLUS; break; // =
case 0x19: vkc = 0x39; break; // 9
case 0x1A: vkc = 0x37; break; // 7
case 0x1B: vkc = VK_OEM_MINUS; break; // -
case 0x1C: vkc = 0x38; break; // 8
case 0x1D: vkc = 0x30; break; // 0
case 0x1E: vkc = VK_OEM_6; break; // ]
case 0x1F: vkc = 0x4F; break; // O
case 0x20: vkc = 0x55; break; // U
case 0x21: vkc = VK_OEM_4; break; // [
case 0x22: vkc = 0x49; break; // I
case 0x23: vkc = 0x50; break; // P
case 0x25: vkc = 0x4C; break; // L
case 0x26: vkc = 0x4A; break; // J
case 0x27: vkc = VK_OEM_7; break; // '
case 0x28: vkc = 0x4B; break; // K
case 0x29: vkc = VK_OEM_1; break; // ;
case 0x2A: vkc = VK_OEM_5; break; // Back Slash
case 0x2B: vkc = VK_OEM_COMMA; break; // ,
case 0x2C: vkc = VK_OEM_2; break; // Slash
case 0x2D: vkc = 0x4E; break; // N
case 0x2E: vkc = 0x4D; break; // M
case 0x2F: vkc = VK_OEM_PERIOD; break; // .
case 0x32: vkc = VK_OEM_3; break; // `
case 0x41: vkc = VK_DECIMAL; break; // .
case 0x43: vkc = VK_MULTIPLY; break; // *
case 0x45: vkc = VK_ADD; break; // +
//case 0x47: vkc = ; break; // keypad "clear"
case 0x4B: vkc = VK_DIVIDE; break; // /
case 0x4C: vkc = VK_RETURN; break; // Enter
case 0x4E: vkc = VK_SUBTRACT; break; // -
//case 0x51: vkc = ; break; // keypad "equals"
case 0x52: vkc = 0x60; break; // 0
case 0x53: vkc = 0x61; break; // 1
case 0x54: vkc = 0x62; break; // 2
case 0x55: vkc = 0x63; break; // 3
case 0x56: vkc = 0x64; break; // 4
case 0x57: vkc = 0x65; break; // 5
case 0x58: vkc = 0x66; break; // 6
case 0x59: vkc = 0x67; break; // 7
case 0x5B: vkc = 0x68; break; // 8
case 0x5C: vkc = 0x69; break; // 9

case 0x24: vkc = VK_RETURN; break; // Enter
case 0x30: vkc = VK_TAB; break; // Tab
case 0x31: vkc = VK_SPACE; break; // Space
case 0x33: vkc = VK_BACK; break; // Del https://discussions.apple.com/thread/4072343?answerId=18799493022#18799493022
case 0x35: vkc = VK_ESCAPE; break; // Esc
case 0x37: vkc = VK_LWIN; // Command
// iTerm2 cmd+v workaround
_iterm2_cmd_state = 1;
_iterm2_cmd_ts = time(NULL);
break;
case 0x38: vkc = VK_SHIFT; break; // Shift
case 0x39: vkc = VK_CAPITAL; break; // CapsLock
case 0x3A: vkc = VK_MENU; break; // Option
case 0x3B: vkc = VK_CONTROL; break; // Control
case 0x3C: vkc = VK_SHIFT; break; // RightShift
case 0x3D: vkc = VK_CONTROL; break; // RightOption // map right Option to right Control
case 0x3E: vkc = VK_CONTROL; break; // RightControl
//case 0x3F: vkc = ; break; // Function
//case 0x40: vkc = ; break; // F17
//case 0x48: vkc = ; break; // VolumeUp
//case 0x49: vkc = ; break; // VolumeDown
//case 0x4A: vkc = ; break; // Mute
//case 0x4F: vkc = ; break; // F18
//case 0x50: vkc = ; break; // F19
//case 0x5A: vkc = ; break; // F20
case 0x60: vkc = VK_F5; break; // F5
case 0x61: vkc = VK_F6; break; // F6
case 0x62: vkc = VK_F7; break; // F7
case 0x63: vkc = VK_F3; break; // F3
case 0x64: vkc = VK_F8; break; // F8
case 0x65: vkc = VK_F9; break; // F9
case 0x67: vkc = VK_F11; break; // F11
//case 0x69: vkc = ; break; // F13
//case 0x6A: vkc = ; break; // F16
//case 0x6B: vkc = ; break; // F14
case 0x6D: vkc = VK_F10; break; // F10
case 0x6F: vkc = VK_F12; break; // F12
//case 0x71: vkc = ; break; // F15
//case 0x72: vkc = ; break; // Help
case 0x73: vkc = VK_HOME; break; // Home
case 0x74: vkc = VK_PRIOR; break; // PageUp
case 0x75: vkc = VK_DELETE; break; // ForwardDelete https://discussions.apple.com/thread/4072343?answerId=18799493022#18799493022
case 0x76: vkc = VK_F4; break; // F4
case 0x77: vkc = VK_END; break; // End
case 0x78: vkc = VK_F2; break; // F2
case 0x79: vkc = VK_NEXT; break; // PageDown
case 0x7A: vkc = VK_F1; break; // F1
case 0x7B: vkc = VK_LEFT; break; // Left
case 0x7C: vkc = VK_RIGHT; break; // Right
case 0x7D: vkc = VK_DOWN; break; // Down
case 0x7E: vkc = VK_UP; break; // Up
}

if (!vkc && uni_char) { vkc = VK_UNASSIGNED; }

if (keycode == 0x3D) flags_win |= ENHANCED_KEY; // RightOption
if (keycode == 0x3E) flags_win |= ENHANCED_KEY; // RightControl
if (keycode == 0x4C) flags_win |= ENHANCED_KEY; // Numpad Enter

bool has_ctrl = 0;
if (flags & 1) { flags_win |= SHIFT_PRESSED; } // todo: LEFT_SHIFT_PRESSED
if (flags & 2) { flags_win |= SHIFT_PRESSED; } // todo: RIGHT_SHIFT_PRESSED
if (flags & 4) { flags_win |= LEFT_ALT_PRESSED; }
// if (flags & 8) { flags_win |= RIGHT_ALT_PRESSED; }
if (flags & 16) { flags_win |= LEFT_CTRL_PRESSED; has_ctrl = 1; }
if (flags & 32) { flags_win |= RIGHT_CTRL_PRESSED; has_ctrl = 1; }

// map right Option to right Control
if (flags & 8) { flags_win |= RIGHT_CTRL_PRESSED; }

if (has_ctrl && vkc >= 0x30 && vkc <= 0x39) {
// special handling of Ctrl+numbers
uni_char = vkc - 0x30 + '0';

if (s[6] == 'u' && vkc > 0x31 && vkc < 0x39) {
// iterm generates only keyup events in such cases
// lets emulate keydown also
INPUT_RECORD ir = {};
ir.EventType = KEY_EVENT;
ir.Event.KeyEvent.wVirtualKeyCode = vkc;
ir.Event.KeyEvent.wVirtualScanCode =
WINPORT(MapVirtualKey)(ir.Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC);
ir.Event.KeyEvent.uChar.UnicodeChar = uni_char;
ir.Event.KeyEvent.bKeyDown = TRUE;
ir.Event.KeyEvent.dwControlKeyState = flags_win;
ir.Event.KeyEvent.wRepeatCount = 1;
if (keycode == 0x3C) ir.Event.KeyEvent.wVirtualScanCode = RIGHT_SHIFT_VSC; // RightShift
_ir_pending.emplace_back(ir);
}
}

INPUT_RECORD ir = {};
ir.EventType = KEY_EVENT;
ir.Event.KeyEvent.wVirtualKeyCode = vkc;
ir.Event.KeyEvent.wVirtualScanCode =
WINPORT(MapVirtualKey)(ir.Event.KeyEvent.wVirtualKeyCode, MAPVK_VK_TO_VSC);
ir.Event.KeyEvent.uChar.UnicodeChar = uni_char;
ir.Event.KeyEvent.bKeyDown = (s[6] == 'd' ? TRUE : FALSE);
ir.Event.KeyEvent.dwControlKeyState = flags_win;
ir.Event.KeyEvent.wRepeatCount = 1;
if (keycode == 0x3C) ir.Event.KeyEvent.wVirtualScanCode = RIGHT_SHIFT_VSC; // RightShift
_ir_pending.emplace_back(ir);

flags_track = flags;
return len;
}

size_t TTYInputSequenceParser::ParseEscapeSequence(const char *s, size_t l)
{
/*
fprintf(stderr, "Parsing: ");
for (size_t i = 0; i < l && s[i] != '\0'; i++) {
fprintf(stderr, "%c", s[i]);
}
fprintf(stderr, "\n");
*/

if (l > 2 && s[0] == '[' && s[2] == 'n') {
return 3;
Expand Down Expand Up @@ -667,7 +963,16 @@ size_t TTYInputSequenceParser::ParseEscapeSequence(const char *s, size_t l)
return 5;
}

size_t r = ParseNChars2Key(s, l);
size_t r = 0;

if (l > 1 && s[0] == ']' && s[1] == '1' && s[2] == '3' && s[3] == '3' && s[4] == '7' && s[5] == ';') {
r = TryParseAsiTerm2EscapeSequence(s, l);
if (r != TTY_PARSED_BADSEQUENCE) {
return r;
}
}

r = ParseNChars2Key(s, l);
if (r != 0)
return r;

Expand Down
4 changes: 4 additions & 0 deletions WinPort/src/Backend/TTY/TTYInputSequenceParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <map>
#include <StackSerializer.h>

extern long _iterm2_cmd_ts;
extern bool _iterm2_cmd_state;

struct TTYInputKey
{
WORD vk;
Expand Down Expand Up @@ -112,6 +115,7 @@ class TTYInputSequenceParser
void ParseMouse(char action, char col, char raw);
void ParseAPC(const char *s, size_t l);
size_t TryParseAsWinTermEscapeSequence(const char *s, size_t l);
size_t TryParseAsiTerm2EscapeSequence(const char *s, size_t l);
size_t TryParseAsKittyEscapeSequence(const char *s, size_t l);
size_t ParseEscapeSequence(const char *s, size_t l);
void OnBracketedPaste(bool start);
Expand Down
Loading