mirror of
https://github.com/microsoft/terminal.git
synced 2026-02-03 18:17:40 -06:00
wip
This commit is contained in:
parent
173f897751
commit
940e8d5c2a
@ -96,7 +96,7 @@ namespace
|
||||
|
||||
// flags=0 (0b00000): No enhancements - legacy mode
|
||||
// Escape key in legacy mode: just ESC byte
|
||||
TestCase{ L"Flags=0 (none) Esc key", L"\x1b", 0, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=0 (none) Esc key", L"\x1b", 0, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=1 (0b00001): DisambiguateEscapeCodes only
|
||||
// Escape key becomes CSI 27 u
|
||||
@ -104,24 +104,24 @@ namespace
|
||||
|
||||
// flags=2 (0b00010): ReportEventTypes only
|
||||
// No disambiguation, so Esc is still legacy (but with event type tracking internally)
|
||||
TestCase{ L"Flags=2 (EventTypes) Esc key down", L"\x1b", 2, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=2 (EventTypes) Esc key down", L"\x1b", 2, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=3 (0b00011): Disambiguate + EventTypes
|
||||
// Escape key with event type: CSI 27;1:1 u (mod=1, event=press=1)
|
||||
TestCase{ L"Flags=3 (Disambiguate+EventTypes) Esc key press", L"\x1b[27;1:1u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=3 (Disambiguate+EventTypes) Esc key press", L"\x1b[27u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=4 (0b00100): ReportAlternateKeys only
|
||||
// Without Disambiguate, Escape is still legacy
|
||||
TestCase{ L"Flags=4 (AltKeys) Esc key", L"\x1b", 4, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=4 (AltKeys) Esc key", L"\x1b", 4, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=5 (0b00101): Disambiguate + AltKeys
|
||||
TestCase{ L"Flags=5 (Disambiguate+AltKeys) Esc key", L"\x1b[27u", 5, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=6 (0b00110): EventTypes + AltKeys
|
||||
TestCase{ L"Flags=6 (EventTypes+AltKeys) Esc key", L"\x1b", 6, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=6 (EventTypes+AltKeys) Esc key", L"\x1b", 6, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=7 (0b00111): Disambiguate + EventTypes + AltKeys
|
||||
TestCase{ L"Flags=7 (Disambiguate+EventTypes+AltKeys) Esc key press", L"\x1b[27;1:1u", 7, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=7 (Disambiguate+EventTypes+AltKeys) Esc key press", L"\x1b[27u", 7, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=8 (0b01000): ReportAllKeysAsEscapeCodes only
|
||||
// All keys become CSI u, including Escape
|
||||
@ -131,10 +131,10 @@ namespace
|
||||
TestCase{ L"Flags=9 (Disambiguate+AllKeys) Esc key", L"\x1b[27u", 9, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=10 (0b01010): EventTypes + AllKeys
|
||||
TestCase{ L"Flags=10 (EventTypes+AllKeys) Esc key press", L"\x1b[27;1:1u", 10, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=10 (EventTypes+AllKeys) Esc key press", L"\x1b[27u", 10, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=11 (0b01011): Disambiguate + EventTypes + AllKeys
|
||||
TestCase{ L"Flags=11 (Disambiguate+EventTypes+AllKeys) Esc key press", L"\x1b[27;1:1u", 11, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=11 (Disambiguate+EventTypes+AllKeys) Esc key press", L"\x1b[27u", 11, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=12 (0b01100): AltKeys + AllKeys
|
||||
TestCase{ L"Flags=12 (AltKeys+AllKeys) Esc key", L"\x1b[27u", 12, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
@ -143,59 +143,59 @@ namespace
|
||||
TestCase{ L"Flags=13 (Disambiguate+AltKeys+AllKeys) Esc key", L"\x1b[27u", 13, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=14 (0b01110): EventTypes + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=14 (EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27;1:1u", 14, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=14 (EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27u", 14, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=15 (0b01111): Disambiguate + EventTypes + AltKeys + AllKeys
|
||||
TestCase{ L"Flags=15 (Disambiguate+EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27;1:1u", 15, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=15 (Disambiguate+EventTypes+AltKeys+AllKeys) Esc key press", L"\x1b[27u", 15, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=16 (0b10000): ReportAssociatedText only (meaningless without AllKeys)
|
||||
TestCase{ L"Flags=16 (AssocText) Esc key", L"\x1b", 16, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=16 (AssocText) Esc key", L"\x1b", 16, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=17 (0b10001): Disambiguate + AssocText
|
||||
TestCase{ L"Flags=17 (Disambiguate+AssocText) Esc key", L"\x1b[27u", 17, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=18 (0b10010): EventTypes + AssocText
|
||||
TestCase{ L"Flags=18 (EventTypes+AssocText) Esc key", L"\x1b", 18, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=18 (EventTypes+AssocText) Esc key", L"\x1b", 18, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=19 (0b10011): Disambiguate + EventTypes + AssocText
|
||||
TestCase{ L"Flags=19 (Disambiguate+EventTypes+AssocText) Esc key press", L"\x1b[27;1:1u", 19, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=19 (Disambiguate+EventTypes+AssocText) Esc key press", L"\x1b[27u", 19, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=20 (0b10100): AltKeys + AssocText
|
||||
TestCase{ L"Flags=20 (AltKeys+AssocText) Esc key", L"\x1b", 20, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=20 (AltKeys+AssocText) Esc key", L"\x1b", 20, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=21 (0b10101): Disambiguate + AltKeys + AssocText
|
||||
TestCase{ L"Flags=21 (Disambiguate+AltKeys+AssocText) Esc key", L"\x1b[27u", 21, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=22 (0b10110): EventTypes + AltKeys + AssocText
|
||||
TestCase{ L"Flags=22 (EventTypes+AltKeys+AssocText) Esc key", L"\x1b", 22, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=22 (EventTypes+AltKeys+AssocText) Esc key", L"\x1b", 22, true, VK_ESCAPE, 0x01, L'\x1b', 0 },
|
||||
|
||||
// flags=23 (0b10111): Disambiguate + EventTypes + AltKeys + AssocText
|
||||
TestCase{ L"Flags=23 (Disambiguate+EventTypes+AltKeys+AssocText) Esc key press", L"\x1b[27;1:1u", 23, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"Flags=23 (Disambiguate+EventTypes+AltKeys+AssocText) Esc key press", L"\x1b[27u", 23, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// flags=24 (0b11000): AllKeys + AssocText
|
||||
// 'a' key with text reporting: CSI 97;;97 u
|
||||
TestCase{ L"Flags=24 (AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 24, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=24 (AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 24, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=25 (0b11001): Disambiguate + AllKeys + AssocText
|
||||
TestCase{ L"Flags=25 (Disambiguate+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 25, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=25 (Disambiguate+AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 25, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=26 (0b11010): EventTypes + AllKeys + AssocText
|
||||
TestCase{ L"Flags=26 (EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 26, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=26 (EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;;97u", 26, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=27 (0b11011): Disambiguate + EventTypes + AllKeys + AssocText
|
||||
TestCase{ L"Flags=27 (Disambiguate+EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 27, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=27 (Disambiguate+EventTypes+AllKeys+AssocText) 'a' key press", L"\x1b[97;;97u", 27, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=28 (0b11100): AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=28 (AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 28, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=28 (AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 28, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=29 (0b11101): Disambiguate + AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=29 (Disambiguate+AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;1;97u", 29, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=29 (Disambiguate+AltKeys+AllKeys+AssocText) 'a' key", L"\x1b[97;;97u", 29, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=30 (0b11110): EventTypes + AltKeys + AllKeys + AssocText
|
||||
TestCase{ L"Flags=30 (EventTypes+AltKeys+AllKeys+AssocText) 'a' key press", L"\x1b[97;1:1;97u", 30, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=30 (EventTypes+AltKeys+AllKeys+AssocText) 'a' key press", L"\x1b[97;;97u", 30, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// flags=31 (0b11111): All flags enabled
|
||||
TestCase{ L"Flags=31 (all) 'a' key press", L"\x1b[97;1:1;97u", 31, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"Flags=31 (all) 'a' key press", L"\x1b[97;;97u", 31, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 2: Modifier Combinations with Disambiguate (flag=1)
|
||||
@ -297,13 +297,13 @@ namespace
|
||||
// ====================================================================
|
||||
|
||||
// Key press with Disambiguate+EventTypes (flag=3)
|
||||
TestCase{ L"EventTypes: Esc press", L"\x1b[27;1:1u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
TestCase{ L"EventTypes: Esc press", L"\x1b[27u", 3, true, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// Key release with Disambiguate+EventTypes (flag=3)
|
||||
TestCase{ L"EventTypes: Esc release", L"\x1b[27;1:3u", 3, false, VK_ESCAPE, 0x01, 0, 0 },
|
||||
|
||||
// Key press with AllKeys+EventTypes (flag=10)
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' press", L"\x1b[97;1:1u", 10, true, 'A', 0x1E, L'a', 0 },
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' press", L"\x1b[97u", 10, true, 'A', 0x1E, L'a', 0 },
|
||||
|
||||
// Key release with AllKeys+EventTypes (flag=10)
|
||||
TestCase{ L"EventTypes+AllKeys: 'a' release", L"\x1b[97;1:3u", 10, false, 'A', 0x1E, L'a', 0 },
|
||||
@ -314,8 +314,8 @@ namespace
|
||||
TestCase{ L"EventTypes+AllKeys: Tab release", L"\x1b[9;1:3u", 10, false, VK_TAB, 0x0F, L'\t', 0 },
|
||||
TestCase{ L"EventTypes+AllKeys: Backspace release", L"\x1b[127;1:3u", 10, false, VK_BACK, 0x0E, L'\b', 0 },
|
||||
|
||||
// Press with modifier: Shift+Esc -> CSI 27;2:1 u
|
||||
TestCase{ L"EventTypes: Shift+Esc press", L"\x1b[27;2:1u", 3, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
// Press with modifier: Shift+Esc -> CSI 27;2 u
|
||||
TestCase{ L"EventTypes: Shift+Esc press", L"\x1b[27;2u", 3, true, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
|
||||
// Release with modifier: Shift+Esc -> CSI 27;2:3 u
|
||||
TestCase{ L"EventTypes: Shift+Esc release", L"\x1b[27;2:3u", 3, false, VK_ESCAPE, 0x01, 0, SHIFT_PRESSED },
|
||||
@ -515,11 +515,11 @@ namespace
|
||||
|
||||
// AllKeys + EventTypes + CapsLock: 'a' press with CapsLock
|
||||
// mod=1+64=65, event=press=1
|
||||
TestCase{ L"AllKeys+EventTypes: CapsLock+a press", L"\x1b[97;65:1u", 10, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
TestCase{ L"AllKeys+EventTypes: CapsLock+a press", L"\x1b[97;65u", 10, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
|
||||
// AllKeys + EventTypes + all modifiers: press
|
||||
// mod=1+1+2+4+64+128=200, event=1
|
||||
TestCase{ L"AllKeys+EventTypes: all mods press", L"\x1b[97;200:1u", 10, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
TestCase{ L"AllKeys+EventTypes: all mods press", L"\x1b[97;200u", 10, true, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// AllKeys + EventTypes + all modifiers: release
|
||||
TestCase{ L"AllKeys+EventTypes: all mods release", L"\x1b[97;200:3u", 10, false, 'A', 0x1E, L'\x01', SHIFT_PRESSED | ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
@ -538,7 +538,7 @@ namespace
|
||||
|
||||
// Ctrl+a produces control character (0x01), which should not be in text
|
||||
// Text field should be omitted for control codes
|
||||
TestCase{ L"AllKeys+AssocText: Ctrl+a (no text)", L"\x1b[97;5;1u", 24, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
TestCase{ L"AllKeys+AssocText: Ctrl+a (no text)", L"\x1b[97;5u", 24, true, 'A', 0x1E, L'\x01', CTRL_PRESSED },
|
||||
|
||||
// ====================================================================
|
||||
// SECTION 16: Edge cases
|
||||
@ -586,7 +586,8 @@ class KittyKeyboardProtocolTests
|
||||
auto input = createInput(tc.flags);
|
||||
const auto expected = TerminalInput::MakeOutput(tc.expected);
|
||||
const auto actual = process(input, tc.keyDown, tc.vk, tc.sc, tc.ch, tc.state);
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
const auto msg = fmt::format(L"{} != {}", til::visualize_control_codes(expected.value_or({})), til::visualize_control_codes(actual.value_or({})));
|
||||
VERIFY_ARE_EQUAL(expected, actual, msg.c_str());
|
||||
}
|
||||
|
||||
// Repeat events require stateful testing - the same key must be pressed twice
|
||||
@ -601,7 +602,7 @@ class KittyKeyboardProtocolTests
|
||||
|
||||
// First press -> event type 1 (press)
|
||||
auto result1 = process(input, true, 'A', 0x1E, L'a', 0);
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97;1:1u");
|
||||
auto expected1 = TerminalInput::MakeOutput(L"\x1b[97u");
|
||||
VERIFY_ARE_EQUAL(expected1, result1, L"First press should be event type 1");
|
||||
|
||||
// Second press (same key, no release) -> event type 2 (repeat)
|
||||
|
||||
@ -353,52 +353,32 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
WI_SetFlagIf(simpleKeyState, SKS_SHIFT, shiftIsPressed);
|
||||
WI_SetFlagIf(simpleKeyState, SKS_ENHANCED, enhanced);
|
||||
|
||||
const auto functionalKeyCode = _getKittyFunctionalKeyCode(keyEvent, simpleKeyState);
|
||||
KeyEncodingInfo info;
|
||||
const auto functionalKeyCode = _getKittyFunctionalKeyCode(keyEvent.wVirtualKeyCode, keyEvent.wVirtualScanCode, simpleKeyState);
|
||||
EncodingHelper enc;
|
||||
|
||||
// Get the codepoint that would be generated without modifiers.
|
||||
// However, we first need to query the key with the original state, to check
|
||||
// whether it's a dead key. If that is the case, ToUnicodeEx should return a
|
||||
// negative number, although in practice it's more likely to return a string
|
||||
// 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 transmitted twice.
|
||||
//
|
||||
// _getKittyFunctionalKeyCode() only returns non-zero for non-text keys.
|
||||
// So, using !functionalKeyCode we filter down to (possible) text keys.
|
||||
//
|
||||
// We can also further filter down to only key-combinations with modifier,
|
||||
// since no modifiers means that we can just use the codepoint as is.
|
||||
auto codepointWithoutModifiers = codepoint;
|
||||
if (!functionalKeyCode && (simpleKeyState & (SKS_CTRL | SKS_ALT)) != 0)
|
||||
{
|
||||
// We need the current keyboard layout and state to look up the character
|
||||
// that would be transmitted in that state (via the ToUnicodeEx API).
|
||||
const auto hkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
auto keyState = _getKeyboardState(virtualKeyCode, controlKeyState);
|
||||
constexpr UINT flags = 4; // Don't modify the state in the ToUnicodeEx call.
|
||||
constexpr int bufferSize = 4;
|
||||
wchar_t buffer[bufferSize];
|
||||
|
||||
// However, we first need to query the key with the original state, to check
|
||||
// whether it's a dead key. If that is the case, ToUnicodeEx should return a
|
||||
// negative number, although in practice it's more likely to return a string
|
||||
// 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 transmitted twice.
|
||||
auto length = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer, bufferSize, flags, hkl);
|
||||
if (length < 0 || (length == 2 && buffer[0] == buffer[1]))
|
||||
const auto hkl = enc.getKeyboardLayoutCached();
|
||||
const auto cb = enc.getKeyboardKey(virtualKeyCode, controlKeyState, hkl);
|
||||
if (cb.len < 0 || (cb.len == 2 && cb.buf[0] == cb.buf[1]))
|
||||
{
|
||||
return _makeNoOutput();
|
||||
}
|
||||
|
||||
// Once we know it's not a dead key, we run the query again, but with the
|
||||
// Ctrl and Alt modifiers disabled to obtain the base character mapping.
|
||||
keyState.at(VK_CONTROL) = keyState.at(VK_LCONTROL) = keyState.at(VK_RCONTROL) = 0;
|
||||
keyState.at(VK_MENU) = keyState.at(VK_LMENU) = keyState.at(VK_RMENU) = 0;
|
||||
length = ToUnicodeEx(virtualKeyCode, 0, keyState.data(), buffer, bufferSize, flags, hkl);
|
||||
if (length == 1 || length == 2)
|
||||
{
|
||||
codepointWithoutModifiers = _bufferToCodepoint(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t kittyKeyCode = 0;
|
||||
uint32_t kittyEventType = 1;
|
||||
uint32_t kittyEventType = 0;
|
||||
uint32_t kittyAltKeyCodeShifted = 0;
|
||||
uint32_t kittyAltKeyCodeBase = 0;
|
||||
uint32_t kittyTextAsCodepoint = 0;
|
||||
@ -411,22 +391,18 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
// KKP> report the Esc, alt+key, ctrl+key, ctrl+alt+key, shift+alt+key
|
||||
// KKP> keys using CSI u sequences instead of legacy ones.
|
||||
// KKP> Here key is any ASCII key as described in Legacy text keys. [...]
|
||||
// KKP> Additionally, all non text keypad keys will be reported [...] with CSI u encoding, [...].
|
||||
//
|
||||
// NOTE: The specification fails to mention ctrl+shift+key, but Kitty does handle it.
|
||||
// So really, it's actually "any modifiers, except for shift+key".
|
||||
|
||||
// KKP> Legacy text keys:
|
||||
// KKP> For legacy compatibility, the keys a-z 0-9 ` - = [ ] \ ; ' , . / [...]
|
||||
//
|
||||
// NOTE: The list of legacy keys doesn't really make any sense.
|
||||
// My interpretation is that the author meant all printable keys,
|
||||
// because as before, that's also how Kitty handles it.
|
||||
//
|
||||
// NOTE: We'll handle modifier+key combinations below, together with ReportAllKeysAsEscapeCodes.
|
||||
|
||||
// KKP> Additionally, all non text keypad keys will be reported [...] with CSI u encoding, [...].
|
||||
|
||||
if (virtualKeyCode == VK_ESCAPE ||
|
||||
(virtualKeyCode >= VK_NUMPAD0 && virtualKeyCode <= VK_DIVIDE) ||
|
||||
((simpleKeyState & ~SKS_ENHANCED) > SKS_SHIFT && _codepointIsText(codepointWithoutModifiers)))
|
||||
if (virtualKeyCode == VK_ESCAPE || (virtualKeyCode >= VK_NUMPAD0 && virtualKeyCode <= VK_DIVIDE))
|
||||
{
|
||||
kittyKeyCode = functionalKeyCode <= KittyKeyCodeLegacySentinel ? 0 : functionalKeyCode;
|
||||
}
|
||||
@ -445,22 +421,36 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
}
|
||||
}
|
||||
|
||||
// Enabling ReportEventTypes implies that `CSI u` is used for all key up events.
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAllKeysAsEscapeCodes) || kittyEventType == 3)
|
||||
{
|
||||
if (
|
||||
// KKP> This [...] turns on key reporting even for key events that generate text.
|
||||
// KKP> [...] text will not be sent, instead only key events are sent.
|
||||
//
|
||||
// KKP> [...] with this mode, events for pressing modifier keys are reported.
|
||||
//
|
||||
// In other words: Get the functional key code if any, otherwise use the codepoint.
|
||||
|
||||
WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAllKeysAsEscapeCodes) ||
|
||||
// A continuation of DisambiguateEscapeCodes above: modifier + text-key = CSI u.
|
||||
(WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::DisambiguateEscapeCodes) &&
|
||||
functionalKeyCode <= KittyKeyCodeLegacySentinel && // Implies that it's a possibly a text-key
|
||||
(simpleKeyState & ~SKS_ENHANCED) > SKS_SHIFT) ||
|
||||
// Enabling ReportEventTypes implies that `CSI u` is used for all key up events.
|
||||
kittyEventType == 3)
|
||||
{
|
||||
kittyKeyCode = functionalKeyCode <= KittyKeyCodeLegacySentinel ? 0 : functionalKeyCode;
|
||||
|
||||
if (kittyKeyCode == 0 && _codepointIsText(codepointWithoutModifiers))
|
||||
if (kittyKeyCode == 0)
|
||||
{
|
||||
// KKP> Note that the codepoint used is always the lower-case [...] version of the key.
|
||||
kittyKeyCode = _codepointToLower(codepointWithoutModifiers);
|
||||
//
|
||||
// In other words, we want the "base key" of the current layout,
|
||||
// effectively the key without Ctrl/Alt/Shift modifiers.
|
||||
const auto hkl = enc.getKeyboardLayoutCached();
|
||||
const auto cb = enc.getKeyboardKey(virtualKeyCode, controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED | SHIFT_PRESSED | CAPSLOCK_ON), hkl);
|
||||
const auto cp = _bufferToCodepoint(cb.buf);
|
||||
|
||||
if (_codepointIsText(cp))
|
||||
{
|
||||
kittyKeyCode = cp;
|
||||
}
|
||||
}
|
||||
|
||||
if (WI_IsFlagSet(_kittyFlags, KittyKeyboardProtocolFlags::ReportAssociatedText))
|
||||
@ -497,44 +487,76 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
|
||||
// KKP> Note that the shifted key must be present only if shift is also present in the modifiers.
|
||||
|
||||
if ((simpleKeyState & SKS_SHIFT) != 0 && _codepointIsText(codepoint))
|
||||
if ((simpleKeyState & SKS_SHIFT) != 0)
|
||||
{
|
||||
// I'm assuming that codepoint is already the shifted version if shift is pressed.
|
||||
kittyAltKeyCodeShifted = codepoint;
|
||||
// This is almost identical to our computation of the "base key" for
|
||||
// ReportAllKeysAsEscapeCodes above, but this time with SHIFT_PRESSED.
|
||||
const auto hkl = enc.getKeyboardLayoutCached();
|
||||
const auto cb = enc.getKeyboardKey(virtualKeyCode, controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON) | SHIFT_PRESSED, hkl);
|
||||
const auto cp = _bufferToCodepoint(cb.buf);
|
||||
|
||||
// The specification doesn't state whether the shifted key should be reported if it's identical
|
||||
// to the unshifted version, so I don't (this matches the behavior for base layout key below).
|
||||
if (_codepointIsText(cp) && cp != kittyKeyCode)
|
||||
{
|
||||
kittyAltKeyCodeShifted = cp;
|
||||
}
|
||||
}
|
||||
|
||||
kittyAltKeyCodeBase = _getBaseLayoutCodepoint(virtualKeyCode);
|
||||
if (keyEvent.wVirtualScanCode)
|
||||
{
|
||||
// > The base layout key is the key corresponding to the physical key in the standard PC-101 key layout.
|
||||
static const auto usLayout = LoadKeyboardLayoutW(L"00000409", 0);
|
||||
if (usLayout)
|
||||
{
|
||||
const auto vkey = MapVirtualKeyExW(keyEvent.wVirtualScanCode, MAPVK_VSC_TO_VK_EX, usLayout);
|
||||
if (vkey)
|
||||
{
|
||||
kittyAltKeyCodeBase = _getKittyFunctionalKeyCode(vkey, keyEvent.wVirtualScanCode, simpleKeyState);
|
||||
|
||||
if (kittyAltKeyCodeBase <= KittyKeyCodeLegacySentinel)
|
||||
{
|
||||
const auto cb = enc.getKeyboardKey(virtualKeyCode, controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED | SHIFT_PRESSED | CAPSLOCK_ON), usLayout);
|
||||
kittyAltKeyCodeBase = _bufferToCodepoint(cb.buf);
|
||||
if (!_codepointIsText(kittyAltKeyCodeBase))
|
||||
{
|
||||
kittyAltKeyCodeShifted = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (kittyAltKeyCodeBase == kittyKeyCode)
|
||||
{
|
||||
kittyAltKeyCodeBase = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (kittyKeyCode)
|
||||
{
|
||||
info.csiFinal = L'u';
|
||||
info.csiParam[0][0] = kittyKeyCode;
|
||||
info.csiParam[0][1] = kittyAltKeyCodeShifted;
|
||||
info.csiParam[0][2] = kittyAltKeyCodeBase;
|
||||
info.csiParam[1][1] = kittyEventType;
|
||||
info.csiParam[2][0] = kittyTextAsCodepoint;
|
||||
enc.csiFinal = L'u';
|
||||
enc.csiParam[0][0] = kittyKeyCode;
|
||||
enc.csiParam[0][1] = kittyAltKeyCodeShifted;
|
||||
enc.csiParam[0][2] = kittyAltKeyCodeBase;
|
||||
// enc.csiParam[1][0] contains the CSI modifier value, which gets calculated below.
|
||||
enc.csiParam[1][1] = kittyEventType;
|
||||
enc.csiParam[2][0] = kittyTextAsCodepoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
_fillRegularKeyEncodingInfo(info, keyEvent, simpleKeyState);
|
||||
_fillRegularKeyEncodingInfo(enc, keyEvent, simpleKeyState);
|
||||
}
|
||||
|
||||
std::wstring seq;
|
||||
|
||||
if (info.csiFinal)
|
||||
if (enc.csiFinal)
|
||||
{
|
||||
// Kitty:
|
||||
// CSI unicode-key-code:alternate-key-code-shift:alternate-key-code-base ; modifiers:event-type ; text-as-codepoint u
|
||||
// Regular:
|
||||
// CSI final
|
||||
// CSI 1; modifiers final
|
||||
|
||||
{
|
||||
// As per KKP: shift=1, alt=2, ctrl=4, super=8, hyper=16, meta=32, caps_lock=64, num_lock=128
|
||||
info.csiParam[1][0] = simpleKeyState & (SKS_SHIFT | SKS_ALT | SKS_CTRL);
|
||||
enc.csiParam[1][0] = simpleKeyState & (SKS_SHIFT | SKS_ALT | SKS_CTRL);
|
||||
|
||||
// KKP> Lock modifiers are not reported for text producing keys, [...].
|
||||
// KKP> To get lock modifiers for all keys use the Report all keys as escape codes enhancement.
|
||||
@ -543,23 +565,23 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
{
|
||||
if (WI_IsFlagSet(controlKeyState, CAPSLOCK_ON))
|
||||
{
|
||||
info.csiParam[1][0] |= 64;
|
||||
enc.csiParam[1][0] |= 64;
|
||||
}
|
||||
if (WI_IsFlagSet(controlKeyState, NUMLOCK_ON))
|
||||
{
|
||||
info.csiParam[1][0] |= 128;
|
||||
enc.csiParam[1][0] |= 128;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.csiParam[1][0] != 0)
|
||||
if (enc.csiParam[1][0] != 0)
|
||||
{
|
||||
// NOTE: The CSI modifier value is 1 based.
|
||||
info.csiParam[1][0] += 1;
|
||||
enc.csiParam[1][0] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t maxParamCount = std::size(info.csiParam);
|
||||
constexpr size_t maxSubParamCount = std::size(info.csiParam[0]);
|
||||
constexpr size_t maxParamCount = std::size(enc.csiParam);
|
||||
constexpr size_t maxSubParamCount = std::size(enc.csiParam[0]);
|
||||
|
||||
// If any sub-parameter of a parameter is set,
|
||||
// the first sub-parameter must be at least 1.
|
||||
@ -568,25 +590,22 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
uint32_t bits = 0;
|
||||
for (size_t s = 1; s < maxSubParamCount; s++)
|
||||
{
|
||||
bits |= info.csiParam[p][s];
|
||||
bits |= enc.csiParam[p][s];
|
||||
}
|
||||
if (bits != 0)
|
||||
{
|
||||
auto& dst = info.csiParam[p][0];
|
||||
auto& dst = enc.csiParam[p][0];
|
||||
dst = std::max<uint32_t>(dst, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Turn `CSI ; ; a` into `CSI 1 ; 1 ; a` by backfilling preceding params with 1.
|
||||
// This loop also doubles as a way to count how many parameters we need to format.
|
||||
size_t parameterCount = 0;
|
||||
for (size_t p = std::size(info.csiParam); p != 0; p--)
|
||||
size_t paramMaxIdx = 0;
|
||||
for (size_t p = maxParamCount - 1; p != 0; p--)
|
||||
{
|
||||
if (info.csiParam[p][0] != 0)
|
||||
if (enc.csiParam[p][0] != 0)
|
||||
{
|
||||
auto& dst = info.csiParam[p - 1][0];
|
||||
dst = std::max<uint32_t>(dst, 1);
|
||||
parameterCount++;
|
||||
paramMaxIdx = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -594,7 +613,7 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
|
||||
// Format the parameters, skipping any trailing empty parameters entirely.
|
||||
// Empty sub-parameters are omitted, while the colons are kept.
|
||||
for (size_t p = 0; p < parameterCount; p++)
|
||||
for (size_t p = 0; p <= paramMaxIdx; p++)
|
||||
{
|
||||
if (p > 0)
|
||||
{
|
||||
@ -602,43 +621,43 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
}
|
||||
|
||||
// Find the last non-zero sub-parameter in this parameter.
|
||||
size_t subCount = 0;
|
||||
for (size_t s = 0; s < maxSubParamCount; s++)
|
||||
size_t subMaxIdx = 0;
|
||||
for (size_t s = maxSubParamCount - 1; s != 0; s--)
|
||||
{
|
||||
if (info.csiParam[p][s] != 0)
|
||||
if (enc.csiParam[p][s] != 0)
|
||||
{
|
||||
subCount = s;
|
||||
subMaxIdx = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool firstInParam = true;
|
||||
for (size_t s = 0; s < subCount; s++)
|
||||
for (size_t s = 0; s <= subMaxIdx; s++)
|
||||
{
|
||||
if (s > 0)
|
||||
{
|
||||
seq.push_back(L':');
|
||||
}
|
||||
if (const auto v = info.csiParam[p][s])
|
||||
if (const auto v = enc.csiParam[p][s])
|
||||
{
|
||||
fmt::format_to(std::back_inserter(seq), FMT_COMPILE(L"{}"), v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seq.push_back(info.csiFinal);
|
||||
seq.push_back(enc.csiFinal);
|
||||
}
|
||||
else if (info.ss3Final)
|
||||
else if (enc.ss3Final)
|
||||
{
|
||||
seq.append(L"\033O");
|
||||
seq.push_back(info.ss3Final);
|
||||
seq.push_back(enc.ss3Final);
|
||||
}
|
||||
else if (!info.plain.empty())
|
||||
else if (!enc.plain.empty())
|
||||
{
|
||||
if (info.plainAltPrefix && (simpleKeyState & SKS_ALT) && _inputMode.test(Mode::Ansi))
|
||||
if (enc.plainAltPrefix && (simpleKeyState & SKS_ALT) && _inputMode.test(Mode::Ansi))
|
||||
{
|
||||
seq.push_back(L'\x1b');
|
||||
}
|
||||
seq.append(info.plain);
|
||||
seq.append(enc.plain);
|
||||
}
|
||||
// If this is a modifier, it won't produce output.
|
||||
else if (virtualKeyCode < VK_SHIFT || virtualKeyCode > VK_MENU)
|
||||
@ -684,10 +703,14 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
seq.push_back(L'\x1b');
|
||||
}
|
||||
|
||||
const auto hkl = enc.getKeyboardLayoutCached();
|
||||
const auto cb = enc.getKeyboardKey(virtualKeyCode, controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED), hkl);
|
||||
auto cp = _bufferToCodepoint(cb.buf);
|
||||
|
||||
// Once we've got the base character, we can apply the Ctrl modifier.
|
||||
if (ctrlIsReallyPressed)
|
||||
{
|
||||
auto cp = _makeCtrlChar(codepointWithoutModifiers);
|
||||
cp = _makeCtrlChar(cp);
|
||||
// If we haven't found a Ctrl mapping for the key, and it's one of
|
||||
// the alphanumeric keys, we try again using the virtual key code.
|
||||
// On keyboard layouts where the alphanumeric keys are not mapped to
|
||||
@ -696,12 +719,9 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
{
|
||||
cp = _makeCtrlChar(virtualKeyCode);
|
||||
}
|
||||
_stringPushCodepoint(seq, cp);
|
||||
}
|
||||
else
|
||||
{
|
||||
_stringPushCodepoint(seq, codepointWithoutModifiers);
|
||||
}
|
||||
|
||||
_stringPushCodepoint(seq, cp);
|
||||
}
|
||||
}
|
||||
|
||||
@ -761,7 +781,7 @@ DWORD TerminalInput::_trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept
|
||||
// recent key press and associated control key state (which is all we need for
|
||||
// our ToUnicodeEx queries). This is a substitute for the GetKeyboardState API,
|
||||
// which can't be used when serving as a conpty host.
|
||||
std::array<byte, 256> TerminalInput::_getKeyboardState(const WORD virtualKeyCode, const DWORD controlKeyState)
|
||||
std::array<byte, 256> TerminalInput::_getKeyboardState(const size_t virtualKeyCode, const DWORD controlKeyState)
|
||||
{
|
||||
auto keyState = std::array<byte, 256>{};
|
||||
if (virtualKeyCode < keyState.size())
|
||||
@ -812,7 +832,7 @@ uint32_t TerminalInput::_makeCtrlChar(const uint32_t ch) noexcept
|
||||
TerminalInput::StringType TerminalInput::_makeCharOutput(const uint32_t cp)
|
||||
{
|
||||
const auto buf = _codepointToBuffer(cp);
|
||||
return { buf.buf, buf.len };
|
||||
return { buf.buf, gsl::narrow_cast<size_t>(buf.len) };
|
||||
}
|
||||
|
||||
TerminalInput::StringType TerminalInput::_makeNoOutput() noexcept
|
||||
@ -820,16 +840,6 @@ TerminalInput::StringType TerminalInput::_makeNoOutput() noexcept
|
||||
return {};
|
||||
}
|
||||
|
||||
// Sends the given char as a sequence representing Alt+char, also the same as Meta+char.
|
||||
void TerminalInput::_escapeOutput(StringType& charSequence, const bool altIsPressed) const
|
||||
{
|
||||
// Alt+char combinations are only applicable in ANSI mode.
|
||||
if (altIsPressed && _inputMode.test(Mode::Ansi))
|
||||
{
|
||||
charSequence.insert(0, 1, L'\x1b');
|
||||
}
|
||||
}
|
||||
|
||||
// Turns an KEY_EVENT_RECORD into a win32-input-mode VT sequence.
|
||||
// It allows us to send KEY_EVENT_RECORD data losslessly to conhost.
|
||||
TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD& key) const
|
||||
@ -857,7 +867,7 @@ TerminalInput::OutputType TerminalInput::_makeWin32Output(const KEY_EVENT_RECORD
|
||||
return fmt::format(FMT_COMPILE(L"{}{};{};{};{};{};{}_"), _csi, vk, sc, uc, kd, cs, rc);
|
||||
}
|
||||
|
||||
void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept
|
||||
void TerminalInput::_fillRegularKeyEncodingInfo(EncodingHelper& enc, const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept
|
||||
{
|
||||
const auto virtualKeyCode = key.wVirtualKeyCode;
|
||||
const auto modified = (simpleKeyState & ~SKS_ENHANCED) != 0;
|
||||
@ -875,14 +885,14 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
// not standard, but a modern terminal convention). The Alt modifier adds
|
||||
// an ESC prefix (also not standard).
|
||||
case VK_BACK:
|
||||
info.plainAltPrefix = true;
|
||||
enc.plainAltPrefix = true;
|
||||
switch (simpleKeyState & ~(SKS_ALT | SKS_SHIFT))
|
||||
{
|
||||
default:
|
||||
info.plain = _inputMode.test(Mode::BackarrowKey) ? L"\b"sv : L"\x7F"sv;
|
||||
enc.plain = _inputMode.test(Mode::BackarrowKey) ? L"\b"sv : L"\x7F"sv;
|
||||
break;
|
||||
case SKS_CTRL:
|
||||
info.plain = _inputMode.test(Mode::BackarrowKey) ? L"\x7f"sv : L"\b"sv;
|
||||
enc.plain = _inputMode.test(Mode::BackarrowKey) ? L"\x7f"sv : L"\b"sv;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -890,14 +900,14 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
// The Alt modifier adds an ESC prefix, although in practice all the Alt
|
||||
// mappings are likely to be system hotkeys.
|
||||
case VK_TAB:
|
||||
info.plainAltPrefix = true;
|
||||
enc.plainAltPrefix = true;
|
||||
switch (simpleKeyState & ~(SKS_ALT | SKS_CTRL))
|
||||
{
|
||||
default:
|
||||
info.plain = L"\t"sv;
|
||||
enc.plain = L"\t"sv;
|
||||
break;
|
||||
case SKS_SHIFT:
|
||||
info.csiFinal = L'Z';
|
||||
enc.csiFinal = L'Z';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -910,23 +920,23 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
{
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
info.ss3Final = L'M';
|
||||
enc.ss3Final = L'M';
|
||||
}
|
||||
else
|
||||
{
|
||||
info.plain = L"\033?M"sv;
|
||||
enc.plain = L"\033?M"sv;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info.plainAltPrefix = true;
|
||||
enc.plainAltPrefix = true;
|
||||
switch (simpleKeyState & ~(SKS_ALT | SKS_SHIFT | SKS_ENHANCED))
|
||||
{
|
||||
default:
|
||||
info.plain = _inputMode.test(Mode::LineFeed) ? L"\r\n"sv : L"\r"sv;
|
||||
enc.plain = _inputMode.test(Mode::LineFeed) ? L"\r\n"sv : L"\r"sv;
|
||||
break;
|
||||
case SKS_CTRL:
|
||||
info.plain = L"\n"sv;
|
||||
enc.plain = L"\n"sv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -935,7 +945,7 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
// PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z,
|
||||
// regardless of modifiers.
|
||||
case VK_PAUSE:
|
||||
info.plain = L"\x1A"sv;
|
||||
enc.plain = L"\x1A"sv;
|
||||
break;
|
||||
|
||||
// F1 to F4 map to the VT keypad function keys, which are SS3 sequences.
|
||||
@ -952,19 +962,19 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
// KKP> be encoded as both CSI R and CSI ~ [and now it doesn't].
|
||||
if (kitty && virtualKeyCode == VK_F3)
|
||||
{
|
||||
info.csiFinal = L'~';
|
||||
info.csiParam[0][0] = 13;
|
||||
enc.csiFinal = L'~';
|
||||
enc.csiParam[0][0] = 13;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto& dst = kitty || modified ? info.csiFinal : info.ss3Final;
|
||||
auto& dst = kitty || modified ? enc.csiFinal : enc.ss3Final;
|
||||
dst = L'P' + (virtualKeyCode - VK_F1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::wstring_view lut[] = { L"\033P", L"\033Q", L"\033R", L"\033S" };
|
||||
info.plain = lut[virtualKeyCode - VK_F1];
|
||||
enc.plain = lut[virtualKeyCode - VK_F1];
|
||||
}
|
||||
break;
|
||||
|
||||
@ -992,21 +1002,21 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
static constexpr uint8_t lut[] = { 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34 };
|
||||
info.csiFinal = L'~';
|
||||
info.csiParam[0][0] = lut[virtualKeyCode - VK_F5];
|
||||
enc.csiFinal = L'~';
|
||||
enc.csiParam[0][0] = lut[virtualKeyCode - VK_F5];
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (virtualKeyCode)
|
||||
{
|
||||
case VK_F11:
|
||||
info.plain = L"\033"sv;
|
||||
enc.plain = L"\033"sv;
|
||||
break;
|
||||
case VK_F12:
|
||||
info.plain = L"\b"sv;
|
||||
enc.plain = L"\b"sv;
|
||||
break;
|
||||
case VK_F13:
|
||||
info.plain = L"\n"sv;
|
||||
enc.plain = L"\n"sv;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -1027,49 +1037,49 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
{
|
||||
static constexpr uint8_t lut[] = { 'D', 'A', 'C', 'B' };
|
||||
const auto csi = kitty || modified || !_inputMode.test(Mode::CursorKey);
|
||||
auto& dst = csi ? info.csiFinal : info.ss3Final;
|
||||
auto& dst = csi ? enc.csiFinal : enc.ss3Final;
|
||||
dst = lut[virtualKeyCode - VK_LEFT];
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::wstring_view lut[] = { L"\033D", L"\033A", L"\033C", L"\033B" };
|
||||
info.plain = lut[virtualKeyCode - VK_LEFT];
|
||||
enc.plain = lut[virtualKeyCode - VK_LEFT];
|
||||
}
|
||||
break;
|
||||
case VK_CLEAR:
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
const auto csi = kitty || modified || !_inputMode.test(Mode::CursorKey);
|
||||
auto& dst = csi ? info.csiFinal : info.ss3Final;
|
||||
auto& dst = csi ? enc.csiFinal : enc.ss3Final;
|
||||
dst = L'E';
|
||||
}
|
||||
else
|
||||
{
|
||||
info.plain = L"\033E"sv;
|
||||
enc.plain = L"\033E"sv;
|
||||
}
|
||||
break;
|
||||
case VK_HOME:
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
const auto csi = kitty || modified || !_inputMode.test(Mode::CursorKey);
|
||||
auto& dst = csi ? info.csiFinal : info.ss3Final;
|
||||
auto& dst = csi ? enc.csiFinal : enc.ss3Final;
|
||||
dst = L'H';
|
||||
}
|
||||
else
|
||||
{
|
||||
info.plain = L"\033H"sv;
|
||||
enc.plain = L"\033H"sv;
|
||||
}
|
||||
break;
|
||||
case VK_END:
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
const auto csi = kitty || modified || !_inputMode.test(Mode::CursorKey);
|
||||
auto& dst = csi ? info.csiFinal : info.ss3Final;
|
||||
auto& dst = csi ? enc.csiFinal : enc.ss3Final;
|
||||
dst = L'F';
|
||||
}
|
||||
else
|
||||
{
|
||||
info.plain = L"\033F"sv;
|
||||
enc.plain = L"\033F"sv;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -1080,16 +1090,16 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
case VK_DELETE: // 0x2E = 3
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
info.csiFinal = L'~';
|
||||
info.csiParam[0][0] = 2 + (virtualKeyCode - VK_INSERT);
|
||||
enc.csiFinal = L'~';
|
||||
enc.csiParam[0][0] = 2 + (virtualKeyCode - VK_INSERT);
|
||||
}
|
||||
break;
|
||||
case VK_PRIOR: // 0x21 = 5
|
||||
case VK_NEXT: // 0x22 = 6
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
info.csiFinal = L'~';
|
||||
info.csiParam[0][0] = 5 + (virtualKeyCode - VK_PRIOR);
|
||||
enc.csiFinal = L'~';
|
||||
enc.csiParam[0][0] = 5 + (virtualKeyCode - VK_PRIOR);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -1112,12 +1122,12 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
{
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
info.ss3Final = L'p' + (virtualKeyCode - VK_NUMPAD0);
|
||||
enc.ss3Final = L'p' + (virtualKeyCode - VK_NUMPAD0);
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::wstring_view lut[] = { L"\033?p", L"\033?q", L"\033?r", L"\033?s", L"\033?t", L"\033?u", L"\033?v", L"\033?w", L"\033?x", L"\033?y" };
|
||||
info.plain = lut[virtualKeyCode - VK_NUMPAD0];
|
||||
enc.plain = lut[virtualKeyCode - VK_NUMPAD0];
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1131,12 +1141,12 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
{
|
||||
if (_inputMode.test(Mode::Ansi))
|
||||
{
|
||||
info.ss3Final = L'j' + (virtualKeyCode - VK_MULTIPLY);
|
||||
enc.ss3Final = L'j' + (virtualKeyCode - VK_MULTIPLY);
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr std::wstring_view lut[] = { L"\033?j", L"\033?k", L"\033?l", L"\033?m", L"\033?n", L"\033?o" };
|
||||
info.plain = lut[virtualKeyCode - VK_MULTIPLY];
|
||||
enc.plain = lut[virtualKeyCode - VK_MULTIPLY];
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1146,7 +1156,7 @@ void TerminalInput::_fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::_getKittyFunctionalKeyCode(const KEY_EVENT_RECORD& key, DWORD simpleKeyState) noexcept
|
||||
uint32_t TerminalInput::_getKittyFunctionalKeyCode(UINT vkey, WORD scanCode, DWORD simpleKeyState) noexcept
|
||||
{
|
||||
// Most KKP functional keys are rather predictable. This LUT helps cover most of them.
|
||||
// Some keys however depend on the key state (specifically the enhanced bit).
|
||||
@ -1242,9 +1252,8 @@ uint32_t TerminalInput::_getKittyFunctionalKeyCode(const KEY_EVENT_RECORD& key,
|
||||
}();
|
||||
|
||||
const auto enhanced = (simpleKeyState & SKS_ENHANCED) != 0;
|
||||
auto virtualKeyCode = key.wVirtualKeyCode;
|
||||
|
||||
switch (virtualKeyCode)
|
||||
switch (vkey)
|
||||
{
|
||||
// These keys don't fall into the Private Use Area (ugh).
|
||||
case VK_ESCAPE:
|
||||
@ -1278,20 +1287,20 @@ uint32_t TerminalInput::_getKittyFunctionalKeyCode(const KEY_EVENT_RECORD& key,
|
||||
case VK_SHIFT:
|
||||
// I've extracted the scan codes from all keyboard layouts that ship with Windows,
|
||||
// and I've found that all of them use scan code 0x2A for VK_LSHIFT and 0x36 for VK_RSHIFT.
|
||||
virtualKeyCode = key.wVirtualScanCode == 0x36 ? VK_RSHIFT : VK_LSHIFT;
|
||||
vkey = scanCode == 0x36 ? VK_RSHIFT : VK_LSHIFT;
|
||||
break;
|
||||
case VK_CONTROL:
|
||||
virtualKeyCode = enhanced ? VK_RCONTROL : VK_LCONTROL;
|
||||
vkey = enhanced ? VK_RCONTROL : VK_LCONTROL;
|
||||
break;
|
||||
case VK_MENU:
|
||||
virtualKeyCode = enhanced ? VK_RMENU : VK_LMENU;
|
||||
vkey = enhanced ? VK_RMENU : VK_LMENU;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto v = lut[virtualKeyCode & 0xff];
|
||||
const auto v = lut[vkey & 0xff];
|
||||
return v <= KittyKeyCodeLegacySentinel ? v : 0xE000 + v;
|
||||
}
|
||||
|
||||
@ -1363,19 +1372,29 @@ uint32_t TerminalInput::_bufferToLowerCodepoint(wchar_t* buf, int cap) noexcept
|
||||
return _bufferToCodepoint(buf);
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::_getBaseLayoutCodepoint(const WORD vkey) noexcept
|
||||
uint32_t TerminalInput::_getBaseLayoutCodepoint(const WORD scanCode) noexcept
|
||||
{
|
||||
if (!scanCode)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// > The base layout key is the key corresponding to the physical key in the standard PC-101 key layout.
|
||||
static const auto usLayout = LoadKeyboardLayoutW(L"00000409", 0);
|
||||
if (!usLayout)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!usLayout || !vkey)
|
||||
const auto vkey = MapVirtualKeyExW(scanCode, MAPVK_VSC_TO_VK, usLayout);
|
||||
if (!vkey)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
wchar_t baseChar[4];
|
||||
const auto keyState = _getKeyboardState(vkey, 0);
|
||||
const auto result = ToUnicodeEx(vkey, 0, keyState.data(), baseChar, 4, 4, usLayout);
|
||||
const auto result = ToUnicodeEx(vkey, scanCode, keyState.data(), baseChar, 4, 4, usLayout);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
|
||||
@ -92,35 +92,97 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
private:
|
||||
struct CodepointBuffer
|
||||
{
|
||||
wchar_t buf[3];
|
||||
uint16_t len;
|
||||
wchar_t buf[4];
|
||||
int len;
|
||||
};
|
||||
|
||||
struct KeyEncodingInfo
|
||||
struct EncodingHelper
|
||||
{
|
||||
explicit KeyEncodingInfo()
|
||||
explicit EncodingHelper()
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
void disableCtrlAltInKeyboardState() noexcept
|
||||
{
|
||||
keyboardState[VK_CONTROL] = 0;
|
||||
keyboardState[VK_MENU] = 0;
|
||||
keyboardState[VK_LCONTROL] = 0;
|
||||
keyboardState[VK_RCONTROL] = 0;
|
||||
keyboardState[VK_LMENU] = 0;
|
||||
keyboardState[VK_RMENU] = 0;
|
||||
}
|
||||
CodepointBuffer getKeyboardKey(UINT vkey, DWORD controlKeyState, HKL hkl) noexcept
|
||||
{
|
||||
CodepointBuffer cb;
|
||||
|
||||
setupKeyboardState(controlKeyState);
|
||||
|
||||
keyboardState[vkey] = 0x80;
|
||||
cb.len = ToUnicodeEx(vkey, 0, keyboardState, cb.buf, ARRAYSIZE(cb.buf), 4, hkl);
|
||||
keyboardState[vkey] = 0;
|
||||
|
||||
return cb;
|
||||
}
|
||||
HKL getKeyboardLayoutCached() noexcept
|
||||
{
|
||||
if (!keyboardLayoutCached)
|
||||
{
|
||||
keyboardLayout = getKeyboardLayout();
|
||||
keyboardLayoutCached = true;
|
||||
}
|
||||
return keyboardLayout;
|
||||
}
|
||||
static HKL getKeyboardLayout() noexcept
|
||||
{
|
||||
// We need the current keyboard layout and state to look up the character
|
||||
// that would be transmitted in that state (via the ToUnicodeEx API).
|
||||
return GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
}
|
||||
void setupKeyboardState(DWORD controlKeyState) noexcept
|
||||
{
|
||||
const uint8_t rightAlt = WI_IsFlagSet(controlKeyState, RIGHT_ALT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftAlt = WI_IsFlagSet(controlKeyState, LEFT_ALT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t rightCtrl = WI_IsFlagSet(controlKeyState, RIGHT_CTRL_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftCtrl = WI_IsFlagSet(controlKeyState, LEFT_CTRL_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftShift = WI_IsFlagSet(controlKeyState, SHIFT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t capsLock = WI_IsFlagSet(controlKeyState, CAPSLOCK_ON) ? 0x01 : 0;
|
||||
|
||||
keyboardState[VK_SHIFT] = leftShift;
|
||||
keyboardState[VK_CONTROL] = leftCtrl | rightCtrl;
|
||||
keyboardState[VK_MENU] = leftAlt | rightAlt;
|
||||
keyboardState[VK_CAPITAL] = capsLock;
|
||||
keyboardState[VK_LSHIFT] = leftShift;
|
||||
keyboardState[VK_LCONTROL] = leftCtrl;
|
||||
keyboardState[VK_RCONTROL] = rightCtrl;
|
||||
keyboardState[VK_LMENU] = leftAlt;
|
||||
keyboardState[VK_RMENU] = rightAlt;
|
||||
}
|
||||
|
||||
HKL keyboardLayout;
|
||||
uint8_t keyboardState[256];
|
||||
uint32_t codepointWithoutCtrlAlt;
|
||||
|
||||
bool keyboardLayoutCached;
|
||||
|
||||
// A non-zero csiFinal value indicates that this key
|
||||
// should be encoded as `CSI $csiParam1 ; $csiFinal`.
|
||||
wchar_t csiFinal = 0;
|
||||
wchar_t csiFinal;
|
||||
// The longest sequence we currently have is Kitty's with 6 parameters:
|
||||
// CSI unicode-key-code:alternate-key-code-shift:alternate-key-code-base ; modifiers:event-type ; text-as-codepoint u
|
||||
// That's 6 parameters, but we can greatly simplify our logic if we just make it 3x3.
|
||||
uint32_t csiParam[3][3] = {};
|
||||
uint32_t csiParam[3][3];
|
||||
|
||||
// A non-zero ss3Final value indicates that this key
|
||||
// should be encoded as `ESC O $ss3Final`.
|
||||
wchar_t ss3Final = 0;
|
||||
wchar_t ss3Final;
|
||||
|
||||
// Any other encoding ends up as a non-zero plain value.
|
||||
// For instance, the Tab key gets translated to a plain "\t".
|
||||
std::wstring_view plain;
|
||||
// If true, and Alt is pressed, an ESC prefix should be added to
|
||||
// the final sequence. This only applies to non-KKP encodings.
|
||||
bool plainAltPrefix = false;
|
||||
bool plainAltPrefix;
|
||||
};
|
||||
|
||||
// storage location for the leading surrogate of a utf-16 surrogate pair
|
||||
@ -150,15 +212,13 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
void _initKeyboardMap() noexcept;
|
||||
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
|
||||
static std::array<byte, 256> _getKeyboardState(WORD virtualKeyCode, DWORD controlKeyState);
|
||||
static std::array<byte, 256> _getKeyboardState(size_t virtualKeyCode, DWORD controlKeyState);
|
||||
[[nodiscard]] static uint32_t _makeCtrlChar(uint32_t ch) noexcept;
|
||||
[[nodiscard]] static StringType _makeCharOutput(uint32_t ch);
|
||||
[[nodiscard]] static StringType _makeNoOutput() noexcept;
|
||||
void _escapeOutput(StringType& charSequence, bool altIsPressed) const;
|
||||
[[nodiscard]] OutputType _makeWin32Output(const KEY_EVENT_RECORD& key) const;
|
||||
void _fillRegularKeyEncodingInfo(KeyEncodingInfo& info, const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
|
||||
static uint32_t _getKittyFunctionalKeyCode(const KEY_EVENT_RECORD& key, DWORD simpleKeyState) noexcept;
|
||||
void _getKittyInfo() noexcept;
|
||||
void _fillRegularKeyEncodingInfo(EncodingHelper& enc, const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
|
||||
static uint32_t _getKittyFunctionalKeyCode(UINT vkey, WORD scanCode, DWORD simpleKeyState) noexcept;
|
||||
std::vector<uint8_t>& _getKittyStack() noexcept;
|
||||
static bool _codepointIsText(uint32_t cp) noexcept;
|
||||
static void _stringPushCodepoint(std::wstring& str, uint32_t cp);
|
||||
@ -166,7 +226,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
static uint32_t _bufferToCodepoint(const wchar_t* str) noexcept;
|
||||
static uint32_t _codepointToLower(uint32_t cp) noexcept;
|
||||
static uint32_t _bufferToLowerCodepoint(wchar_t* buf, int cap) noexcept;
|
||||
static uint32_t _getBaseLayoutCodepoint(WORD vkey) noexcept;
|
||||
static uint32_t _getBaseLayoutCodepoint(WORD scanCode) noexcept;
|
||||
|
||||
#pragma region MouseInputState Management
|
||||
// These methods are defined in mouseInputState.cpp
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user