mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-18 01:00:00 +00:00
Compare commits
10 Commits
dev/cazamo
...
dev/lhecke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9354b5c7f6 | ||
|
|
63b6bba433 | ||
|
|
6e3bb0ead4 | ||
|
|
51416799f3 | ||
|
|
552a79f269 | ||
|
|
0e4d6b8d0d | ||
|
|
fcda6e9df1 | ||
|
|
f0e34328fd | ||
|
|
e27f76da3f | ||
|
|
446f0dffcf |
5
.github/actions/spelling/expect/expect.txt
vendored
5
.github/actions/spelling/expect/expect.txt
vendored
@@ -29,6 +29,7 @@ allocing
|
||||
alpc
|
||||
ALTERNATENAME
|
||||
ALTF
|
||||
ALTGR
|
||||
ALTNUMPAD
|
||||
ALWAYSTIP
|
||||
ansicpg
|
||||
@@ -76,6 +77,7 @@ autoscrolling
|
||||
Autowrap
|
||||
AVerify
|
||||
awch
|
||||
AZERTY
|
||||
azurecr
|
||||
backgrounded
|
||||
Backgrounder
|
||||
@@ -840,6 +842,8 @@ kinda
|
||||
KIYEOK
|
||||
KKP
|
||||
KLF
|
||||
klid
|
||||
KLLF
|
||||
KLMNO
|
||||
KOK
|
||||
KPRIORITY
|
||||
@@ -1102,6 +1106,7 @@ NOSELECTION
|
||||
NOSENDCHANGING
|
||||
NOSIZE
|
||||
NOSNAPSHOT
|
||||
NOTELLSHELL
|
||||
NOTHOUSANDS
|
||||
NOTICKS
|
||||
NOTIMEOUTIFNOTHUNG
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
<ClCompile Include="inputTest.cpp" />
|
||||
<ClCompile Include="kittyKeyboardProtocol.cpp" />
|
||||
<ClCompile Include="MouseInputTest.cpp" />
|
||||
<ClCompile Include="TestHook.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
<ClInclude Include="TestHook.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\buffer\out\lib\bufferout.vcxproj">
|
||||
|
||||
@@ -30,11 +30,17 @@
|
||||
<ClCompile Include="kittyKeyboardProtocol.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TestHook.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\precomp.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TestHook.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
|
||||
132
src/terminal/adapter/ut_adapter/TestHook.cpp
Normal file
132
src/terminal/adapter/ut_adapter/TestHook.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#include "precomp.h"
|
||||
#include "TestHook.h"
|
||||
|
||||
using namespace TestHook;
|
||||
|
||||
thread_local HKL g_keyboardLayout;
|
||||
|
||||
extern "C" HKL TestHook_TerminalInput_KeyboardLayout()
|
||||
{
|
||||
return g_keyboardLayout;
|
||||
}
|
||||
|
||||
static bool isPreloadedLayout(const wchar_t* klid) noexcept
|
||||
{
|
||||
wil::unique_hkey preloadKey;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, L"Keyboard Layout\\Preload", 0, KEY_READ, preloadKey.addressof()) != ERROR_SUCCESS)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
wil::unique_hkey substitutesKey;
|
||||
RegOpenKeyExW(HKEY_CURRENT_USER, L"Keyboard Layout\\Substitutes", 0, KEY_READ, substitutesKey.addressof());
|
||||
|
||||
wchar_t idx[16];
|
||||
wchar_t layoutId[KL_NAMELENGTH];
|
||||
|
||||
for (DWORD i = 0;; i++)
|
||||
{
|
||||
DWORD idxLen = ARRAYSIZE(idx);
|
||||
DWORD layoutIdSize = sizeof(layoutId);
|
||||
if (RegEnumValueW(preloadKey.get(), i, idx, &idxLen, nullptr, nullptr, reinterpret_cast<BYTE*>(layoutId), &layoutIdSize) != ERROR_SUCCESS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Preload contains base language IDs (e.g. "0000040c").
|
||||
// The actual layout ID (e.g. "0001040c") may only appear in the Substitutes key.
|
||||
if (substitutesKey)
|
||||
{
|
||||
wchar_t substitute[KL_NAMELENGTH];
|
||||
DWORD substituteSize = sizeof(substitute);
|
||||
if (RegGetValueW(substitutesKey.get(), nullptr, layoutId, RRF_RT_REG_SZ, nullptr, substitute, &substituteSize) == ERROR_SUCCESS)
|
||||
{
|
||||
memcpy(layoutId, substitute, sizeof(layoutId));
|
||||
}
|
||||
}
|
||||
|
||||
if (wcscmp(layoutId, klid) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LayoutGuard::_destroy() const noexcept
|
||||
{
|
||||
if (g_keyboardLayout == _layout)
|
||||
{
|
||||
g_keyboardLayout = nullptr;
|
||||
}
|
||||
if (_needsUnload)
|
||||
{
|
||||
UnloadKeyboardLayout(_layout);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutGuard::LayoutGuard(HKL layout, bool needsUnload) noexcept :
|
||||
_layout{ layout },
|
||||
_needsUnload{ needsUnload }
|
||||
{
|
||||
}
|
||||
|
||||
LayoutGuard::~LayoutGuard()
|
||||
{
|
||||
_destroy();
|
||||
}
|
||||
|
||||
LayoutGuard::LayoutGuard(LayoutGuard&& other) noexcept :
|
||||
_layout{ std::exchange(other._layout, nullptr) },
|
||||
_needsUnload{ std::exchange(other._needsUnload, false) }
|
||||
{
|
||||
}
|
||||
|
||||
LayoutGuard& LayoutGuard::operator=(LayoutGuard&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
_destroy();
|
||||
_layout = std::exchange(other._layout, nullptr);
|
||||
_needsUnload = std::exchange(other._needsUnload, false);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
LayoutGuard::operator bool() const noexcept
|
||||
{
|
||||
return _layout != nullptr;
|
||||
}
|
||||
|
||||
LayoutGuard::operator HKL() const noexcept
|
||||
{
|
||||
return _layout;
|
||||
}
|
||||
|
||||
LayoutGuard TestHook::SetTerminalInputKeyboardLayout(const wchar_t* klid)
|
||||
{
|
||||
THROW_HR_IF_MSG(E_UNEXPECTED, g_keyboardLayout != nullptr, "Nested layout test overrides are not supported");
|
||||
|
||||
// Check if the layout is installed. LoadKeyboardLayoutW silently returns the
|
||||
// current active layout if the requested one is missing.
|
||||
const auto keyPath = fmt::format(FMT_COMPILE(L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\{}"), klid);
|
||||
wil::unique_hkey key;
|
||||
if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyPath.c_str(), 0, KEY_READ, key.addressof()) != ERROR_SUCCESS)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto layout = LoadKeyboardLayoutW(klid, KLF_NOTELLSHELL);
|
||||
THROW_LAST_ERROR_IF_NULL(layout);
|
||||
|
||||
g_keyboardLayout = layout;
|
||||
|
||||
// Unload the layout if it's not one of the user's layouts.
|
||||
// GetKeyboardLayoutList is unreliable for this purpose, as the keyboard layout API mutates global OS state.
|
||||
// If a process crashes or exits early without calling UnloadKeyboardLayout all future processes will get it
|
||||
// returned in their GetKeyboardLayoutList calls. Shell could fix it but alas. So we peek into the registry.
|
||||
const auto needsUnload = !isPreloadedLayout(klid);
|
||||
|
||||
return { layout, needsUnload };
|
||||
}
|
||||
27
src/terminal/adapter/ut_adapter/TestHook.h
Normal file
27
src/terminal/adapter/ut_adapter/TestHook.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
namespace TestHook
|
||||
{
|
||||
struct LayoutGuard
|
||||
{
|
||||
LayoutGuard() = default;
|
||||
LayoutGuard(HKL layout, bool needsUnload) noexcept;
|
||||
~LayoutGuard();
|
||||
|
||||
LayoutGuard(const LayoutGuard&) = delete;
|
||||
LayoutGuard& operator=(const LayoutGuard&) = delete;
|
||||
LayoutGuard(LayoutGuard&& other) noexcept;
|
||||
LayoutGuard& operator=(LayoutGuard&& other) noexcept;
|
||||
|
||||
explicit operator bool() const noexcept;
|
||||
operator HKL() const noexcept;
|
||||
|
||||
private:
|
||||
void _destroy() const noexcept;
|
||||
|
||||
HKL _layout = nullptr;
|
||||
bool _needsUnload = false;
|
||||
};
|
||||
|
||||
LayoutGuard SetTerminalInputKeyboardLayout(const wchar_t* klid);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "TestHook.h"
|
||||
#include "../../../interactivity/inc/VtApiRedirection.hpp"
|
||||
#include "../../input/terminalInput.hpp"
|
||||
#include "../types/inc/IInputEvent.hpp"
|
||||
@@ -308,6 +309,30 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
const auto slashVkey = LOBYTE(OneCoreSafeVkKeyScanW(L'/'));
|
||||
const auto nullVkey = LOBYTE(OneCoreSafeVkKeyScanW(0));
|
||||
|
||||
uint8_t keyboardState[256] = {};
|
||||
wchar_t unicodeBuf[4] = {};
|
||||
const uint8_t rightAlt = WI_IsFlagSet(uiKeystate, RIGHT_ALT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftAlt = WI_IsFlagSet(uiKeystate, LEFT_ALT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t rightCtrl = WI_IsFlagSet(uiKeystate, RIGHT_CTRL_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t leftCtrl = WI_IsFlagSet(uiKeystate, LEFT_CTRL_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t shift = WI_IsFlagSet(uiKeystate, SHIFT_PRESSED) ? 0x80 : 0;
|
||||
const uint8_t capsLock = WI_IsFlagSet(uiKeystate, CAPSLOCK_ON) ? 0x01 : 0;
|
||||
keyboardState[VK_SHIFT] = shift;
|
||||
keyboardState[VK_CONTROL] = leftCtrl | rightCtrl;
|
||||
keyboardState[VK_MENU] = leftAlt | rightAlt;
|
||||
keyboardState[VK_CAPITAL] = capsLock;
|
||||
keyboardState[VK_LSHIFT] = shift;
|
||||
keyboardState[VK_LCONTROL] = leftCtrl;
|
||||
keyboardState[VK_RCONTROL] = rightCtrl;
|
||||
keyboardState[VK_LMENU] = leftAlt;
|
||||
keyboardState[VK_RMENU] = rightAlt;
|
||||
|
||||
const auto anyCtrlPressed = WI_IsAnyFlagSet(uiKeystate, CTRL_PRESSED);
|
||||
const auto bothCtrlPressed = WI_AreAllFlagsSet(uiKeystate, CTRL_PRESSED);
|
||||
const auto anyAltPressed = WI_IsAnyFlagSet(uiKeystate, ALT_PRESSED);
|
||||
const auto bothAltPressed = WI_AreAllFlagsSet(uiKeystate, ALT_PRESSED);
|
||||
const auto shiftPressed = WI_IsFlagSet(uiKeystate, SHIFT_PRESSED);
|
||||
|
||||
Log::Comment(L"Sending every possible VKEY at the input stream for interception during key DOWN.");
|
||||
for (BYTE vkey = 0; vkey < BYTE_MAX; vkey++)
|
||||
{
|
||||
@@ -315,9 +340,17 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
|
||||
auto fExpectedKeyHandled = true;
|
||||
auto fModifySequence = false;
|
||||
wchar_t ch = LOWORD(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR));
|
||||
|
||||
if (ControlPressed(uiKeystate))
|
||||
til::at(keyboardState, vkey) = 0x80; // Momentarily pretend as if the key is set
|
||||
const auto unicodeLen = ToUnicodeEx(vkey, 0, &keyboardState[0], &unicodeBuf[0], ARRAYSIZE(unicodeBuf), 0b101, nullptr);
|
||||
til::at(keyboardState, vkey) = 0;
|
||||
|
||||
wchar_t ch = unicodeLen == 1 ? unicodeBuf[0] : 0;
|
||||
const auto altGrPressed = anyAltPressed && anyCtrlPressed && (ch > 0x20 && ch != 0x7f);
|
||||
const auto ctrlPressed = bothCtrlPressed || (anyCtrlPressed && !altGrPressed);
|
||||
const auto altPressed = bothAltPressed || (anyAltPressed && !altGrPressed);
|
||||
|
||||
if (ctrlPressed)
|
||||
{
|
||||
// For Ctrl-/ see DifferentModifiersTest.
|
||||
if (vkey == VK_DIVIDE || vkey == slashVkey)
|
||||
@@ -472,28 +505,28 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
expected = TerminalInput::MakeOutput({ &ch, 1 });
|
||||
break;
|
||||
case VK_RETURN:
|
||||
if (AltPressed(uiKeystate))
|
||||
if (altPressed)
|
||||
{
|
||||
const auto str = ControlPressed(uiKeystate) ? L"\x1b\n" : L"\x1b\r";
|
||||
const auto str = ctrlPressed ? L"\x1b\n" : L"\x1b\r";
|
||||
expected = TerminalInput::MakeOutput(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto str = ControlPressed(uiKeystate) ? L"\n" : L"\r";
|
||||
const auto str = ctrlPressed ? L"\n" : L"\r";
|
||||
expected = TerminalInput::MakeOutput(str);
|
||||
}
|
||||
break;
|
||||
case VK_TAB:
|
||||
if (AltPressed(uiKeystate))
|
||||
if (altPressed)
|
||||
{
|
||||
// Alt+Tab isn't possible - that's reserved by the system.
|
||||
continue;
|
||||
}
|
||||
else if (ShiftPressed(uiKeystate))
|
||||
else if (shiftPressed)
|
||||
{
|
||||
expected = TerminalInput::MakeOutput(L"\x1b[Z");
|
||||
}
|
||||
else if (ControlPressed(uiKeystate))
|
||||
else
|
||||
{
|
||||
expected = TerminalInput::MakeOutput(L"\t");
|
||||
}
|
||||
@@ -506,13 +539,19 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
case VK_OEM_102:
|
||||
// OEM keys require special case handling when combined with a Ctrl
|
||||
// modifier, but otherwise work the same way as regular keys.
|
||||
if (ControlPressed(uiKeystate))
|
||||
if (ctrlPressed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9'))
|
||||
// Map VK_ESCAPE, etc., to their corresponding character value, if needed.
|
||||
if (ch == 0)
|
||||
{
|
||||
ch = LOWORD(OneCoreSafeMapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR));
|
||||
}
|
||||
|
||||
if (ctrlPressed && (vkey >= '1' && vkey <= '9'))
|
||||
{
|
||||
// The C-# keys get translated into very specific control
|
||||
// characters that don't play nicely with this test. These keys
|
||||
@@ -531,7 +570,7 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
// Alt+Key generates [0x1b, Ctrl+key] into the stream
|
||||
// Pressing the control key causes all bits but the 5 least
|
||||
// significant ones to be zeroed out (when using ASCII).
|
||||
if (AltPressed(uiKeystate) && ControlPressed(uiKeystate) && ch > 0x40 && ch <= 0x5A)
|
||||
if (altPressed && ctrlPressed && ch > L'@' && ch <= L'~')
|
||||
{
|
||||
const wchar_t buffer[2]{ L'\x1b', gsl::narrow_cast<wchar_t>(ch & 0b11111) };
|
||||
expected = TerminalInput::MakeOutput({ &buffer[0], 2 });
|
||||
@@ -540,17 +579,25 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
}
|
||||
|
||||
// Alt+Key generates [0x1b, key] into the stream
|
||||
if (AltPressed(uiKeystate) && ch != 0)
|
||||
if (altPressed && ch != 0)
|
||||
{
|
||||
const wchar_t buffer[2]{ L'\x1b', ch };
|
||||
expected = TerminalInput::MakeOutput({ &buffer[0], 2 });
|
||||
if (ControlPressed(uiKeystate))
|
||||
if (ctrlPressed)
|
||||
{
|
||||
ch = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Ctrl+Key masks the key value.
|
||||
if (ctrlPressed && ch > L'@' && ch <= L'~')
|
||||
{
|
||||
const auto b = gsl::narrow_cast<wchar_t>(ch & 0b11111);
|
||||
expected = TerminalInput::MakeOutput({ &b, 1 });
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch != 0)
|
||||
{
|
||||
expected = TerminalInput::MakeOutput({ &ch, 1 });
|
||||
@@ -563,11 +610,14 @@ void InputTest::TerminalInputModifierKeyTests()
|
||||
|
||||
if (fModifySequence)
|
||||
{
|
||||
auto fShift = !!(uiKeystate & SHIFT_PRESSED);
|
||||
auto fAlt = (uiKeystate & LEFT_ALT_PRESSED) || (uiKeystate & RIGHT_ALT_PRESSED);
|
||||
auto fCtrl = (uiKeystate & LEFT_CTRL_PRESSED) || (uiKeystate & RIGHT_CTRL_PRESSED);
|
||||
const auto mod = shiftPressed + (2 * altPressed) + (4 * ctrlPressed);
|
||||
if (mod == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& str = expected.value();
|
||||
str[str.size() - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0);
|
||||
str[str.size() - 2] = static_cast<wchar_t>(L'1' + mod);
|
||||
}
|
||||
|
||||
TestKey(expected, input, uiKeystate, vkey, ch);
|
||||
@@ -578,13 +628,20 @@ void InputTest::TerminalInputNullKeyTests()
|
||||
{
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
auto layout = TestHook::SetTerminalInputKeyboardLayout(L"00000409"); // US English
|
||||
if (!layout)
|
||||
{
|
||||
Log::Result(TestResults::Result::Skipped);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int uiKeystate = LEFT_CTRL_PRESSED;
|
||||
|
||||
TerminalInput input;
|
||||
|
||||
Log::Comment(L"Sending every possible VKEY at the input stream for interception during key DOWN.");
|
||||
|
||||
BYTE vkey = LOBYTE(OneCoreSafeVkKeyScanW(0));
|
||||
BYTE vkey = LOBYTE(VkKeyScanExW(0, layout));
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
|
||||
INPUT_RECORD irTest = { 0 };
|
||||
@@ -600,7 +657,6 @@ void InputTest::TerminalInputNullKeyTests()
|
||||
vkey = VK_SPACE;
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
|
||||
irTest.Event.KeyEvent.uChar.UnicodeChar = vkey;
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\0"sv), input.HandleKey(irTest), L"Verify key was handled if it should have been.");
|
||||
|
||||
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <consoletaeftemplates.hpp>
|
||||
#include <WexTestClass.h>
|
||||
|
||||
#include "TestHook.h"
|
||||
#include "../../input/terminalInput.hpp"
|
||||
|
||||
using namespace WEX::TestExecution;
|
||||
@@ -65,24 +66,24 @@ namespace
|
||||
constexpr TestCase testCases[] = {
|
||||
// Core behavior: DisambiguateEscapeCodes (D)
|
||||
{ L"D Esc", L"\x1b[27u", D, true, VK_ESCAPE, 1, 0, 0 },
|
||||
{ L"D Ctrl+a", L"\x1b[97;5u", D, true, 'A', 0x1E, L'\x01', Ctrl },
|
||||
{ L"D Ctrl+Alt+a", L"\x1b[97;7u", D, true, 'A', 0x1E, L'\x01', Ctrl | Alt },
|
||||
{ L"D Shift+Alt+a", L"\x1b[97;4u", D, true, 'A', 0x1E, L'A', Shift | Alt },
|
||||
{ L"D Shift+a", L"A", D, true, 'A', 0x1E, L'A', Shift },
|
||||
{ L"D Ctrl+a", L"\x1b[97;5u", D, true, 'A', 0x10, 0, Ctrl },
|
||||
{ L"D Ctrl+Alt+a", L"æ", D, true, 'A', 0x10, L'æ', Ctrl | Alt },
|
||||
{ L"D Shift+Alt+a", L"\x1b[97;4u", D, true, 'A', 0x10, L'A', Shift | Alt },
|
||||
{ L"D Shift+a", L"A", D, true, 'A', 0x10, L'A', Shift },
|
||||
|
||||
// Modifiers with AllKeys (K): all keys use CSI u
|
||||
{ L"K a", L"\x1b[97u", K, true, 'A', 0x1E, L'a', 0 },
|
||||
{ L"K Shift+a", L"\x1b[97;2u", K, true, 'A', 0x1E, L'A', Shift },
|
||||
{ L"K Alt+a", L"\x1b[97;3u", K, true, 'A', 0x1E, L'a', Alt },
|
||||
{ L"K Ctrl+a", L"\x1b[97;5u", K, true, 'A', 0x1E, L'\x01', Ctrl },
|
||||
{ L"K Shift+Alt+a", L"\x1b[97;4u", K, true, 'A', 0x1E, L'A', Shift | Alt },
|
||||
{ L"K Shift+Ctrl+a", L"\x1b[97;6u", K, true, 'A', 0x1E, L'\x01', Shift | Ctrl },
|
||||
{ L"K Alt+Ctrl+a", L"\x1b[97;7u", K, true, 'A', 0x1E, L'\x01', Alt | Ctrl },
|
||||
{ L"K Shift+Alt+Ctrl+a", L"\x1b[97;8u", K, true, 'A', 0x1E, L'\x01', Shift | Alt | Ctrl },
|
||||
{ L"K CapsLock+a", L"\x1b[97;65u", K, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
{ L"K NumLock+a", L"\x1b[97;129u", K, true, 'A', 0x1E, L'a', NUMLOCK_ON },
|
||||
{ L"K CapsLock+NumLock+a", L"\x1b[97;193u", K, true, 'A', 0x1E, L'A', CAPSLOCK_ON | NUMLOCK_ON },
|
||||
{ L"K all mods", L"\x1b[97;200u", K, true, 'A', 0x1E, L'\x01', Shift | Alt | Ctrl | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
{ L"K a", L"\x1b[97u", K, true, 'A', 0x10, L'a', 0 },
|
||||
{ L"K Shift+a", L"\x1b[97;2u", K, true, 'A', 0x10, L'A', Shift },
|
||||
{ L"K Alt+a", L"\x1b[97;3u", K, true, 'A', 0x10, L'a', Alt },
|
||||
{ L"K Ctrl+a", L"\x1b[97;5u", K, true, 'A', 0x10, 0, Ctrl },
|
||||
{ L"K Shift+Alt+a", L"\x1b[97;4u", K, true, 'A', 0x10, L'A', Shift | Alt },
|
||||
{ L"K Shift+Ctrl+a", L"\x1b[97;6u", K, true, 'A', 0x10, 0, Shift | Ctrl },
|
||||
{ L"K Ctrl+Alt+a", L"\x1b[230u", K, true, 'A', 0x10, L'æ', Ctrl | Alt },
|
||||
{ L"K Shift+Ctrl+Alt+a", L"\x1b[230;2u", K, true, 'A', 0x10, L'Æ', Shift | Ctrl | Alt },
|
||||
{ L"K CapsLock+a", L"\x1b[97;65u", K, true, 'A', 0x10, L'A', CAPSLOCK_ON },
|
||||
{ L"K NumLock+a", L"\x1b[97;129u", K, true, 'A', 0x10, L'a', NUMLOCK_ON },
|
||||
{ L"K CapsLock+NumLock+a", L"\x1b[97;193u", K, true, 'A', 0x10, L'A', CAPSLOCK_ON | NUMLOCK_ON },
|
||||
{ L"K all mods", L"\x1b[230;194u", K, true, 'A', 0x10, L'Æ', Shift | Ctrl | Alt | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// Enter/Tab/Backspace: CSI u with K
|
||||
{ L"K Enter", L"\x1b[13u", K, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
@@ -96,8 +97,8 @@ namespace
|
||||
// Event types (D|E, E|K): release sends ;1:3
|
||||
{ L"D|E Esc press", L"\x1b[27u", D | E, true, VK_ESCAPE, 1, 0, 0 },
|
||||
{ L"D|E Esc release", L"\x1b[27;1:3u", D | E, false, VK_ESCAPE, 1, 0, 0 },
|
||||
{ L"E|K a press", L"\x1b[97u", E | K, true, 'A', 0x1E, L'a', 0 },
|
||||
{ L"E|K a release", L"\x1b[97;1:3u", E | K, false, 'A', 0x1E, L'a', 0 },
|
||||
{ L"E|K a press", L"\x1b[97u", E | K, true, 'A', 0x10, L'a', 0 },
|
||||
{ L"E|K a release", L"\x1b[97;1:3u", E | K, false, 'A', 0x10, L'a', 0 },
|
||||
{ L"E|K Enter release", L"\x1b[13;1:3u", E | K, false, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
{ L"E|K Tab release", L"\x1b[9;1:3u", E | K, false, VK_TAB, 0x0F, L'\t', 0 },
|
||||
{ L"E|K Backspace release", L"\x1b[127;1:3u", E | K, false, VK_BACK, 0x0E, L'\b', 0 },
|
||||
@@ -161,21 +162,21 @@ namespace
|
||||
{ L"K Shift+F13", L"\x1b[57376;2u", K, true, VK_F13, 0x64, 0, Shift },
|
||||
|
||||
// Alternate keys (A|K): shifted key and base layout key
|
||||
{ L"A|K Shift+a", L"\x1b[97:65;2u", A | K, true, 'A', 0x1E, L'A', Shift },
|
||||
{ L"A|K Shift+1", L"\x1b[49:33;2u", A | K, true, '1', 0x02, L'!', Shift },
|
||||
{ L"A|K a (no shift)", L"\x1b[97u", A | K, true, 'A', 0x1E, L'a', 0 },
|
||||
{ L"A|K Shift+a", L"\x1b[97:65:113;2u", A | K, true, 'A', 0x10, L'A', Shift },
|
||||
{ L"A|K Shift+1", L"\x1b[224:49:49;2u", A | K, true, '1', 0x02, L'!', Shift },
|
||||
{ L"A|K a (no shift)", L"\x1b[97::113u", A | K, true, 'A', 0x10, L'a', 0 },
|
||||
|
||||
// Associated text (K|T): text codepoint in 3rd param
|
||||
{ L"K|T Shift+a", L"\x1b[97;2;65u", K | T, true, 'A', 0x1E, L'A', Shift },
|
||||
{ L"K|T Shift+1", L"\x1b[49;2;33u", K | T, true, '1', 0x02, L'!', Shift },
|
||||
{ L"K|T Ctrl+a", L"\x1b[97;5u", K | T, true, 'A', 0x1E, L'\x01', Ctrl }, // control char omitted
|
||||
{ L"K|T Shift+a", L"\x1b[97;2;65u", K | T, true, 'A', 0x10, L'A', Shift },
|
||||
{ L"K|T Shift+1", L"\x1b[224;2;33u", K | T, true, '1', 0x02, L'!', Shift },
|
||||
{ L"K|T Ctrl+a", L"\x1b[97;5u", K | T, true, 'A', 0x10, 0, Ctrl }, // control char omitted
|
||||
|
||||
// Edge cases
|
||||
{ L"K Keypad Enter", L"\x1b[57414u", K, true, VK_RETURN, 0x1C, L'\r', ENHANCED_KEY },
|
||||
{ L"K Regular Enter", L"\x1b[13u", K, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
{ L"K Shift+Alt+Ctrl+Esc", L"\x1b[27;8u", K, true, VK_ESCAPE, 1, 0, Shift | Alt | Ctrl },
|
||||
{ L"E|K CapsLock+a", L"\x1b[97;65u", E | K, true, 'A', 0x1E, L'A', CAPSLOCK_ON },
|
||||
{ L"E|K all mods release", L"\x1b[97;200:3u", E | K, false, 'A', 0x1E, L'\x01', Shift | Alt | Ctrl | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
{ L"K Shift+Ctrl+Alt+Esc", L"\x1b[27;8u", K, true, VK_ESCAPE, 1, 0, Shift | Ctrl | Alt },
|
||||
{ L"E|K CapsLock+a", L"\x1b[97;65u", E | K, true, 'A', 0x10, L'A', CAPSLOCK_ON },
|
||||
{ L"E|K all mods release", L"\x1b[230;194:3u", E | K, false, 'A', 0x10, L'Æ', Shift | Ctrl | Alt | CAPSLOCK_ON | NUMLOCK_ON },
|
||||
|
||||
// F1-F4 with kitty flags (CSI instead of SS3, F3 special case)
|
||||
{ L"D F1", L"\x1b[P", D, true, VK_F1, 0x3B, 0, 0 },
|
||||
@@ -210,20 +211,20 @@ namespace
|
||||
{ L"E|K Up release", L"\x1b[1;1:3A", E | K, false, VK_UP, 0x48, 0, ENHANCED_KEY },
|
||||
{ L"E|K Insert release", L"\x1b[2;1:3~", E | K, false, VK_INSERT, 0x52, 0, ENHANCED_KEY },
|
||||
// Alternate keys with modifiers
|
||||
{ L"A|K Shift+Ctrl+a", L"\x1b[97:65;6u", A | K, true, 'A', 0x1E, L'\x01', Shift | Ctrl },
|
||||
{ L"A|K Shift+Ctrl+a", L"\x1b[97:65:113;6u", A | K, true, 'A', 0x10, 0, Shift | Ctrl },
|
||||
// Associated text with plain key
|
||||
{ L"K|T a", L"\x1b[97;;97u", K | T, true, 'A', 0x1E, L'a', 0 },
|
||||
{ L"K|T a", L"\x1b[97;;97u", K | T, true, 'A', 0x10, L'a', 0 },
|
||||
// Text not reported on release
|
||||
{ L"E|K|T a release", L"\x1b[97;1:3u", E | K | T, false, 'A', 0x1E, L'a', 0 },
|
||||
{ L"E|K|T a release", L"\x1b[97;1:3u", E | K | T, false, 'A', 0x10, L'a', 0 },
|
||||
// Escape has no associated text
|
||||
{ L"K|T Esc", L"\x1b[27u", K | T, true, VK_ESCAPE, 1, 0, 0 },
|
||||
// Combined flags: alternate keys with locks
|
||||
{ L"A|K CapsLock+Shift+a", L"\x1b[97:65;66u", A | K, true, 'A', 0x1E, L'a', CAPSLOCK_ON | Shift },
|
||||
{ L"A|K CapsLock+Shift+a", L"\x1b[97:65:113;66u", A | K, true, 'A', 0x10, L'a', CAPSLOCK_ON | Shift },
|
||||
// All flags combined
|
||||
{ L"A|K|T Shift+a", L"\x1b[97:65;2;65u", A | K | T, true, 'A', 0x1E, L'A', Shift },
|
||||
{ L"A|K|T Shift+a", L"\x1b[97:65:113;2;65u", A | K | T, true, 'A', 0x10, L'A', Shift },
|
||||
|
||||
// Release without EventTypes flag: no output
|
||||
{ L"K a release (no EventTypes)", L"", K, false, 'A', 0x1E, L'a', 0 },
|
||||
{ L"K a release (no EventTypes)", L"", K, false, 'A', 0x10, L'a', 0 },
|
||||
|
||||
// Enter/Tab/Backspace release without AllKeys: no output
|
||||
{ L"D|E Enter press", L"\r", D | E, true, VK_RETURN, 0x1C, L'\r', 0 },
|
||||
@@ -238,8 +239,8 @@ namespace
|
||||
{ L"E|K CapsLock release (now on)", L"\x1b[57358;65:3u", E | K, false, VK_CAPITAL, 0x3A, 0, CAPSLOCK_ON },
|
||||
|
||||
// Associated text filtering
|
||||
{ L"K|T Shift+a (text)", L"\x1b[97;2;65u", K | T, true, 'A', 0x1E, L'A', Shift },
|
||||
{ L"K|T Ctrl+a (control char filtered)", L"\x1b[97;5u", K | T, true, 'A', 0x1E, L'\x01', Ctrl },
|
||||
{ L"K|T Shift+a (text)", L"\x1b[97;2;65u", K | T, true, 'A', 0x10, L'A', Shift },
|
||||
{ L"K|T Ctrl+a (control char filtered)", L"\x1b[97;5u", K | T, true, 'A', 0x10, 0, Ctrl },
|
||||
{ L"K|T Esc (no text)", L"\x1b[27u", K | T, true, VK_ESCAPE, 1, 0, 0 },
|
||||
};
|
||||
}
|
||||
@@ -251,8 +252,26 @@ extern "C" HRESULT __declspec(dllexport) __cdecl KittyKeyTestDataSource(IDataSou
|
||||
|
||||
class KittyKeyboardProtocolTests
|
||||
{
|
||||
TestHook::LayoutGuard layout;
|
||||
|
||||
TEST_CLASS(KittyKeyboardProtocolTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
layout = TestHook::SetTerminalInputKeyboardLayout(L"0001040c"); // French (Standard, AZERTY)
|
||||
if (!layout)
|
||||
{
|
||||
Log::Result(TestResults::Result::Skipped);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_CLASS_CLEANUP(ClassCleanup)
|
||||
{
|
||||
layout = {};
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_METHOD(KeyPressTests)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
@@ -278,25 +297,25 @@ class KittyKeyboardProtocolTests
|
||||
TEST_METHOD(KeyRepeatEvents)
|
||||
{
|
||||
auto input = createInput(E | K);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x1E, L'a', 0));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;1:2u"), process(input, true, 'A', 0x1E, L'a', 0)); // repeat
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;1:2u"), process(input, true, 'A', 0x1E, L'a', 0)); // repeat
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;1:3u"), process(input, false, 'A', 0x1E, L'a', 0)); // release
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x1E, L'a', 0)); // new press
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x10, L'a', 0));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;1:2u"), process(input, true, 'A', 0x10, L'a', 0)); // repeat
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;1:2u"), process(input, true, 'A', 0x10, L'a', 0)); // repeat
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;1:3u"), process(input, false, 'A', 0x10, L'a', 0)); // release
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x10, L'a', 0)); // new press
|
||||
}
|
||||
|
||||
TEST_METHOD(KeyRepeatWithModifiers)
|
||||
{
|
||||
auto input = createInput(E | K);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;2u"), process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;2:2u"), process(input, true, 'A', 0x1E, L'A', SHIFT_PRESSED));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;2u"), process(input, true, 'A', 0x10, L'A', SHIFT_PRESSED));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97;2:2u"), process(input, true, 'A', 0x10, L'A', SHIFT_PRESSED));
|
||||
}
|
||||
|
||||
TEST_METHOD(KeyRepeatResetOnDifferentKey)
|
||||
{
|
||||
auto input = createInput(E | K);
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x1E, L'a', 0));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x10, L'a', 0));
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[98u"), process(input, true, 'B', 0x30, L'b', 0)); // different key
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x1E, L'a', 0)); // not repeat
|
||||
VERIFY_ARE_EQUAL(TerminalInput::MakeOutput(L"\x1b[97u"), process(input, true, 'A', 0x10, L'a', 0)); // not repeat
|
||||
}
|
||||
};
|
||||
|
||||
14
src/terminal/input/TestHook.cpp
Normal file
14
src/terminal/input/TestHook.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
// This default no-op implementation lives in its own .obj so that the linker
|
||||
// can skip it when a test DLL supplies its own definition. The classic linking
|
||||
// model only pulls in .obj files from a .lib if they resolve an otherwise
|
||||
// unresolved symbol - and nothing else in the test DLL refers to this file.
|
||||
// See: https://devblogs.microsoft.com/oldnewthing/20250416-00/?p=111077
|
||||
extern "C" HKL TestHook_TerminalInput_KeyboardLayout()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\mouseInput.cpp" />
|
||||
<ClCompile Include="..\terminalInput.cpp" />
|
||||
<ClCompile Include="..\TestHook.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
||||
@@ -521,6 +521,6 @@ TerminalInput::OutputType TerminalInput::_makeAlternateScrollOutput(const unsign
|
||||
_encodeRegular(enc, key);
|
||||
|
||||
std::wstring str;
|
||||
_formatEncodingHelper(enc, str);
|
||||
_formatEncodingHelper(enc, key, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ PRECOMPILED_INCLUDE = ..\precomp.h
|
||||
SOURCES= \
|
||||
..\terminalInput.cpp \
|
||||
..\mouseInput.cpp \
|
||||
..\TestHook.cpp \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
|
||||
@@ -277,21 +277,48 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
}
|
||||
|
||||
// Keep track of key repeats.
|
||||
key.keyRepeat = _lastVirtualKeyCode == key.virtualKey;
|
||||
//
|
||||
// For modifier keys:
|
||||
// * Map the vkey to a dwControlKeyState flag
|
||||
// (_controlKeyStateFromVirtualKey returns 0 for non-modifier keys)
|
||||
// * Checking whether the flag was already set previously
|
||||
// For standard keys:
|
||||
// * Simply check if the last vkey equals the current one
|
||||
//
|
||||
// This split helps with international keyboard layouts that use the KLLF_ALTGR flag.
|
||||
// Those generate interleaved LEFT_CTRL_PRESSED and RIGHT_ALT_PRESSED events,
|
||||
// which a single _lastVirtualKeyCode field will fail to track.
|
||||
if (key.keyDown)
|
||||
{
|
||||
_lastVirtualKeyCode = key.virtualKey;
|
||||
if (const auto flags = _controlKeyStateFromVirtualKey(key.virtualKey, key.controlKeyState))
|
||||
{
|
||||
key.keyRepeat = (_previousControlKeyState & flags) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
key.keyRepeat = _lastVirtualKeyCode == key.virtualKey;
|
||||
_lastVirtualKeyCode = key.virtualKey;
|
||||
}
|
||||
}
|
||||
else if (key.keyRepeat)
|
||||
else
|
||||
{
|
||||
_lastVirtualKeyCode = std::nullopt;
|
||||
}
|
||||
|
||||
// If this is a repeat of the last recorded key press, and Auto Repeat Mode
|
||||
// is disabled, then we should suppress this event.
|
||||
if (key.keyRepeat && !_inputMode.test(Mode::AutoRepeat))
|
||||
if (key.keyRepeat)
|
||||
{
|
||||
return _makeNoOutput();
|
||||
if (
|
||||
// Suppress modifier key events at all times - they aren't reported in any protocol.
|
||||
(key.virtualKey >= VK_SHIFT && key.virtualKey <= VK_MENU) ||
|
||||
(key.virtualKey >= VK_LSHIFT && key.virtualKey <= VK_RMENU) ||
|
||||
(_kittyFlags != 0 ?
|
||||
// If KKP is enabled, we only report repeats if ReportEventTypes is enabled.
|
||||
WI_IsFlagClear(_kittyFlags, KittyKeyboardProtocolFlags::ReportEventTypes) :
|
||||
// Otherwise, it depends on the classic auto-repeat mode setting.
|
||||
!_inputMode.test(Mode::AutoRepeat)))
|
||||
{
|
||||
return _makeNoOutput();
|
||||
}
|
||||
}
|
||||
|
||||
// There's a bunch of early returns we can place on key-up events,
|
||||
@@ -331,19 +358,42 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
// be able to detect when the Ctrl key isn't genuine. We do so by tracking
|
||||
// the time between the Alt and Ctrl key presses, and only consider the Ctrl
|
||||
// key to really be pressed if the difference is more than 50ms.
|
||||
key.leftCtrlIsReallyPressed = WI_IsFlagSet(key.controlKeyState, LEFT_CTRL_PRESSED);
|
||||
auto leftCtrlIsReallyPressed = false;
|
||||
if (WI_AreAllFlagsSet(key.controlKeyState, LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED))
|
||||
{
|
||||
const auto max = std::max(_lastLeftCtrlTime, _lastRightAltTime);
|
||||
const auto min = std::min(_lastLeftCtrlTime, _lastRightAltTime);
|
||||
key.leftCtrlIsReallyPressed = (max - min) > 50;
|
||||
leftCtrlIsReallyPressed = (max - min) > 50;
|
||||
}
|
||||
|
||||
const auto anyCtrlPressed = WI_IsAnyFlagSet(key.controlKeyState, CTRL_PRESSED);
|
||||
const auto bothCtrlPressed = WI_AreAllFlagsSet(key.controlKeyState, CTRL_PRESSED);
|
||||
const auto anyAltPressed = WI_IsAnyFlagSet(key.controlKeyState, ALT_PRESSED);
|
||||
const auto bothAltPressed = WI_AreAllFlagsSet(key.controlKeyState, ALT_PRESSED);
|
||||
// We distinguish AltGr+Key / Ctrl+Alt+Key combinations on international keyboard layouts from
|
||||
// genuine, intentional Ctrl+Alt+Key combinations by checking whether the codepoint is valid.
|
||||
// Windows should not send a valid codepoint for e.g. Ctrl+Alt+Q on a US ANSI layout,
|
||||
// so we treat it as a genuine Ctrl+Alt+Q.
|
||||
//
|
||||
// However, this isn't universally true and more of a heuristic. Ctrl+Alt+Esc
|
||||
// for instance results in codepoint=0x1b! As such we restrict to graphical codepoints.
|
||||
// This should not be considered "Reference Windows Code". It's a personal best guess.
|
||||
key.altGrPressed = anyAltPressed && anyCtrlPressed && (key.codepoint > 0x20 && key.codepoint != 0x7f);
|
||||
// Ctrl is a bit tricky to detect, since international keyboards with KLLF_ALTGR will
|
||||
// send Left-Ctrl + Right-Alt. If both Ctrl keys are pressed it's unambiguous.
|
||||
// Otherwise, if we haven't guessed this to be an AltGr key, then we can safely
|
||||
// assume this to be a Ctrl combination as well. Otherwise, we also have our
|
||||
// timing logic above to guess if the Left-Ctrl key was pressed by a human.
|
||||
key.ctrlPressed = bothCtrlPressed || (anyCtrlPressed && !key.altGrPressed) || leftCtrlIsReallyPressed;
|
||||
// Alt is a bit simpler than Ctrl and follows the same pattern.
|
||||
key.altPressed = bothAltPressed || (anyAltPressed && !key.altGrPressed);
|
||||
key.shiftPressed = WI_IsFlagSet(key.controlKeyState, SHIFT_PRESSED);
|
||||
|
||||
KeyboardHelper kbd;
|
||||
EncodingHelper enc;
|
||||
WI_SetFlagIf(enc.csiModifier, CSI_CTRL, key.leftCtrlIsReallyPressed || WI_IsFlagSet(key.controlKeyState, RIGHT_CTRL_PRESSED));
|
||||
WI_SetFlagIf(enc.csiModifier, CSI_ALT, WI_IsAnyFlagSet(key.controlKeyState, ALT_PRESSED));
|
||||
WI_SetFlagIf(enc.csiModifier, CSI_SHIFT, WI_IsFlagSet(key.controlKeyState, SHIFT_PRESSED));
|
||||
WI_SetFlagIf(enc.csiModifier, CSI_CTRL, key.ctrlPressed);
|
||||
WI_SetFlagIf(enc.csiModifier, CSI_ALT, key.altPressed);
|
||||
WI_SetFlagIf(enc.csiModifier, CSI_SHIFT, key.shiftPressed);
|
||||
|
||||
if (_kittyFlags == 0 || !_encodeKitty(kbd, enc, key))
|
||||
{
|
||||
@@ -351,9 +401,9 @@ TerminalInput::OutputType TerminalInput::HandleKey(const INPUT_RECORD& event)
|
||||
}
|
||||
|
||||
std::wstring seq;
|
||||
if (!_formatEncodingHelper(enc, seq))
|
||||
if (!_formatEncodingHelper(enc, key, seq))
|
||||
{
|
||||
_formatFallback(kbd, enc, key, seq);
|
||||
_formatFallback(kbd, key, seq);
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
@@ -390,6 +440,8 @@ void TerminalInput::_initKeyboardMap() noexcept
|
||||
|
||||
DWORD TerminalInput::_trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept
|
||||
{
|
||||
_previousControlKeyState = _lastControlKeyState;
|
||||
|
||||
// First record which key state bits were previously off but are now on.
|
||||
const auto pressedKeyState = ~_lastControlKeyState & key.dwControlKeyState;
|
||||
// Then save the new key state so we can determine future state changes.
|
||||
@@ -402,23 +454,37 @@ DWORD TerminalInput::_trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept
|
||||
// can be misinterpreted as an Alt+AltGr key combination.
|
||||
const auto rightAltDown = key.bKeyDown && key.wVirtualKeyCode == VK_MENU && WI_IsFlagSet(key.dwControlKeyState, ENHANCED_KEY);
|
||||
WI_ClearFlagIf(_lastControlKeyState, RIGHT_ALT_PRESSED, WI_IsFlagSet(pressedKeyState, RIGHT_ALT_PRESSED) && !rightAltDown);
|
||||
// We also take this opportunity to record the time at which the LeftCtrl
|
||||
// and RightAlt keys are pressed. This is needed to determine whether the
|
||||
// Ctrl key was pressed by the user, or fabricated by an AltGr key press.
|
||||
if (key.bKeyDown)
|
||||
{
|
||||
if (WI_IsFlagSet(pressedKeyState, LEFT_CTRL_PRESSED))
|
||||
{
|
||||
_lastLeftCtrlTime = GetTickCount64();
|
||||
}
|
||||
if (WI_IsFlagSet(pressedKeyState, RIGHT_ALT_PRESSED))
|
||||
{
|
||||
_lastRightAltTime = GetTickCount64();
|
||||
}
|
||||
}
|
||||
return _lastControlKeyState;
|
||||
}
|
||||
|
||||
// Maps a modifier virtual key code to its corresponding dwControlKeyState flag.
|
||||
// Returns 0 for non-modifier keys. For VK_CONTROL and VK_MENU, the ENHANCED_KEY
|
||||
// bit in controlKeyState disambiguates left vs. right.
|
||||
DWORD TerminalInput::_controlKeyStateFromVirtualKey(uint16_t vk, uint32_t controlKeyState) noexcept
|
||||
{
|
||||
switch (vk)
|
||||
{
|
||||
case VK_SHIFT:
|
||||
case VK_LSHIFT:
|
||||
case VK_RSHIFT:
|
||||
return SHIFT_PRESSED;
|
||||
case VK_CONTROL:
|
||||
return WI_IsFlagSet(controlKeyState, ENHANCED_KEY) ? RIGHT_CTRL_PRESSED : LEFT_CTRL_PRESSED;
|
||||
case VK_LCONTROL:
|
||||
return LEFT_CTRL_PRESSED;
|
||||
case VK_RCONTROL:
|
||||
return RIGHT_CTRL_PRESSED;
|
||||
case VK_MENU:
|
||||
return WI_IsFlagSet(controlKeyState, ENHANCED_KEY) ? RIGHT_ALT_PRESSED : LEFT_ALT_PRESSED;
|
||||
case VK_LMENU:
|
||||
return LEFT_ALT_PRESSED;
|
||||
case VK_RMENU:
|
||||
return RIGHT_ALT_PRESSED;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::_makeCtrlChar(const uint32_t ch) noexcept
|
||||
{
|
||||
if (ch >= L'@' && ch <= L'~')
|
||||
@@ -623,7 +689,7 @@ bool TerminalInput::_encodeKitty(KeyboardHelper& kbd, EncodingHelper& enc, const
|
||||
|
||||
// KKP> Note that the shifted key must be present only if shift is also present in the modifiers.
|
||||
|
||||
if (isTextKey(functionalKeyCode) && enc.shiftPressed())
|
||||
if (isTextKey(functionalKeyCode) && key.shiftPressed)
|
||||
{
|
||||
// This is almost identical to our computation of the "base key" for
|
||||
// ReportAllKeysAsEscapeCodes above, but this time with SHIFT_PRESSED.
|
||||
@@ -839,9 +905,9 @@ void TerminalInput::_encodeRegular(EncodingHelper& enc, const SanitizedKeyEvent&
|
||||
// not standard, but a modern terminal convention). The Alt modifier adds
|
||||
// an ESC prefix (also not standard).
|
||||
enc.altPrefix = true;
|
||||
const auto ctrl = (enc.csiModifier & CSI_CTRL) == 0;
|
||||
const auto ctrl = key.ctrlPressed;
|
||||
const auto back = _inputMode.test(Mode::BackarrowKey);
|
||||
enc.plain = ctrl != back ? L"\x7f"sv : L"\b"sv;
|
||||
enc.plain = ctrl == back ? L"\x7f"sv : L"\b"sv;
|
||||
break;
|
||||
}
|
||||
case VK_TAB:
|
||||
@@ -849,7 +915,7 @@ void TerminalInput::_encodeRegular(EncodingHelper& enc, const SanitizedKeyEvent&
|
||||
// The Alt modifier adds an ESC prefix, although in practice all the Alt
|
||||
// mappings are likely to be system hotkeys.
|
||||
enc.altPrefix = true;
|
||||
if ((enc.csiModifier & CSI_SHIFT) == 0)
|
||||
if (!key.shiftPressed)
|
||||
{
|
||||
enc.plain = L"\t"sv;
|
||||
}
|
||||
@@ -879,7 +945,7 @@ void TerminalInput::_encodeRegular(EncodingHelper& enc, const SanitizedKeyEvent&
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((enc.csiModifier & CSI_CTRL) == 0)
|
||||
if (!key.ctrlPressed)
|
||||
{
|
||||
enc.plain = _inputMode.test(Mode::LineFeed) ? L"\r\n"sv : L"\r"sv;
|
||||
}
|
||||
@@ -1104,12 +1170,12 @@ void TerminalInput::_encodeRegular(EncodingHelper& enc, const SanitizedKeyEvent&
|
||||
}
|
||||
}
|
||||
|
||||
bool TerminalInput::_formatEncodingHelper(EncodingHelper& enc, std::wstring& seq) const
|
||||
bool TerminalInput::_formatEncodingHelper(EncodingHelper& enc, const SanitizedKeyEvent& key, std::wstring& seq) const
|
||||
{
|
||||
// NOTE: altPrefix is only ever true for `_fillRegularKeyEncodingInfo` calls,
|
||||
// and only if one of the 3 conditions below applies.
|
||||
// In other words, we return with an unmodified `str` if `enc` is unmodified.
|
||||
if (enc.altPrefix && enc.altPressed() && _inputMode.test(Mode::Ansi))
|
||||
if (enc.altPrefix && key.altPressed && _inputMode.test(Mode::Ansi))
|
||||
{
|
||||
seq.push_back(L'\x1b');
|
||||
}
|
||||
@@ -1180,7 +1246,7 @@ bool TerminalInput::_formatEncodingHelper(EncodingHelper& enc, std::wstring& seq
|
||||
return false;
|
||||
}
|
||||
|
||||
void TerminalInput::_formatFallback(KeyboardHelper& kbd, const EncodingHelper& enc, const SanitizedKeyEvent& key, std::wstring& seq) const
|
||||
void TerminalInput::_formatFallback(KeyboardHelper& kbd, const SanitizedKeyEvent& key, std::wstring& seq) const
|
||||
{
|
||||
// If this is a modifier, it won't produce output, so we can return early.
|
||||
if (key.virtualKey >= VK_SHIFT && key.virtualKey <= VK_MENU)
|
||||
@@ -1188,41 +1254,24 @@ void TerminalInput::_formatFallback(KeyboardHelper& kbd, const EncodingHelper& e
|
||||
return;
|
||||
}
|
||||
|
||||
const auto anyAltPressed = key.anyAltPressed();
|
||||
auto codepoint = key.codepoint;
|
||||
|
||||
// If it's not in the key map, we'll use the UnicodeChar, if provided,
|
||||
// except in the case of Ctrl+Space, which is often mapped incorrectly as
|
||||
// a space character when it's expected to be mapped to NUL. We need to
|
||||
// let that fall through to the standard mapping algorithm below.
|
||||
const auto ctrlSpaceKey = enc.ctrlPressed() && key.virtualKey == VK_SPACE;
|
||||
const auto ctrlSpaceKey = key.ctrlPressed && key.virtualKey == VK_SPACE;
|
||||
if (codepoint != 0 && !ctrlSpaceKey)
|
||||
{
|
||||
// In the case of an AltGr key, we may still need to apply a Ctrl
|
||||
// modifier to the char, either because both Ctrl keys were pressed,
|
||||
// or we got a LeftCtrl that was distinctly separate from the RightAlt.
|
||||
const auto altGrPressed = key.altGrPressed();
|
||||
const auto bothAltPressed = key.bothAltPressed();
|
||||
const auto bothCtrlPressed = key.bothCtrlPressed();
|
||||
const auto rightAltPressed = key.rightAltPressed();
|
||||
|
||||
if (altGrPressed && (bothCtrlPressed || (rightAltPressed && key.leftCtrlIsReallyPressed)))
|
||||
if (key.ctrlPressed)
|
||||
{
|
||||
codepoint = _makeCtrlChar(codepoint);
|
||||
}
|
||||
|
||||
// We may also need to apply an Alt prefix to the char sequence, but
|
||||
// if this is an AltGr key, we only do so if both Alts are pressed.
|
||||
const auto wantsEscPrefix = altGrPressed ? bothAltPressed : anyAltPressed;
|
||||
if (wantsEscPrefix && _inputMode.test(Mode::Ansi))
|
||||
{
|
||||
seq.push_back(L'\x1b');
|
||||
}
|
||||
}
|
||||
// If we don't have a UnicodeChar, we'll try and determine what the key
|
||||
// would have transmitted without any Ctrl or Alt modifiers applied. But
|
||||
// this only makes sense if there were actually modifiers pressed.
|
||||
else if (anyAltPressed || WI_IsAnyFlagSet(key.controlKeyState, CTRL_PRESSED))
|
||||
else if (key.altPressed || key.ctrlPressed)
|
||||
{
|
||||
// IMPORTANT NOTE: This implicitly, reliably rejects dead keys for us (good!).
|
||||
//
|
||||
@@ -1238,14 +1287,8 @@ void TerminalInput::_formatFallback(KeyboardHelper& kbd, const EncodingHelper& e
|
||||
return;
|
||||
}
|
||||
|
||||
// If Alt is pressed, that also needs to be applied to the sequence.
|
||||
if (anyAltPressed && _inputMode.test(Mode::Ansi))
|
||||
{
|
||||
seq.push_back(L'\x1b');
|
||||
}
|
||||
|
||||
// Once we've got the base character, we can apply the Ctrl modifier.
|
||||
if (enc.ctrlPressed())
|
||||
if (key.ctrlPressed)
|
||||
{
|
||||
codepoint = _makeCtrlChar(codepoint);
|
||||
// If we haven't found a Ctrl mapping for the key, and it's one of
|
||||
@@ -1263,6 +1306,12 @@ void TerminalInput::_formatFallback(KeyboardHelper& kbd, const EncodingHelper& e
|
||||
return;
|
||||
}
|
||||
|
||||
// If Alt is pressed, that also needs to be applied to the sequence.
|
||||
if (key.altPressed && _inputMode.test(Mode::Ansi))
|
||||
{
|
||||
seq.push_back(L'\x1b');
|
||||
}
|
||||
|
||||
_stringPushCodepoint(seq, codepoint);
|
||||
}
|
||||
|
||||
@@ -1311,9 +1360,8 @@ TerminalInput::CodepointBuffer::CodepointBuffer(uint32_t cp) noexcept
|
||||
void TerminalInput::CodepointBuffer::convertLowercase() noexcept
|
||||
{
|
||||
// NOTE: MSDN states that `lpSrcStr == lpDestStr` is valid for LCMAP_LOWERCASE.
|
||||
len = LCMapStringW(LOCALE_INVARIANT, LCMAP_LOWERCASE, &buf[0], len, &buf[0], ARRAYSIZE(buf));
|
||||
// NOTE: LCMapStringW returns the length including the null terminator.
|
||||
len -= 1;
|
||||
// NOTE: LCMapStringEx does not null-terminate the output if there's insufficient space. As such we subtract 1 from the buf size.
|
||||
len = LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_LOWERCASE, &buf[0], len, &buf[0], ARRAYSIZE(buf) - 1, nullptr, nullptr, 0);
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::CodepointBuffer::asSingleCodepoint() const noexcept
|
||||
@@ -1335,49 +1383,33 @@ uint32_t TerminalInput::CodepointBuffer::asSingleCodepoint() const noexcept
|
||||
return InvalidCodepoint;
|
||||
}
|
||||
|
||||
bool TerminalInput::SanitizedKeyEvent::anyAltPressed() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED);
|
||||
}
|
||||
|
||||
bool TerminalInput::SanitizedKeyEvent::bothAltPressed() const noexcept
|
||||
{
|
||||
return WI_AreAllFlagsSet(controlKeyState, ALT_PRESSED);
|
||||
}
|
||||
|
||||
bool TerminalInput::SanitizedKeyEvent::rightAltPressed() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(controlKeyState, RIGHT_ALT_PRESSED);
|
||||
}
|
||||
|
||||
bool TerminalInput::SanitizedKeyEvent::bothCtrlPressed() const noexcept
|
||||
{
|
||||
return WI_AreAllFlagsSet(controlKeyState, CTRL_PRESSED);
|
||||
}
|
||||
|
||||
bool TerminalInput::SanitizedKeyEvent::altGrPressed() const noexcept
|
||||
{
|
||||
return WI_IsAnyFlagSet(controlKeyState, ALT_PRESSED) && WI_IsAnyFlagSet(controlKeyState, CTRL_PRESSED);
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::KeyboardHelper::getUnmodifiedKeyboardKey(const SanitizedKeyEvent& key) noexcept
|
||||
{
|
||||
const auto virtualKey = key.virtualKey;
|
||||
const auto controlKeyState = key.controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED);
|
||||
return getKeyboardKey(virtualKey, controlKeyState, nullptr);
|
||||
return getKeyboardKeyHelper(key, ALT_PRESSED | CTRL_PRESSED, 0);
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::KeyboardHelper::getKittyBaseKey(const SanitizedKeyEvent& key) noexcept
|
||||
{
|
||||
const auto virtualKey = key.virtualKey;
|
||||
const auto controlKeyState = key.controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED | SHIFT_PRESSED | CAPSLOCK_ON);
|
||||
return _codepointToLower(getKeyboardKey(virtualKey, controlKeyState, nullptr));
|
||||
return _codepointToLower(getKeyboardKeyHelper(key, ALT_PRESSED | CTRL_PRESSED | SHIFT_PRESSED | CAPSLOCK_ON, 0));
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::KeyboardHelper::getKittyShiftedKey(const SanitizedKeyEvent& key) noexcept
|
||||
{
|
||||
return getKeyboardKeyHelper(key, ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON, SHIFT_PRESSED);
|
||||
}
|
||||
|
||||
uint32_t TerminalInput::KeyboardHelper::getKeyboardKeyHelper(const SanitizedKeyEvent& key, DWORD removeFlags, DWORD addFlags) noexcept
|
||||
{
|
||||
const auto virtualKey = key.virtualKey;
|
||||
const auto controlKeyState = key.controlKeyState & ~(ALT_PRESSED | CTRL_PRESSED | CAPSLOCK_ON) | SHIFT_PRESSED;
|
||||
auto controlKeyState = (key.controlKeyState & ~removeFlags) | addFlags;
|
||||
|
||||
// In the context of KKP, AltGr acts more like a keyboard "layer" toggle.
|
||||
// It's not a modifier that's ever transmitted as-is and instead modifies the actual base key code.
|
||||
if (key.altGrPressed)
|
||||
{
|
||||
controlKeyState |= LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED;
|
||||
}
|
||||
|
||||
return getKeyboardKey(virtualKey, controlKeyState, nullptr);
|
||||
}
|
||||
|
||||
@@ -1465,9 +1497,21 @@ void TerminalInput::KeyboardHelper::init() noexcept
|
||||
}
|
||||
}
|
||||
|
||||
// The default no-op implementation lives in TestHook.cpp (its own .obj) so the
|
||||
// linker can skip it when a test DLL supplies its own definition.
|
||||
extern "C" HKL TestHook_TerminalInput_KeyboardLayout();
|
||||
|
||||
void TerminalInput::KeyboardHelper::initSlow() noexcept
|
||||
{
|
||||
_keyboardLayout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
if (const auto hkl = TestHook_TerminalInput_KeyboardLayout())
|
||||
{
|
||||
_keyboardLayout = hkl;
|
||||
}
|
||||
else
|
||||
{
|
||||
_keyboardLayout = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), nullptr));
|
||||
}
|
||||
|
||||
memset(&_keyboardState[0], 0, sizeof(_keyboardState));
|
||||
_initialized = true;
|
||||
}
|
||||
@@ -1476,18 +1520,3 @@ TerminalInput::EncodingHelper::EncodingHelper() noexcept
|
||||
{
|
||||
memset(this, 0, sizeof(*this));
|
||||
}
|
||||
|
||||
bool TerminalInput::EncodingHelper::shiftPressed() const noexcept
|
||||
{
|
||||
return csiModifier & CSI_SHIFT;
|
||||
}
|
||||
|
||||
bool TerminalInput::EncodingHelper::altPressed() const noexcept
|
||||
{
|
||||
return csiModifier & CSI_ALT;
|
||||
}
|
||||
|
||||
bool TerminalInput::EncodingHelper::ctrlPressed() const noexcept
|
||||
{
|
||||
return csiModifier & CSI_CTRL;
|
||||
}
|
||||
|
||||
@@ -109,15 +109,12 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
uint16_t scanCode = 0;
|
||||
uint32_t codepoint = 0;
|
||||
uint32_t controlKeyState = 0;
|
||||
bool leftCtrlIsReallyPressed = false;
|
||||
bool keyDown = false;
|
||||
bool keyRepeat = false;
|
||||
|
||||
bool anyAltPressed() const noexcept;
|
||||
bool bothAltPressed() const noexcept;
|
||||
bool rightAltPressed() const noexcept;
|
||||
bool bothCtrlPressed() const noexcept;
|
||||
bool altGrPressed() const noexcept;
|
||||
bool altGrPressed = false;
|
||||
bool ctrlPressed = false;
|
||||
bool altPressed = false;
|
||||
bool shiftPressed = false;
|
||||
};
|
||||
|
||||
struct KeyboardHelper
|
||||
@@ -132,6 +129,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
private:
|
||||
uint32_t getKeyboardKey(UINT vkey, DWORD controlKeyState, HKL hkl) noexcept;
|
||||
uint32_t getKeyboardKeyHelper(const SanitizedKeyEvent& key, DWORD removeFlags, DWORD addFlags) noexcept;
|
||||
void init() noexcept;
|
||||
void initSlow() noexcept;
|
||||
|
||||
@@ -145,11 +143,6 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
struct EncodingHelper
|
||||
{
|
||||
explicit EncodingHelper() noexcept;
|
||||
|
||||
bool shiftPressed() const noexcept;
|
||||
bool altPressed() const noexcept;
|
||||
bool ctrlPressed() const noexcept;
|
||||
|
||||
// The KKP CSI u sequence is a superset of other CSI sequences:
|
||||
// CSI unicode-key-code:alternate-key-code-shift:alternate-key-code-base ; modifiers:event-type ; text-as-codepoint u
|
||||
uint32_t csiUnicodeKeyCode;
|
||||
@@ -180,6 +173,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
std::optional<WORD> _lastVirtualKeyCode;
|
||||
DWORD _lastControlKeyState = 0;
|
||||
DWORD _previousControlKeyState = 0;
|
||||
uint64_t _lastLeftCtrlTime = 0;
|
||||
uint64_t _lastRightAltTime = 0;
|
||||
|
||||
@@ -201,6 +195,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
void _initKeyboardMap() noexcept;
|
||||
DWORD _trackControlKeyState(const KEY_EVENT_RECORD& key) noexcept;
|
||||
[[nodiscard]] static DWORD _controlKeyStateFromVirtualKey(uint16_t vk, uint32_t controlKeyState) noexcept;
|
||||
[[nodiscard]] static uint32_t _makeCtrlChar(uint32_t ch) noexcept;
|
||||
[[nodiscard]] static StringType _makeCharOutput(uint32_t ch);
|
||||
[[nodiscard]] static StringType _makeNoOutput() noexcept;
|
||||
@@ -208,8 +203,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool _encodeKitty(KeyboardHelper& kbd, EncodingHelper& enc, const SanitizedKeyEvent& key) noexcept;
|
||||
static uint32_t _getKittyFunctionalKeyCode(UINT vkey, WORD scanCode, bool enhanced) noexcept;
|
||||
void _encodeRegular(EncodingHelper& enc, const SanitizedKeyEvent& key) const noexcept;
|
||||
bool _formatEncodingHelper(EncodingHelper& enc, std::wstring& str) const;
|
||||
void _formatFallback(KeyboardHelper& kbd, const EncodingHelper& enc, const SanitizedKeyEvent& key, std::wstring& seq) const;
|
||||
bool _formatEncodingHelper(EncodingHelper& enc, const SanitizedKeyEvent& key, std::wstring& str) const;
|
||||
void _formatFallback(KeyboardHelper& kbd, const SanitizedKeyEvent& key, std::wstring& seq) const;
|
||||
static void _stringPushCodepoint(std::wstring& str, uint32_t cp);
|
||||
static uint32_t _codepointToLower(uint32_t cp) noexcept;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user