This commit is contained in:
Leonard Hecker
2026-02-03 20:04:56 +01:00
parent ad501c9d92
commit d875ff4d6c
4 changed files with 464 additions and 577 deletions

View File

@@ -1,208 +0,0 @@
void TerminalInput::_initKeyboardMap() noexcept
try
{
auto defineKeyWithUnusedModifiers = [this](const int keyCode, const std::wstring& sequence) {
for (auto m = 0; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = sequence;
};
auto defineKeyWithAltModifier = [this](const int keyCode, const std::wstring& sequence) {
_keyMap[keyCode] = sequence;
_keyMap[Alt + keyCode] = L"\x1B" + sequence;
};
auto defineKeypadKey = [this](const int keyCode, const wchar_t* prefix, const wchar_t finalChar) {
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), prefix, finalChar);
for (auto m = 1; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}1;{}{}"), _csi, m + 1, finalChar);
};
auto defineEditingKey = [this](const int keyCode, const int parm) {
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}~"), _csi, parm);
for (auto m = 1; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{};{}~"), _csi, parm, m + 1);
};
auto defineNumericKey = [this](const int keyCode, const wchar_t finalChar) {
_keyMap[keyCode] = fmt::format(FMT_COMPILE(L"{}{}"), _ss3, finalChar);
for (auto m = 1; m < 8; m++)
_keyMap[VTModifier(m) + keyCode] = fmt::format(FMT_COMPILE(L"{}{}{}"), _ss3, m + 1, finalChar);
};
_keyMap.clear();
// The CSI and SS3 introducers are C1 control codes, which can either be
// sent as a single codepoint, or as a two character escape sequence.
if (_inputMode.test(Mode::SendC1))
{
_csi = L"\x9B";
_ss3 = L"\x8F";
}
else
{
_csi = L"\x1B[";
_ss3 = L"\x1BO";
}
// PAUSE doesn't have a VT mapping, but traditionally we've mapped it to ^Z,
// regardless of modifiers.
defineKeyWithUnusedModifiers(VK_PAUSE, L"\x1A"s);
// BACKSPACE maps to either DEL or BS, depending on the Backarrow Key mode.
// The Ctrl modifier inverts the active mode, swapping BS and DEL (this is
// not standard, but a modern terminal convention). The Alt modifier adds
// an ESC prefix (also not standard).
const auto backSequence = _inputMode.test(Mode::BackarrowKey) ? L"\b"s : L"\x7F"s;
const auto ctrlBackSequence = _inputMode.test(Mode::BackarrowKey) ? L"\x7F"s : L"\b"s;
defineKeyWithAltModifier(VK_BACK, backSequence);
defineKeyWithAltModifier(Ctrl + VK_BACK, ctrlBackSequence);
defineKeyWithAltModifier(Shift + VK_BACK, backSequence);
defineKeyWithAltModifier(Ctrl + Shift + VK_BACK, ctrlBackSequence);
// TAB maps to HT, and Shift+TAB to CBT. The Ctrl modifier has no effect.
// The Alt modifier adds an ESC prefix, although in practice all the Alt
// mappings are likely to be system hotkeys.
const auto shiftTabSequence = fmt::format(FMT_COMPILE(L"{}Z"), _csi);
defineKeyWithAltModifier(VK_TAB, L"\t"s);
defineKeyWithAltModifier(Ctrl + VK_TAB, L"\t"s);
defineKeyWithAltModifier(Shift + VK_TAB, shiftTabSequence);
defineKeyWithAltModifier(Ctrl + Shift + VK_TAB, shiftTabSequence);
// RETURN maps to either CR or CR LF, depending on the Line Feed mode. With
// a Ctrl modifier it maps to LF, because that's the expected behavior for
// most PC keyboard layouts. The Alt modifier adds an ESC prefix.
const auto returnSequence = _inputMode.test(Mode::LineFeed) ? L"\r\n"s : L"\r"s;
defineKeyWithAltModifier(VK_RETURN, returnSequence);
defineKeyWithAltModifier(Shift + VK_RETURN, returnSequence);
defineKeyWithAltModifier(Ctrl + VK_RETURN, L"\n"s);
defineKeyWithAltModifier(Ctrl + Shift + VK_RETURN, L"\n"s);
// The keypad RETURN key works the same way, except when Keypad mode is
// enabled, but that's handled below with the other keypad keys.
defineKeyWithAltModifier(Enhanced + VK_RETURN, returnSequence);
defineKeyWithAltModifier(Shift + Enhanced + VK_RETURN, returnSequence);
defineKeyWithAltModifier(Ctrl + Enhanced + VK_RETURN, L"\n"s);
defineKeyWithAltModifier(Ctrl + Shift + Enhanced + VK_RETURN, L"\n"s);
if (_inputMode.test(Mode::Ansi))
{
// F1 to F4 map to the VT keypad function keys, which are SS3 sequences.
// When combined with a modifier, we use CSI sequences with the modifier
// embedded as a parameter (not standard - a modern terminal extension).
defineKeypadKey(VK_F1, _ss3, L'P');
defineKeypadKey(VK_F2, _ss3, L'Q');
defineKeypadKey(VK_F3, _ss3, L'R');
defineKeypadKey(VK_F4, _ss3, L'S');
// F5 through F20 map to the top row VT function keys. They use standard
// DECFNK sequences with the modifier embedded as a parameter. The first
// five function keys on a VT terminal are typically local functions, so
// there's not much need to support mappings for them.
for (auto vk = VK_F5; vk <= VK_F20; vk++)
{
static constexpr std::array<uint8_t, 16> parameters = { 15, 17, 18, 19, 20, 21, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34 };
const auto parm = parameters.at(static_cast<size_t>(vk) - VK_F5);
defineEditingKey(vk, parm);
}
// Cursor keys follow a similar pattern to the VT keypad function keys,
// although they only use an SS3 prefix when the Cursor Key mode is set.
// When combined with a modifier, they'll use CSI sequences with the
// modifier embedded as a parameter (again not standard).
const auto ckIntroducer = _inputMode.test(Mode::CursorKey) ? _ss3 : _csi;
defineKeypadKey(VK_UP, ckIntroducer, L'A');
defineKeypadKey(VK_DOWN, ckIntroducer, L'B');
defineKeypadKey(VK_RIGHT, ckIntroducer, L'C');
defineKeypadKey(VK_LEFT, ckIntroducer, L'D');
defineKeypadKey(VK_CLEAR, ckIntroducer, L'E');
defineKeypadKey(VK_HOME, ckIntroducer, L'H');
defineKeypadKey(VK_END, ckIntroducer, L'F');
// Editing keys follow the same pattern as the top row VT function
// keys, using standard DECFNK sequences with the modifier embedded.
defineEditingKey(VK_INSERT, 2);
defineEditingKey(VK_DELETE, 3);
defineEditingKey(VK_PRIOR, 5);
defineEditingKey(VK_NEXT, 6);
// Keypad keys depend on the Keypad mode. When reset, they transmit
// the ASCII character assigned by the keyboard layout, but when set
// they transmit SS3 escape sequences. When used with a modifier, the
// modifier is embedded as a parameter value (not standard).
if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad))
{
defineNumericKey(VK_MULTIPLY, L'j');
defineNumericKey(VK_ADD, L'k');
defineNumericKey(VK_SEPARATOR, L'l');
defineNumericKey(VK_SUBTRACT, L'm');
defineNumericKey(VK_DECIMAL, L'n');
defineNumericKey(VK_DIVIDE, L'o');
defineNumericKey(VK_NUMPAD0, L'p');
defineNumericKey(VK_NUMPAD1, L'q');
defineNumericKey(VK_NUMPAD2, L'r');
defineNumericKey(VK_NUMPAD3, L's');
defineNumericKey(VK_NUMPAD4, L't');
defineNumericKey(VK_NUMPAD5, L'u');
defineNumericKey(VK_NUMPAD6, L'v');
defineNumericKey(VK_NUMPAD7, L'w');
defineNumericKey(VK_NUMPAD8, L'x');
defineNumericKey(VK_NUMPAD9, L'y');
defineNumericKey(Enhanced + VK_RETURN, L'M');
}
}
else
{
// In VT52 mode, the sequences tend to use the same final character as
// their ANSI counterparts, but with a simple ESC prefix. The modifier
// keys have no effect.
// VT52 only support PF1 through PF4 function keys.
defineKeyWithUnusedModifiers(VK_F1, L"\033P"s);
defineKeyWithUnusedModifiers(VK_F2, L"\033Q"s);
defineKeyWithUnusedModifiers(VK_F3, L"\033R"s);
defineKeyWithUnusedModifiers(VK_F4, L"\033S"s);
// But terminals with application functions keys would
// map some of them as controls keys in VT52 mode.
defineKeyWithUnusedModifiers(VK_F11, L"\033"s);
defineKeyWithUnusedModifiers(VK_F12, L"\b"s);
defineKeyWithUnusedModifiers(VK_F13, L"\n"s);
// Cursor keys use the same finals as the ANSI sequences.
defineKeyWithUnusedModifiers(VK_UP, L"\033A"s);
defineKeyWithUnusedModifiers(VK_DOWN, L"\033B"s);
defineKeyWithUnusedModifiers(VK_RIGHT, L"\033C"s);
defineKeyWithUnusedModifiers(VK_LEFT, L"\033D"s);
defineKeyWithUnusedModifiers(VK_CLEAR, L"\033E"s);
defineKeyWithUnusedModifiers(VK_HOME, L"\033H"s);
defineKeyWithUnusedModifiers(VK_END, L"\033F"s);
// Keypad keys also depend on Keypad mode, the same as ANSI mappings,
// but the sequences use an ESC ? prefix instead of SS3.
if (Feature_KeypadModeEnabled::IsEnabled() && _inputMode.test(Mode::Keypad))
{
defineKeyWithUnusedModifiers(VK_MULTIPLY, L"\033?j"s);
defineKeyWithUnusedModifiers(VK_ADD, L"\033?k"s);
defineKeyWithUnusedModifiers(VK_SEPARATOR, L"\033?l"s);
defineKeyWithUnusedModifiers(VK_SUBTRACT, L"\033?m"s);
defineKeyWithUnusedModifiers(VK_DECIMAL, L"\033?n"s);
defineKeyWithUnusedModifiers(VK_DIVIDE, L"\033?o"s);
defineKeyWithUnusedModifiers(VK_NUMPAD0, L"\033?p"s);
defineKeyWithUnusedModifiers(VK_NUMPAD1, L"\033?q"s);
defineKeyWithUnusedModifiers(VK_NUMPAD2, L"\033?r"s);
defineKeyWithUnusedModifiers(VK_NUMPAD3, L"\033?s"s);
defineKeyWithUnusedModifiers(VK_NUMPAD4, L"\033?t"s);
defineKeyWithUnusedModifiers(VK_NUMPAD5, L"\033?u"s);
defineKeyWithUnusedModifiers(VK_NUMPAD6, L"\033?v"s);
defineKeyWithUnusedModifiers(VK_NUMPAD7, L"\033?w"s);
defineKeyWithUnusedModifiers(VK_NUMPAD8, L"\033?x"s);
defineKeyWithUnusedModifiers(VK_NUMPAD9, L"\033?y"s);
defineKeyWithUnusedModifiers(Enhanced + VK_RETURN, L"\033?M"s);
}
}
_focusInSequence = _csi + L"I"s;
_focusOutSequence = _csi + L"O"s;
}
CATCH_LOG()

