From 8f73145d9d10aded2a583dd9698f813cb9040011 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 10 Feb 2021 17:10:56 -0800 Subject: [PATCH] Move CharToKeyEvents (and friends) into InteractivityBase (#9106) These functions have a dependency on the "VT Redirected" versions of VkKeyScanW, MapVirtualKeyW and GetKeyState. Those implementations depend on the service locator and therefore the entire interactivity stack. This meant that anybody depending on just Types had to pull in **the entire host** worth of dependencies (!). Since these functions are only used in places where we have or are testing interactivity, it makes sense to consolidate them here. --- src/interactivity/base/EventSynthesis.cpp | 210 ++++++++++++++++++ .../base/lib/InteractivityBase.vcxproj | 2 + .../lib/InteractivityBase.vcxproj.filters | 8 +- src/interactivity/base/sources.inc | 3 +- src/interactivity/inc/EventSynthesis.hpp | 29 +++ src/interactivity/win32/Clipboard.cpp | 1 + src/terminal/adapter/InteractDispatch.cpp | 4 +- .../parser/ut_parser/InputEngineTest.cpp | 4 +- .../parser/ut_parser/Parser.UnitTests.vcxproj | 5 +- src/types/convert.cpp | 202 ----------------- src/types/inc/convert.hpp | 10 - 11 files changed, 259 insertions(+), 219 deletions(-) create mode 100644 src/interactivity/base/EventSynthesis.cpp create mode 100644 src/interactivity/inc/EventSynthesis.hpp diff --git a/src/interactivity/base/EventSynthesis.cpp b/src/interactivity/base/EventSynthesis.cpp new file mode 100644 index 000000000..26a15b123 --- /dev/null +++ b/src/interactivity/base/EventSynthesis.cpp @@ -0,0 +1,210 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" +#include "../inc/EventSynthesis.hpp" +#include "../../types/inc/convert.hpp" + +#ifdef BUILD_ONECORE_INTERACTIVITY +#include "../inc/VtApiRedirection.hpp" +#endif + +#pragma hdrstop + +// TODO: MSFT 14150722 - can these const values be generated at +// runtime without breaking compatibility? +static constexpr WORD altScanCode = 0x38; +static constexpr WORD leftShiftScanCode = 0x2A; + +std::deque> Microsoft::Console::Interactivity::CharToKeyEvents(const wchar_t wch, + const unsigned int codepage) +{ + const short invalidKey = -1; + short keyState = VkKeyScanW(wch); + + if (keyState == invalidKey) + { + // Determine DBCS character because these character does not know by VkKeyScan. + // GetStringTypeW(CT_CTYPE3) & C3_ALPHA can determine all linguistic characters. However, this is + // not include symbolic character for DBCS. + WORD CharType = 0; + GetStringTypeW(CT_CTYPE3, &wch, 1, &CharType); + + if (WI_IsFlagSet(CharType, C3_ALPHA) || GetQuickCharWidth(wch) == CodepointWidth::Wide) + { + keyState = 0; + } + } + + std::deque> convertedEvents; + if (keyState == invalidKey) + { + // if VkKeyScanW fails (char is not in kbd layout), we must + // emulate the key being input through the numpad + convertedEvents = SynthesizeNumpadEvents(wch, codepage); + } + else + { + convertedEvents = SynthesizeKeyboardEvents(wch, keyState); + } + + return convertedEvents; +} + +// Routine Description: +// - converts a wchar_t into a series of KeyEvents as if it was typed +// using the keyboard +// Arguments: +// - wch - the wchar_t to convert +// Return Value: +// - deque of KeyEvents that represent the wchar_t being typed +// Note: +// - will throw exception on error +std::deque> Microsoft::Console::Interactivity::SynthesizeKeyboardEvents(const wchar_t wch, const short keyState) +{ + const byte modifierState = HIBYTE(keyState); + + bool altGrSet = false; + bool shiftSet = false; + std::deque> keyEvents; + + // add modifier key event if necessary + if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed)) + { + altGrSet = true; + keyEvents.push_back(std::make_unique(true, + 1ui16, + static_cast(VK_MENU), + altScanCode, + UNICODE_NULL, + (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED))); + } + else if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed)) + { + shiftSet = true; + keyEvents.push_back(std::make_unique(true, + 1ui16, + static_cast(VK_SHIFT), + leftShiftScanCode, + UNICODE_NULL, + SHIFT_PRESSED)); + } + + const auto vk = LOBYTE(keyState); + const WORD virtualScanCode = gsl::narrow(MapVirtualKeyW(vk, MAPVK_VK_TO_VSC)); + KeyEvent keyEvent{ true, 1, LOBYTE(keyState), virtualScanCode, wch, 0 }; + + // add modifier flags if necessary + if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed)) + { + keyEvent.ActivateModifierKey(ModifierKeyState::Shift); + } + if (WI_IsFlagSet(modifierState, VkKeyScanModState::CtrlPressed)) + { + keyEvent.ActivateModifierKey(ModifierKeyState::LeftCtrl); + } + if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed)) + { + keyEvent.ActivateModifierKey(ModifierKeyState::RightAlt); + } + + // add key event down and up + keyEvents.push_back(std::make_unique(keyEvent)); + keyEvent.SetKeyDown(false); + keyEvents.push_back(std::make_unique(keyEvent)); + + // add modifier key up event + if (altGrSet) + { + keyEvents.push_back(std::make_unique(false, + 1ui16, + static_cast(VK_MENU), + altScanCode, + UNICODE_NULL, + ENHANCED_KEY)); + } + else if (shiftSet) + { + keyEvents.push_back(std::make_unique(false, + 1ui16, + static_cast(VK_SHIFT), + leftShiftScanCode, + UNICODE_NULL, + 0)); + } + + return keyEvents; +} + +// Routine Description: +// - converts a wchar_t into a series of KeyEvents as if it was typed +// using Alt + numpad +// Arguments: +// - wch - the wchar_t to convert +// Return Value: +// - deque of KeyEvents that represent the wchar_t being typed using +// alt + numpad +// Note: +// - will throw exception on error +std::deque> Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage) +{ + std::deque> keyEvents; + + //alt keydown + keyEvents.push_back(std::make_unique(true, + 1ui16, + static_cast(VK_MENU), + altScanCode, + UNICODE_NULL, + LEFT_ALT_PRESSED)); + + const int radix = 10; + std::wstring wstr{ wch }; + const auto convertedChars = ConvertToA(codepage, wstr); + if (convertedChars.size() == 1) + { + // It is OK if the char is "signed -1", we want to interpret that as "unsigned 255" for the + // "integer to character" conversion below with ::to_string, thus the static_cast. + // Prime example is nonbreaking space U+00A0 will convert to OEM by codepage 437 to 0xFF which is -1 signed. + // But it is absolutely valid as 0xFF or 255 unsigned as the correct CP437 character. + // We need to treat it as unsigned because we're going to pretend it was a keypad entry + // and you don't enter negative numbers on the keypad. + unsigned char const uch = static_cast(convertedChars.at(0)); + + // unsigned char values are in the range [0, 255] so we need to be + // able to store up to 4 chars from the conversion (including the end of string char) + auto charString = std::to_string(uch); + + for (auto& ch : std::string_view(charString)) + { + if (ch == 0) + { + break; + } + const WORD virtualKey = ch - '0' + VK_NUMPAD0; + const WORD virtualScanCode = gsl::narrow(MapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC)); + + keyEvents.push_back(std::make_unique(true, + 1ui16, + virtualKey, + virtualScanCode, + UNICODE_NULL, + LEFT_ALT_PRESSED)); + keyEvents.push_back(std::make_unique(false, + 1ui16, + virtualKey, + virtualScanCode, + UNICODE_NULL, + LEFT_ALT_PRESSED)); + } + } + + // alt keyup + keyEvents.push_back(std::make_unique(false, + 1ui16, + static_cast(VK_MENU), + altScanCode, + wch, + 0)); + return keyEvents; +} diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj b/src/interactivity/base/lib/InteractivityBase.vcxproj index b82ff404e..bf64e6cf4 100644 --- a/src/interactivity/base/lib/InteractivityBase.vcxproj +++ b/src/interactivity/base/lib/InteractivityBase.vcxproj @@ -21,6 +21,7 @@ + Create @@ -39,6 +40,7 @@ + diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters index fa2a220e9..58825ba6f 100644 --- a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters +++ b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -74,8 +77,11 @@ Header Files + + Header Files + - + \ No newline at end of file diff --git a/src/interactivity/base/sources.inc b/src/interactivity/base/sources.inc index 323690abc..4881f7911 100644 --- a/src/interactivity/base/sources.inc +++ b/src/interactivity/base/sources.inc @@ -43,9 +43,10 @@ SOURCES = \ ..\InteractivityFactory.cpp \ ..\ServiceLocator.cpp \ ..\VtApiRedirection.cpp \ + ..\EventSynthesis.cpp \ INCLUDES = \ $(INCLUDES); \ ..; \ ..\..\..\..\..\ConIoSrv; \ - \ No newline at end of file + diff --git a/src/interactivity/inc/EventSynthesis.hpp b/src/interactivity/inc/EventSynthesis.hpp new file mode 100644 index 000000000..a1ebb64f1 --- /dev/null +++ b/src/interactivity/inc/EventSynthesis.hpp @@ -0,0 +1,29 @@ +/*++ +Copyright (c) Microsoft Corporation + +Module Name: +- EventSynthesis.hpp + +Abstract: +- Defined functions for converting strings/characters into events (for interactivity) + Separated from types/convert. + +Author: +- Dustin Howett (duhowett) 10-Feb-2021 + +--*/ + +#pragma once +#include +#include +#include "../../types/inc/IInputEvent.hpp" + +namespace Microsoft::Console::Interactivity +{ + std::deque> CharToKeyEvents(const wchar_t wch, const unsigned int codepage); + + std::deque> SynthesizeKeyboardEvents(const wchar_t wch, + const short keyState); + + std::deque> SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage); +} diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index c86ef678d..b12161ccf 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -14,6 +14,7 @@ #include "../../types/inc/viewport.hpp" #include "../inc/conint.h" +#include "../inc/EventSynthesis.hpp" #include "../inc/ServiceLocator.hpp" #pragma hdrstop diff --git a/src/terminal/adapter/InteractDispatch.cpp b/src/terminal/adapter/InteractDispatch.cpp index d6786cf78..e65d39179 100644 --- a/src/terminal/adapter/InteractDispatch.cpp +++ b/src/terminal/adapter/InteractDispatch.cpp @@ -6,8 +6,8 @@ #include "InteractDispatch.hpp" #include "DispatchCommon.hpp" #include "conGetSet.hpp" +#include "../../interactivity/inc/EventSynthesis.hpp" #include "../../types/inc/Viewport.hpp" -#include "../../types/inc/convert.hpp" #include "../../inc/unicode.hpp" using namespace Microsoft::Console::Types; @@ -72,7 +72,7 @@ bool InteractDispatch::WriteString(const std::wstring_view string) for (const auto& wch : string) { - std::deque> convertedEvents = CharToKeyEvents(wch, codepage); + std::deque> convertedEvents = Microsoft::Console::Interactivity::CharToKeyEvents(wch, codepage); std::move(convertedEvents.begin(), convertedEvents.end(), diff --git a/src/terminal/parser/ut_parser/InputEngineTest.cpp b/src/terminal/parser/ut_parser/InputEngineTest.cpp index 0d3ff97f2..b870e8ac9 100644 --- a/src/terminal/parser/ut_parser/InputEngineTest.cpp +++ b/src/terminal/parser/ut_parser/InputEngineTest.cpp @@ -10,7 +10,7 @@ #include "ascii.hpp" #include "../input/terminalInput.hpp" #include "../../inc/unicode.hpp" -#include "../../types/inc/convert.hpp" +#include "../../interactivity/inc/EventSynthesis.hpp" #include #include @@ -379,7 +379,7 @@ bool TestInteractDispatch::WriteString(const std::wstring_view string) { // We're forcing the translation to CP_USA, so that it'll be constant // regardless of the CP the test is running in - std::deque> convertedEvents = CharToKeyEvents(wch, CP_USA); + std::deque> convertedEvents = Microsoft::Console::Interactivity::CharToKeyEvents(wch, CP_USA); std::move(convertedEvents.begin(), convertedEvents.end(), std::back_inserter(keyEvents)); diff --git a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj index df36be81a..b7833bd72 100644 --- a/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj +++ b/src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj @@ -38,6 +38,9 @@ {3ae13314-1939-4dfa-9c14-38ca0834050c} + + {06ec74cb-9a12-429c-b551-8562ec964846} + @@ -52,4 +55,4 @@ - \ No newline at end of file + diff --git a/src/types/convert.cpp b/src/types/convert.cpp index 3e9d8e149..010d89fb1 100644 --- a/src/types/convert.cpp +++ b/src/types/convert.cpp @@ -6,17 +6,8 @@ #include "../inc/unicode.hpp" -#ifdef BUILD_ONECORE_INTERACTIVITY -#include "../../interactivity/inc/VtApiRedirection.hpp" -#endif - #pragma hdrstop -// TODO: MSFT 14150722 - can these const values be generated at -// runtime without breaking compatibility? -static const WORD altScanCode = 0x38; -static const WORD leftShiftScanCode = 0x2A; - // Routine Description: // - Takes a multibyte string, allocates the appropriate amount of memory for the conversion, performs the conversion, // and returns the Unicode UTF-16 result in the smart pointer (and the length). @@ -139,199 +130,6 @@ static const WORD leftShiftScanCode = 0x2A; return cchTarget; } -std::deque> CharToKeyEvents(const wchar_t wch, - const unsigned int codepage) -{ - const short invalidKey = -1; - short keyState = VkKeyScanW(wch); - - if (keyState == invalidKey) - { - // Determine DBCS character because these character does not know by VkKeyScan. - // GetStringTypeW(CT_CTYPE3) & C3_ALPHA can determine all linguistic characters. However, this is - // not include symbolic character for DBCS. - WORD CharType = 0; - GetStringTypeW(CT_CTYPE3, &wch, 1, &CharType); - - if (WI_IsFlagSet(CharType, C3_ALPHA) || GetQuickCharWidth(wch) == CodepointWidth::Wide) - { - keyState = 0; - } - } - - std::deque> convertedEvents; - if (keyState == invalidKey) - { - // if VkKeyScanW fails (char is not in kbd layout), we must - // emulate the key being input through the numpad - convertedEvents = SynthesizeNumpadEvents(wch, codepage); - } - else - { - convertedEvents = SynthesizeKeyboardEvents(wch, keyState); - } - - return convertedEvents; -} - -// Routine Description: -// - converts a wchar_t into a series of KeyEvents as if it was typed -// using the keyboard -// Arguments: -// - wch - the wchar_t to convert -// Return Value: -// - deque of KeyEvents that represent the wchar_t being typed -// Note: -// - will throw exception on error -std::deque> SynthesizeKeyboardEvents(const wchar_t wch, const short keyState) -{ - const byte modifierState = HIBYTE(keyState); - - bool altGrSet = false; - bool shiftSet = false; - std::deque> keyEvents; - - // add modifier key event if necessary - if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed)) - { - altGrSet = true; - keyEvents.push_back(std::make_unique(true, - 1ui16, - static_cast(VK_MENU), - altScanCode, - UNICODE_NULL, - (ENHANCED_KEY | LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED))); - } - else if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed)) - { - shiftSet = true; - keyEvents.push_back(std::make_unique(true, - 1ui16, - static_cast(VK_SHIFT), - leftShiftScanCode, - UNICODE_NULL, - SHIFT_PRESSED)); - } - - const auto vk = LOBYTE(keyState); - const WORD virtualScanCode = gsl::narrow(MapVirtualKeyW(vk, MAPVK_VK_TO_VSC)); - KeyEvent keyEvent{ true, 1, LOBYTE(keyState), virtualScanCode, wch, 0 }; - - // add modifier flags if necessary - if (WI_IsFlagSet(modifierState, VkKeyScanModState::ShiftPressed)) - { - keyEvent.ActivateModifierKey(ModifierKeyState::Shift); - } - if (WI_IsFlagSet(modifierState, VkKeyScanModState::CtrlPressed)) - { - keyEvent.ActivateModifierKey(ModifierKeyState::LeftCtrl); - } - if (WI_AreAllFlagsSet(modifierState, VkKeyScanModState::CtrlAndAltPressed)) - { - keyEvent.ActivateModifierKey(ModifierKeyState::RightAlt); - } - - // add key event down and up - keyEvents.push_back(std::make_unique(keyEvent)); - keyEvent.SetKeyDown(false); - keyEvents.push_back(std::make_unique(keyEvent)); - - // add modifier key up event - if (altGrSet) - { - keyEvents.push_back(std::make_unique(false, - 1ui16, - static_cast(VK_MENU), - altScanCode, - UNICODE_NULL, - ENHANCED_KEY)); - } - else if (shiftSet) - { - keyEvents.push_back(std::make_unique(false, - 1ui16, - static_cast(VK_SHIFT), - leftShiftScanCode, - UNICODE_NULL, - 0)); - } - - return keyEvents; -} - -// Routine Description: -// - converts a wchar_t into a series of KeyEvents as if it was typed -// using Alt + numpad -// Arguments: -// - wch - the wchar_t to convert -// Return Value: -// - deque of KeyEvents that represent the wchar_t being typed using -// alt + numpad -// Note: -// - will throw exception on error -std::deque> SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage) -{ - std::deque> keyEvents; - - //alt keydown - keyEvents.push_back(std::make_unique(true, - 1ui16, - static_cast(VK_MENU), - altScanCode, - UNICODE_NULL, - LEFT_ALT_PRESSED)); - - const int radix = 10; - std::wstring wstr{ wch }; - const auto convertedChars = ConvertToA(codepage, wstr); - if (convertedChars.size() == 1) - { - // It is OK if the char is "signed -1", we want to interpret that as "unsigned 255" for the - // "integer to character" conversion below with ::to_string, thus the static_cast. - // Prime example is nonbreaking space U+00A0 will convert to OEM by codepage 437 to 0xFF which is -1 signed. - // But it is absolutely valid as 0xFF or 255 unsigned as the correct CP437 character. - // We need to treat it as unsigned because we're going to pretend it was a keypad entry - // and you don't enter negative numbers on the keypad. - unsigned char const uch = static_cast(convertedChars.at(0)); - - // unsigned char values are in the range [0, 255] so we need to be - // able to store up to 4 chars from the conversion (including the end of string char) - auto charString = std::to_string(uch); - - for (auto& ch : std::string_view(charString)) - { - if (ch == 0) - { - break; - } - const WORD virtualKey = ch - '0' + VK_NUMPAD0; - const WORD virtualScanCode = gsl::narrow(MapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC)); - - keyEvents.push_back(std::make_unique(true, - 1ui16, - virtualKey, - virtualScanCode, - UNICODE_NULL, - LEFT_ALT_PRESSED)); - keyEvents.push_back(std::make_unique(false, - 1ui16, - virtualKey, - virtualScanCode, - UNICODE_NULL, - LEFT_ALT_PRESSED)); - } - } - - // alt keyup - keyEvents.push_back(std::make_unique(false, - 1ui16, - static_cast(VK_MENU), - altScanCode, - wch, - 0)); - return keyEvents; -} - // Routine Description: // - naively determines the width of a UCS2 encoded wchar // Arguments: diff --git a/src/types/inc/convert.hpp b/src/types/inc/convert.hpp index 6d51653c1..6223a07de 100644 --- a/src/types/inc/convert.hpp +++ b/src/types/inc/convert.hpp @@ -14,11 +14,8 @@ Author: --*/ #pragma once -#include #include #include -#include -#include "IInputEvent.hpp" enum class CodepointWidth : BYTE { @@ -37,13 +34,6 @@ enum class CodepointWidth : BYTE [[nodiscard]] size_t GetALengthFromW(const UINT codepage, const std::wstring_view source); -std::deque> CharToKeyEvents(const wchar_t wch, const unsigned int codepage); - -std::deque> SynthesizeKeyboardEvents(const wchar_t wch, - const short keyState); - -std::deque> SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage); - CodepointWidth GetQuickCharWidth(const wchar_t wch) noexcept; wchar_t Utf16ToUcs2(const std::wstring_view charData);