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);