View File

@@ -117,6 +117,14 @@ using namespace Microsoft::Console::Interactivity;
CATCH_RETURN();
}
// TODO: Avoid translating win32im sequences to Kitty Keyboard Protocol temporarily.
// This is because as of this writing, our implementation is brand new, and Windows Terminal
// needs a toggle to disable it. That only works if ConPTY then doesn't do it anyway.
if (const auto inputBuffer = ServiceLocator::LocateGlobals().getConsoleInformation().pInputBuffer)
{
inputBuffer->GetTerminalInput().ForceDisableKittyKeyboardProtocol(true);
}
// The only way we're initialized is if the args said we're in conpty mode.
// If the args say so, then at least one of in, out, or signal was specified
_state = State::Initialized;

File diff suppressed because it is too large Load Diff

View File

@@ -98,15 +98,18 @@ namespace Microsoft::Console::VirtualTerminal
struct KeyEncodingInfo
{
// If not zero, this value represents the first field in the Kitty
// Keyboard Protocol (KKP) CSI u sequence. If the KKP is requested,
// this field will be preferred over the following fields.
int32_t kittyKeyCode = 0;
explicit KeyEncodingInfo()
{
memset(this, 0, sizeof(*this));
}
// A non-zero csiFinal value indicates that this key
// should be encoded as `CSI $csiParam1 ; $csiFinal`.
wchar_t csiFinal = 0;
int32_t csiParam1 = 0;
// 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] = {};
// A non-zero ss3Final value indicates that this key
// should be encoded as `ESC O $ss3Final`.
@@ -115,10 +118,9 @@ namespace Microsoft::Console::VirtualTerminal
// 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 altPrefix = false;
bool plainAltPrefix = false;
};
// storage location for the leading surrogate of a utf-16 surrogate pair
@@ -149,14 +151,17 @@ 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);
[[nodiscard]] static wchar_t _makeCtrlChar(wchar_t ch);
[[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;
[[nodiscard]] KeyEncodingInfo _getKeyEncodingInfo(const KEY_EVENT_RECORD& key, DWORD simpleKeyState) const noexcept;
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;
std::vector<uint8_t>& _getKittyStack() noexcept;
static bool _codepointIsText(uint32_t cp) noexcept;
static void _stringPushCodepoint(std::wstring& str, uint32_t cp);
static CodepointBuffer _codepointToBuffer(uint32_t cp) noexcept;
static uint32_t _bufferToCodepoint(const wchar_t* str) noexcept;
static uint32_t _codepointToLower(uint32_t cp) noexcept;