-
Notifications
You must be signed in to change notification settings - Fork 9.2k
Fix various KKP encoding issues #20052
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
446f0df
e27f76d
f0e3432
fcda6e9
0e4d6b8
552a79f
5141679
6e3bb0e
63b6bba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #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 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"); | ||
|
|
||
| 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 }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #pragma once | ||
|
|
||
| namespace TestHook | ||
| { | ||
| struct LayoutGuard | ||
| { | ||
| 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; | ||
|
|
||
| operator HKL() const noexcept; | ||
|
|
||
| private: | ||
| void _destroy() const noexcept; | ||
|
|
||
| HKL _layout = nullptr; | ||
| bool _needsUnload = false; | ||
| }; | ||
|
|
||
| LayoutGuard SetTerminalInputKeyboardLayout(const wchar_t* klid); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,16 +309,48 @@ 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++) | ||
| { | ||
| Log::Comment(NoThrowString().Format(L"Testing Key 0x%x", vkey)); | ||
|
|
||
| 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,14 @@ void InputTest::TerminalInputNullKeyTests() | |
| { | ||
| using namespace std::string_view_literals; | ||
|
|
||
| const auto layout = TestHook::SetTerminalInputKeyboardLayout(L"00000409"); // US English | ||
| 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)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still need to use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But it depends on
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, so here's what I have come to understand. We refer to these APIs using an extension APIset. OneCoreSafeEtc runs those through coniosrv or equivalent rather than ntuser. As long as the implementation calls OneCoreSafeEtc, we're fine. We will need to prevent the kitty tests from running on OneCore, so that we do not experience test gate failures. :)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added exception guards around |
||
| Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate)); | ||
|
|
||
| INPUT_RECORD irTest = { 0 }; | ||
|
|
@@ -600,7 +651,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; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.