Delegate all character input to the character event handler (#4192)
My basic idea was that `WM_CHAR` is just the better `WM_KEYDOWN`. The latter fails to properly support common dead key sequences like in #3516. As such I added some logic to `Terminal::SendKeyEvent` to make it return false if the pressed key represents a printable character. This causes us to receive a character event with a (hopefully) correctly composed code unit, which then gets sent to `Terminal::SendCharEvent`. `Terminal::SendCharEvent` in turn had to be modified to support potentially pressed modifier keys, since `Terminal::SendKeyEvent` isn't doing that for us anymore. Lastly `TerminalInput` had to be modified heavily to support character events with modifier key states. In order to do so I merged its `HandleKey` and `HandleChar` methods into a single one, that now handles both cases. Since key events will now contain character data and character events key codes the decision logic in `TerminalInput::HandleKey` had to be rewritten. ## PR Checklist * [x] CLA signed * [x] Tests added/passed * [x] I've discussed this with core contributors already. ## Validation Steps Performed * See #3516. * I don't have any keyboard that generates surrogate characters. Due to this I modified `TermControl::_SendPastedTextToConnection` to send the data to `_terminal->SendCharEvent()` instead. I then pasted the test string ""𐐌𐐜𐐬" and ensured that the new `TerminalInput::_SendChar` method still correctly assembles surrogate pairs. Closes #3516 Closes #3554 (obsoleted by this PR) Potentially impacts #391, which sounds like a duplicate of #3516
This commit is contained in:
parent
52d6c03e64
commit
a9c9714295
|
@ -427,10 +427,8 @@ const wchar_t* _stdcall TerminalGetSelection(void* terminal)
|
|||
return returnText.release();
|
||||
}
|
||||
|
||||
void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam)
|
||||
static ControlKeyStates getControlKeyState() noexcept
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
const auto scanCode = MapVirtualKeyW((UINT)wParam, MAPVK_VK_TO_VSC);
|
||||
struct KeyModifier
|
||||
{
|
||||
int vkey;
|
||||
|
@ -458,10 +456,17 @@ void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam)
|
|||
}
|
||||
}
|
||||
|
||||
publicTerminal->_terminal->SendKeyEvent((WORD)wParam, (WORD)scanCode, flags);
|
||||
return flags;
|
||||
}
|
||||
|
||||
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch)
|
||||
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
const auto flags = getControlKeyState();
|
||||
publicTerminal->_terminal->SendKeyEvent(vkey, scanCode, flags);
|
||||
}
|
||||
|
||||
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
|
||||
{
|
||||
if (ch == '\t')
|
||||
{
|
||||
|
@ -469,7 +474,8 @@ void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch)
|
|||
}
|
||||
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
publicTerminal->_terminal->SendCharEvent(ch);
|
||||
const auto flags = getControlKeyState();
|
||||
publicTerminal->_terminal->SendCharEvent(ch, scanCode, flags);
|
||||
}
|
||||
|
||||
void _stdcall DestroyTerminal(void* terminal)
|
||||
|
|
|
@ -34,8 +34,8 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
|
|||
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
|
||||
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
|
||||
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
|
||||
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam);
|
||||
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch);
|
||||
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode);
|
||||
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
|
||||
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
|
||||
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
|
||||
};
|
||||
|
@ -82,8 +82,8 @@ private:
|
|||
friend void _stdcall TerminalClearSelection(void* terminal);
|
||||
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
|
||||
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
|
||||
friend void _stdcall TerminalSendKeyEvent(void* terminal, WPARAM wParam);
|
||||
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch);
|
||||
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode);
|
||||
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
|
||||
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
|
||||
friend void _stdcall TerminalBlinkCursor(void* terminal);
|
||||
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
|
||||
|
|
|
@ -650,8 +650,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
}
|
||||
|
||||
const auto ch = e.Character();
|
||||
|
||||
const bool handled = _terminal->SendCharEvent(ch);
|
||||
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
const bool handled = _terminal->SendCharEvent(ch, scanCode, modifiers);
|
||||
e.Handled(handled);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Microsoft::Terminal::Core
|
|||
|
||||
virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) = 0;
|
||||
virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0;
|
||||
virtual bool SendCharEvent(const wchar_t ch) = 0;
|
||||
virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0;
|
||||
|
||||
// void SendMouseEvent(uint row, uint col, KeyModifiers modifiers);
|
||||
[[nodiscard]] virtual HRESULT UserResize(const COORD size) noexcept = 0;
|
||||
|
|
|
@ -387,14 +387,21 @@ bool Terminal::IsTrackingMouseInput() const noexcept
|
|||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular key event to the terminal. The terminal will translate
|
||||
// the key and the modifiers pressed into the appropriate VT sequence for that
|
||||
// key chord. If we do translate the key, we'll return true. In that case, the
|
||||
// event should NOT be processed any further. If we return false, the event
|
||||
// was NOT translated, and we should instead use the event to try and get the
|
||||
// real character out of the event.
|
||||
// - Send this particular (non-character) key event to the terminal.
|
||||
// - The terminal will translate the key and the modifiers pressed into the
|
||||
// appropriate VT sequence for that key chord. If we do translate the key,
|
||||
// we'll return true. In that case, the event should NOT be processed any further.
|
||||
// - Character events (e.g. WM_CHAR) are generally the best way to properly receive
|
||||
// keyboard input on Windows though, as the OS is suited best at handling the
|
||||
// translation of the current keyboard layout, dead keys, etc.
|
||||
// As a result of this false is returned for all key events that contain characters.
|
||||
// SendCharEvent may then be called with the data obtained from a character event.
|
||||
// - As a special case we'll always handle VK_TAB key events.
|
||||
// This must be done due to TermControl::_KeyDownHandler (one of the callers)
|
||||
// always marking tab key events as handled, causing no character event to be raised.
|
||||
// Arguments:
|
||||
// - vkey: The vkey of the key pressed.
|
||||
// - vkey: The vkey of the last pressed key.
|
||||
// - scanCode: The scan code of the last pressed key.
|
||||
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
// Return Value:
|
||||
// - true if we translated the key event, and it should not be processed any further.
|
||||
|
@ -402,50 +409,35 @@ bool Terminal::IsTrackingMouseInput() const noexcept
|
|||
bool Terminal::SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states)
|
||||
{
|
||||
TrySnapOnInput();
|
||||
_StoreKeyEvent(vkey, scanCode);
|
||||
|
||||
const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed();
|
||||
|
||||
// Alt key sequences _require_ the char to be in the keyevent. If alt is
|
||||
// pressed, manually get the character that's being typed, and put it in the
|
||||
// KeyEvent.
|
||||
// DON'T manually handle Alt+Space - the system will use this to bring up
|
||||
// the system menu for restore, min/maximize, size, move, close
|
||||
wchar_t ch = UNICODE_NULL;
|
||||
if (states.IsAltPressed() && vkey != VK_SPACE)
|
||||
// the system menu for restore, min/maximize, size, move, close.
|
||||
// (This doesn't apply to Ctrl+Alt+Space.)
|
||||
if (isAltOnlyPressed && vkey == VK_SPACE)
|
||||
{
|
||||
ch = _CharacterFromKeyEvent(vkey, scanCode, states);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (states.IsCtrlPressed())
|
||||
{
|
||||
switch (vkey)
|
||||
{
|
||||
case 0x48:
|
||||
// Manually handle Ctrl+H. Ctrl+H should be handled as Backspace. To do this
|
||||
// correctly, the keyEvents's char needs to be set to Backspace.
|
||||
// 0x48 is the VKEY for 'H', which isn't named
|
||||
ch = UNICODE_BACKSPACE;
|
||||
break;
|
||||
case VK_SPACE:
|
||||
// Manually handle Ctrl+Space here. The terminalInput translator requires
|
||||
// the char to be set to Space for space handling to work correctly.
|
||||
ch = UNICODE_SPACE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto ch = _CharacterFromKeyEvent(vkey, scanCode, states);
|
||||
|
||||
// Manually handle Escape here. If we let it fall through, it'll come
|
||||
// back up through the character handler. It's registered as a translation
|
||||
// in TerminalInput, so we'll let TerminalInput control it.
|
||||
if (vkey == VK_ESCAPE)
|
||||
// Delegate it to the character event handler if this key event can be
|
||||
// mapped to one (see method description above). For Alt+key combinations
|
||||
// we'll not receive another character event for some reason though.
|
||||
// -> Don't delegate the event if this is a Alt+key combination.
|
||||
//
|
||||
// As a special case we'll furthermore always handle VK_TAB
|
||||
// key events here instead of in Terminal::SendCharEvent.
|
||||
// See the method description for more information.
|
||||
if (!isAltOnlyPressed && vkey != VK_TAB && ch != UNICODE_NULL)
|
||||
{
|
||||
ch = UNICODE_ESC;
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool manuallyHandled = ch != UNICODE_NULL;
|
||||
|
||||
KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
|
||||
const bool translated = _terminalInput->HandleKey(&keyEv);
|
||||
|
||||
return translated && manuallyHandled;
|
||||
return _terminalInput->HandleKey(&keyEv);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -474,9 +466,39 @@ bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButt
|
|||
return _terminalInput->HandleMouse(viewportPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta);
|
||||
}
|
||||
|
||||
bool Terminal::SendCharEvent(const wchar_t ch)
|
||||
// Method Description:
|
||||
// - Send this particular character to the terminal.
|
||||
// - This method is the counterpart to SendKeyEvent and behaves almost identical.
|
||||
// The difference is the focus on sending characters to the terminal,
|
||||
// whereas SendKeyEvent handles the sending of keys like the arrow keys.
|
||||
// Arguments:
|
||||
// - ch: The UTF-16 code unit to be sent.
|
||||
// - scanCode: The scan code of the last pressed key. Can be left 0.
|
||||
// - states: The Microsoft::Terminal::Core::ControlKeyStates representing the modifier key states.
|
||||
// Return Value:
|
||||
// - true if we translated the character event, and it should not be processed any further.
|
||||
// - false otherwise.
|
||||
bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states)
|
||||
{
|
||||
return _terminalInput->HandleChar(ch);
|
||||
// DON'T manually handle Alt+Space - the system will use this to bring up
|
||||
// the system menu for restore, min/maximize, size, move, close.
|
||||
if (ch == L' ' && states.IsAltPressed() && !states.IsCtrlPressed())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto vkey = _TakeVirtualKeyFromLastKeyEvent(scanCode);
|
||||
if (vkey == 0 && scanCode != 0)
|
||||
{
|
||||
vkey = _VirtualKeyFromScanCode(scanCode);
|
||||
}
|
||||
if (vkey == 0)
|
||||
{
|
||||
vkey = _VirtualKeyFromCharacter(ch);
|
||||
}
|
||||
|
||||
KeyEvent keyEv{ true, 0, vkey, scanCode, ch, states.Value() };
|
||||
return _terminalInput->HandleKey(&keyEv);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -490,6 +512,29 @@ WORD Terminal::_ScanCodeFromVirtualKey(const WORD vkey) noexcept
|
|||
return LOWORD(MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the virtual key code for the given keyboard's scan code.
|
||||
// Arguments:
|
||||
// - scanCode: The keyboard's scan code.
|
||||
// Return Value:
|
||||
// - The virtual key code. 0 if no mapping can be found.
|
||||
WORD Terminal::_VirtualKeyFromScanCode(const WORD scanCode) noexcept
|
||||
{
|
||||
return LOWORD(MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns any virtual key code that produces the given character.
|
||||
// Arguments:
|
||||
// - scanCode: The keyboard's scan code.
|
||||
// Return Value:
|
||||
// - The virtual key code. 0 if no mapping can be found.
|
||||
WORD Terminal::_VirtualKeyFromCharacter(const wchar_t ch) noexcept
|
||||
{
|
||||
const auto vkey = LOWORD(VkKeyScanW(ch));
|
||||
return vkey == -1 ? 0 : vkey;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Translates the specified virtual key code and keyboard state to the corresponding character.
|
||||
// Arguments:
|
||||
|
@ -532,6 +577,40 @@ catch (...)
|
|||
return UNICODE_INVALID;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - It's possible for a single scan code on a keyboard to
|
||||
// produce different key codes depending on the keyboard state.
|
||||
// MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK) will always chose one of the
|
||||
// possibilities no matter what though and thus can't be used in SendCharEvent.
|
||||
// - This method stores the key code from a key event (SendKeyEvent).
|
||||
// If the key event contains character data, handling of the event will be
|
||||
// denied, in order to delegate the work to the character event handler.
|
||||
// - The character event handler (SendCharEvent) will now pick up
|
||||
// the stored key code to restore the full key event data.
|
||||
// Arguments:
|
||||
// - vkey: The virtual key code.
|
||||
// - scanCode: The scan code.
|
||||
void Terminal::_StoreKeyEvent(const WORD vkey, const WORD scanCode)
|
||||
{
|
||||
_lastKeyEventCodes.emplace(KeyEventCodes{ vkey, scanCode });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This method acts as a counterpart to _StoreKeyEvent and extracts a stored
|
||||
// key code. As a safety measure it'll ensure that the given scan code
|
||||
// matches the stored scan code from the previous key event.
|
||||
// - See _StoreKeyEvent for more information.
|
||||
// Arguments:
|
||||
// - scanCode: The scan code.
|
||||
// Return Value:
|
||||
// - The key code matching the given scan code. Otherwise 0.
|
||||
WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
|
||||
{
|
||||
const auto codes = _lastKeyEventCodes.value_or(KeyEventCodes{});
|
||||
_lastKeyEventCodes.reset();
|
||||
return codes.ScanCode == scanCode ? codes.VirtualKey : 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Acquire a read lock on the terminal.
|
||||
// Return Value:
|
||||
|
|
|
@ -115,7 +115,7 @@ public:
|
|||
// These methods are defined in Terminal.cpp
|
||||
bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states) override;
|
||||
bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override;
|
||||
bool SendCharEvent(const wchar_t ch) override;
|
||||
bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override;
|
||||
|
||||
[[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override;
|
||||
void UserScrollViewport(const int viewTop) override;
|
||||
|
@ -251,9 +251,22 @@ private:
|
|||
// underneath them, while others would prefer to anchor it in place.
|
||||
// Either way, we should make this behavior controlled by a setting.
|
||||
|
||||
// Since virtual keys are non-zero, you assume that this field is empty/invalid if it is.
|
||||
struct KeyEventCodes
|
||||
{
|
||||
WORD VirtualKey;
|
||||
WORD ScanCode;
|
||||
};
|
||||
std::optional<KeyEventCodes> _lastKeyEventCodes;
|
||||
|
||||
static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept;
|
||||
static WORD _VirtualKeyFromScanCode(const WORD scanCode) noexcept;
|
||||
static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept;
|
||||
static wchar_t _CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept;
|
||||
|
||||
void _StoreKeyEvent(const WORD vkey, const WORD scanCode);
|
||||
WORD _TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept;
|
||||
|
||||
int _VisibleStartIndex() const noexcept;
|
||||
int _VisibleEndIndex() const noexcept;
|
||||
|
||||
|
|
|
@ -48,12 +48,12 @@ namespace TerminalCoreUnitTests
|
|||
// Verify that Alt+a generates a lowercase a on the input
|
||||
expectedinput = L"\x1b"
|
||||
"a";
|
||||
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', 0, ControlKeyStates::LeftAltPressed));
|
||||
VERIFY_IS_TRUE(term.SendCharEvent(L'a', 0, ControlKeyStates::LeftAltPressed));
|
||||
|
||||
// Verify that Alt+shift+a generates a uppercase a on the input
|
||||
expectedinput = L"\x1b"
|
||||
"A";
|
||||
VERIFY_IS_TRUE(term.SendKeyEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
|
||||
VERIFY_IS_TRUE(term.SendCharEvent(L'A', 0, ControlKeyStates::LeftAltPressed | ControlKeyStates::ShiftPressed));
|
||||
}
|
||||
|
||||
void InputTest::AltSpace()
|
||||
|
@ -62,5 +62,6 @@ namespace TerminalCoreUnitTests
|
|||
// bring up the system menu for restore, min/maximize, size, move,
|
||||
// close
|
||||
VERIFY_IS_FALSE(term.SendKeyEvent(L' ', 0, ControlKeyStates::LeftAltPressed));
|
||||
VERIFY_IS_FALSE(term.SendCharEvent(L' ', 0, ControlKeyStates::LeftAltPressed));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -207,10 +207,10 @@ namespace Microsoft.Terminal.Wpf
|
|||
public static extern void DestroyTerminal(IntPtr terminal);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern void TerminalSendKeyEvent(IntPtr terminal, IntPtr wParam);
|
||||
public static extern void TerminalSendKeyEvent(IntPtr terminal, ushort vkey, ushort scanCode);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern void TerminalSendCharEvent(IntPtr terminal, char ch);
|
||||
public static extern void TerminalSendCharEvent(IntPtr terminal, char ch, ushort scanCode);
|
||||
|
||||
[DllImport("PublicTerminalCore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)]
|
||||
public static extern void TerminalSetTheme(IntPtr terminal, [MarshalAs(UnmanagedType.Struct)] TerminalTheme theme, string fontFamily, short fontSize, int newDpi);
|
||||
|
|
|
@ -231,13 +231,15 @@ namespace Microsoft.Terminal.Wpf
|
|||
NativeMethods.SetFocus(this.hwnd);
|
||||
break;
|
||||
case NativeMethods.WindowMessage.WM_KEYDOWN:
|
||||
// WM_KEYDOWN lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
|
||||
NativeMethods.TerminalSetCursorVisible(this.terminal, true);
|
||||
NativeMethods.TerminalClearSelection(this.terminal);
|
||||
NativeMethods.TerminalSendKeyEvent(this.terminal, wParam);
|
||||
NativeMethods.TerminalSendKeyEvent(this.terminal, (ushort)wParam, Marshal.ReadByte(lParam, 2));
|
||||
this.blinkTimer?.Start();
|
||||
break;
|
||||
case NativeMethods.WindowMessage.WM_CHAR:
|
||||
NativeMethods.TerminalSendCharEvent(this.terminal, (char)wParam);
|
||||
// WM_CHAR lParam layout documentation: https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-char
|
||||
NativeMethods.TerminalSendCharEvent(this.terminal, (char)wParam, Marshal.ReadByte(lParam, 2));
|
||||
break;
|
||||
case NativeMethods.WindowMessage.WM_WINDOWPOSCHANGED:
|
||||
var windowpos = (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(NativeMethods.WINDOWPOS));
|
||||
|
|
|
@ -31,8 +31,8 @@ using namespace Microsoft::Console::VirtualTerminal;
|
|||
|
||||
// For magic reasons, this has to live outside the class. Something wonderful about TAEF macros makes it
|
||||
// invisible to the linker when inside the class.
|
||||
static PWSTR s_pwszInputExpected;
|
||||
static wchar_t s_pwsInputBuffer[256];
|
||||
static std::wstring s_expectedInput;
|
||||
|
||||
class Microsoft::Console::VirtualTerminal::InputTest
|
||||
{
|
||||
public:
|
||||
|
@ -82,10 +82,7 @@ void InputTest::s_TerminalInputTestCallback(_In_ std::deque<std::unique_ptr<IInp
|
|||
{
|
||||
auto records = IInputEvent::ToInputRecords(inEvents);
|
||||
|
||||
size_t cInputExpected = 0;
|
||||
VERIFY_SUCCEEDED(StringCchLengthW(s_pwszInputExpected, STRSAFE_MAX_CCH, &cInputExpected));
|
||||
|
||||
if (VERIFY_ARE_EQUAL(cInputExpected, records.size(), L"Verify expected and actual input array lengths matched."))
|
||||
if (VERIFY_ARE_EQUAL(s_expectedInput.size(), records.size(), L"Verify expected and actual input array lengths matched."))
|
||||
{
|
||||
Log::Comment(L"We are expecting always key events and always key down. All other properties should not be written by simulated keys.");
|
||||
|
||||
|
@ -97,8 +94,8 @@ void InputTest::s_TerminalInputTestCallback(_In_ std::deque<std::unique_ptr<IInp
|
|||
Log::Comment(L"Verifying individual array members...");
|
||||
for (size_t i = 0; i < records.size(); i++)
|
||||
{
|
||||
irExpected.Event.KeyEvent.uChar.UnicodeChar = s_pwszInputExpected[i];
|
||||
VERIFY_ARE_EQUAL(irExpected, records[i], NoThrowString().Format(L"%c, %c", s_pwszInputExpected[i], records[i].Event.KeyEvent.uChar.UnicodeChar));
|
||||
irExpected.Event.KeyEvent.uChar.UnicodeChar = s_expectedInput[i];
|
||||
VERIFY_ARE_EQUAL(irExpected, records[i], NoThrowString().Format(L"%c, %c", s_expectedInput[i], records[i].Event.KeyEvent.uChar.UnicodeChar));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +154,7 @@ void InputTest::TerminalInputTests()
|
|||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
|
||||
Log::Comment(L"Sending every possible VKEY at the input stream for interception during key DOWN.");
|
||||
for (BYTE vkey = 0; vkey < BYTE_MAX; vkey++)
|
||||
|
@ -171,109 +168,100 @@ void InputTest::TerminalInputTests()
|
|||
irTest.Event.KeyEvent.wRepeatCount = 1;
|
||||
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
|
||||
irTest.Event.KeyEvent.bKeyDown = TRUE;
|
||||
// MapVirtualKey's return value must be mapped to a wchar_t because
|
||||
// that's what we're requesting from it, there isn't any data loss
|
||||
// from the cast.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4242)
|
||||
irTest.Event.KeyEvent.uChar.UnicodeChar = (wchar_t)MapVirtualKey(vkey, MAPVK_VK_TO_CHAR);
|
||||
#pragma warning(pop)
|
||||
irTest.Event.KeyEvent.uChar.UnicodeChar = LOWORD(MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR));
|
||||
|
||||
// Set up expected result
|
||||
switch (vkey)
|
||||
{
|
||||
case VK_TAB:
|
||||
s_pwszInputExpected = L"\x09";
|
||||
s_expectedInput = L"\x09";
|
||||
break;
|
||||
case VK_BACK:
|
||||
s_pwszInputExpected = L"\x7f";
|
||||
s_expectedInput = L"\x7f";
|
||||
break;
|
||||
case VK_ESCAPE:
|
||||
s_pwszInputExpected = L"\x1b";
|
||||
s_expectedInput = L"\x1b";
|
||||
break;
|
||||
case VK_PAUSE:
|
||||
s_pwszInputExpected = L"\x1a";
|
||||
s_expectedInput = L"\x1a";
|
||||
break;
|
||||
case VK_UP:
|
||||
s_pwszInputExpected = L"\x1b[A";
|
||||
s_expectedInput = L"\x1b[A";
|
||||
break;
|
||||
case VK_DOWN:
|
||||
s_pwszInputExpected = L"\x1b[B";
|
||||
s_expectedInput = L"\x1b[B";
|
||||
break;
|
||||
case VK_RIGHT:
|
||||
s_pwszInputExpected = L"\x1b[C";
|
||||
s_expectedInput = L"\x1b[C";
|
||||
break;
|
||||
case VK_LEFT:
|
||||
s_pwszInputExpected = L"\x1b[D";
|
||||
s_expectedInput = L"\x1b[D";
|
||||
break;
|
||||
case VK_HOME:
|
||||
s_pwszInputExpected = L"\x1b[H";
|
||||
s_expectedInput = L"\x1b[H";
|
||||
break;
|
||||
case VK_INSERT:
|
||||
s_pwszInputExpected = L"\x1b[2~";
|
||||
s_expectedInput = L"\x1b[2~";
|
||||
break;
|
||||
case VK_DELETE:
|
||||
s_pwszInputExpected = L"\x1b[3~";
|
||||
s_expectedInput = L"\x1b[3~";
|
||||
break;
|
||||
case VK_END:
|
||||
s_pwszInputExpected = L"\x1b[F";
|
||||
s_expectedInput = L"\x1b[F";
|
||||
break;
|
||||
case VK_PRIOR:
|
||||
s_pwszInputExpected = L"\x1b[5~";
|
||||
s_expectedInput = L"\x1b[5~";
|
||||
break;
|
||||
case VK_NEXT:
|
||||
s_pwszInputExpected = L"\x1b[6~";
|
||||
s_expectedInput = L"\x1b[6~";
|
||||
break;
|
||||
case VK_F1:
|
||||
s_pwszInputExpected = L"\x1bOP";
|
||||
s_expectedInput = L"\x1bOP";
|
||||
break;
|
||||
case VK_F2:
|
||||
s_pwszInputExpected = L"\x1bOQ";
|
||||
s_expectedInput = L"\x1bOQ";
|
||||
break;
|
||||
case VK_F3:
|
||||
s_pwszInputExpected = L"\x1bOR";
|
||||
s_expectedInput = L"\x1bOR";
|
||||
break;
|
||||
case VK_F4:
|
||||
s_pwszInputExpected = L"\x1bOS";
|
||||
s_expectedInput = L"\x1bOS";
|
||||
break;
|
||||
case VK_F5:
|
||||
s_pwszInputExpected = L"\x1b[15~";
|
||||
s_expectedInput = L"\x1b[15~";
|
||||
break;
|
||||
case VK_F6:
|
||||
s_pwszInputExpected = L"\x1b[17~";
|
||||
s_expectedInput = L"\x1b[17~";
|
||||
break;
|
||||
case VK_F7:
|
||||
s_pwszInputExpected = L"\x1b[18~";
|
||||
s_expectedInput = L"\x1b[18~";
|
||||
break;
|
||||
case VK_F8:
|
||||
s_pwszInputExpected = L"\x1b[19~";
|
||||
s_expectedInput = L"\x1b[19~";
|
||||
break;
|
||||
case VK_F9:
|
||||
s_pwszInputExpected = L"\x1b[20~";
|
||||
s_expectedInput = L"\x1b[20~";
|
||||
break;
|
||||
case VK_F10:
|
||||
s_pwszInputExpected = L"\x1b[21~";
|
||||
s_expectedInput = L"\x1b[21~";
|
||||
break;
|
||||
case VK_F11:
|
||||
s_pwszInputExpected = L"\x1b[23~";
|
||||
s_expectedInput = L"\x1b[23~";
|
||||
break;
|
||||
case VK_F12:
|
||||
s_pwszInputExpected = L"\x1b[24~";
|
||||
s_expectedInput = L"\x1b[24~";
|
||||
break;
|
||||
case VK_CANCEL:
|
||||
s_pwszInputExpected = L"\x3";
|
||||
s_expectedInput = L"\x3";
|
||||
break;
|
||||
default:
|
||||
fExpectedKeyHandled = false;
|
||||
break;
|
||||
}
|
||||
if (!fExpectedKeyHandled && (vkey >= '0' && vkey <= 'Z'))
|
||||
if (!fExpectedKeyHandled && irTest.Event.KeyEvent.uChar.UnicodeChar != 0)
|
||||
{
|
||||
// we need to have some sort of string to compare to in the
|
||||
// callback, we'll build it here.
|
||||
static wchar_t keyArr[2] = { 0 };
|
||||
keyArr[0] = vkey;
|
||||
s_pwszInputExpected = keyArr;
|
||||
s_expectedInput.clear();
|
||||
s_expectedInput.push_back(irTest.Event.KeyEvent.uChar.UnicodeChar);
|
||||
fExpectedKeyHandled = true;
|
||||
}
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
|
@ -335,150 +323,143 @@ void InputTest::TerminalInputModifierKeyTests()
|
|||
TEST_METHOD_PROPERTY(L"Data:uiModifierKeystate", L"{0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x000A, 0x000C, 0x000E, 0x0010, 0x0011, 0x0012, 0x0013}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
unsigned int uiActualKeystate;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiModifierKeystate", uiActualKeystate));
|
||||
unsigned int uiKeystate = uiActualKeystate;
|
||||
unsigned int uiKeystate;
|
||||
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"uiModifierKeystate", uiKeystate));
|
||||
|
||||
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
const BYTE slashVkey = LOBYTE(VkKeyScanW(L'/'));
|
||||
const BYTE nullVkey = LOBYTE(VkKeyScanW(0));
|
||||
|
||||
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));
|
||||
// zero memory
|
||||
memset(s_pwsInputBuffer, 0, ARRAYSIZE(s_pwsInputBuffer) * sizeof(wchar_t));
|
||||
|
||||
bool fExpectedKeyHandled = true;
|
||||
bool fModifySequence = false;
|
||||
INPUT_RECORD irTest = { 0 };
|
||||
irTest.EventType = KEY_EVENT;
|
||||
irTest.Event.KeyEvent.dwControlKeyState = uiActualKeystate;
|
||||
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
|
||||
irTest.Event.KeyEvent.wRepeatCount = 1;
|
||||
irTest.Event.KeyEvent.wVirtualKeyCode = vkey;
|
||||
irTest.Event.KeyEvent.bKeyDown = TRUE;
|
||||
irTest.Event.KeyEvent.uChar.UnicodeChar = LOWORD(MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR));
|
||||
|
||||
// Ctrl-/ is handled in another test, because it's weird.
|
||||
if (ControlPressed(uiKeystate) && (vkey == VK_DIVIDE || vkey == slashVkey))
|
||||
if (ControlPressed(uiKeystate))
|
||||
{
|
||||
continue;
|
||||
// For Ctrl-/ see DifferentModifiersTest.
|
||||
if (vkey == VK_DIVIDE || vkey == slashVkey)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// For Ctrl-@/Ctrl-Space see TerminalInputNullKeyTests.
|
||||
if (vkey == nullVkey || vkey == ' ')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Set up expected result
|
||||
switch (vkey)
|
||||
{
|
||||
case '@':
|
||||
case '2':
|
||||
if (ControlPressed(uiKeystate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// C-@ gets translated to null, which doesn't play nicely with this test.
|
||||
// So theres the TerminalInputNullKeyTests Test instead.
|
||||
break;
|
||||
case 0x20:
|
||||
// Space generally gets translated to null, which again, doesn't play well.
|
||||
continue;
|
||||
case VK_BACK:
|
||||
// Backspace is kinda different from other keys - we'll handle in another test.
|
||||
case VK_OEM_2:
|
||||
// VK_OEM_2 is typically the '/?' key
|
||||
continue;
|
||||
// wcscpy_s(s_pwsInputBuffer, L"\x7f");
|
||||
break;
|
||||
case VK_ESCAPE:
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b");
|
||||
// s_expectedInput = L"\x7f";
|
||||
break;
|
||||
case VK_PAUSE:
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1a");
|
||||
s_expectedInput = L"\x1a";
|
||||
break;
|
||||
case VK_UP:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mA");
|
||||
s_expectedInput = L"\x1b[1;mA";
|
||||
break;
|
||||
case VK_DOWN:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mB");
|
||||
s_expectedInput = L"\x1b[1;mB";
|
||||
break;
|
||||
case VK_RIGHT:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mC");
|
||||
s_expectedInput = L"\x1b[1;mC";
|
||||
break;
|
||||
case VK_LEFT:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mD");
|
||||
s_expectedInput = L"\x1b[1;mD";
|
||||
break;
|
||||
case VK_HOME:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mH");
|
||||
s_expectedInput = L"\x1b[1;mH";
|
||||
break;
|
||||
case VK_INSERT:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[2;m~");
|
||||
s_expectedInput = L"\x1b[2;m~";
|
||||
break;
|
||||
case VK_DELETE:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[3;m~");
|
||||
s_expectedInput = L"\x1b[3;m~";
|
||||
break;
|
||||
case VK_END:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mF");
|
||||
s_expectedInput = L"\x1b[1;mF";
|
||||
break;
|
||||
case VK_PRIOR:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[5;m~");
|
||||
s_expectedInput = L"\x1b[5;m~";
|
||||
break;
|
||||
case VK_NEXT:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[6;m~");
|
||||
s_expectedInput = L"\x1b[6;m~";
|
||||
break;
|
||||
case VK_F1:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mP");
|
||||
s_expectedInput = L"\x1b[1;mP";
|
||||
break;
|
||||
case VK_F2:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mQ");
|
||||
s_expectedInput = L"\x1b[1;mQ";
|
||||
break;
|
||||
case VK_F3:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mR");
|
||||
s_expectedInput = L"\x1b[1;mR";
|
||||
break;
|
||||
case VK_F4:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[1;mS");
|
||||
s_expectedInput = L"\x1b[1;mS";
|
||||
break;
|
||||
case VK_F5:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[15;m~");
|
||||
s_expectedInput = L"\x1b[15;m~";
|
||||
break;
|
||||
case VK_F6:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[17;m~");
|
||||
s_expectedInput = L"\x1b[17;m~";
|
||||
break;
|
||||
case VK_F7:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[18;m~");
|
||||
s_expectedInput = L"\x1b[18;m~";
|
||||
break;
|
||||
case VK_F8:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[19;m~");
|
||||
s_expectedInput = L"\x1b[19;m~";
|
||||
break;
|
||||
case VK_F9:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[20;m~");
|
||||
s_expectedInput = L"\x1b[20;m~";
|
||||
break;
|
||||
case VK_F10:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[21;m~");
|
||||
s_expectedInput = L"\x1b[21;m~";
|
||||
break;
|
||||
case VK_F11:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[23;m~");
|
||||
s_expectedInput = L"\x1b[23;m~";
|
||||
break;
|
||||
case VK_F12:
|
||||
fModifySequence = true;
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[24;m~");
|
||||
s_expectedInput = L"\x1b[24;m~";
|
||||
break;
|
||||
case VK_TAB:
|
||||
if (AltPressed(uiKeystate))
|
||||
|
@ -488,62 +469,64 @@ void InputTest::TerminalInputModifierKeyTests()
|
|||
}
|
||||
else if (ShiftPressed(uiKeystate))
|
||||
{
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1b[Z");
|
||||
fExpectedKeyHandled = true;
|
||||
s_expectedInput = L"\x1b[Z";
|
||||
}
|
||||
else if (ControlPressed(uiKeystate))
|
||||
{
|
||||
wcscpy_s(s_pwsInputBuffer, L"\t");
|
||||
fExpectedKeyHandled = true;
|
||||
s_expectedInput = L"\t";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Alt+Key generates [0x1b, key] into the stream
|
||||
if (AltPressed(uiKeystate) && (vkey > 0x40 && vkey <= 0x5A))
|
||||
wchar_t ch = irTest.Event.KeyEvent.uChar.UnicodeChar;
|
||||
|
||||
// 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 < 0x7F)
|
||||
{
|
||||
wcscpy_s(s_pwsInputBuffer, L"\x1bm");
|
||||
wchar_t wchShifted = vkey;
|
||||
// Alt + Ctrl + key generates [0x1b, control key] in the stream.
|
||||
if (ControlPressed(uiKeystate))
|
||||
{
|
||||
// Generally the control key is key-0x40
|
||||
wchShifted = vkey - 0x40;
|
||||
}
|
||||
s_pwsInputBuffer[1] = wchShifted;
|
||||
fExpectedKeyHandled = true;
|
||||
s_expectedInput.clear();
|
||||
s_expectedInput.push_back(L'\x1b');
|
||||
s_expectedInput.push_back(ch & 0b11111);
|
||||
break;
|
||||
}
|
||||
else if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9'))
|
||||
|
||||
// Alt+Key generates [0x1b, key] into the stream
|
||||
if (AltPressed(uiKeystate) && ch != 0)
|
||||
{
|
||||
s_expectedInput.clear();
|
||||
s_expectedInput.push_back(L'\x1b');
|
||||
s_expectedInput.push_back(ch);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9'))
|
||||
{
|
||||
// The C-# keys get translated into very specific control
|
||||
// characters that don't play nicely with this test. These keys
|
||||
// are tested in the CtrlNumTest Test instead.
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
if (ch != 0)
|
||||
{
|
||||
fExpectedKeyHandled = false;
|
||||
s_expectedInput.clear();
|
||||
s_expectedInput.push_back(irTest.Event.KeyEvent.uChar.UnicodeChar);
|
||||
break;
|
||||
}
|
||||
|
||||
fExpectedKeyHandled = false;
|
||||
break;
|
||||
}
|
||||
if (!fExpectedKeyHandled && ((vkey >= '0' && vkey <= 'Z') || vkey == VK_CANCEL))
|
||||
|
||||
if (fModifySequence && s_expectedInput.size() > 1)
|
||||
{
|
||||
fExpectedKeyHandled = true;
|
||||
bool fShift = !!(uiKeystate & SHIFT_PRESSED);
|
||||
bool fAlt = (uiKeystate & LEFT_ALT_PRESSED) || (uiKeystate & RIGHT_ALT_PRESSED);
|
||||
bool fCtrl = (uiKeystate & LEFT_CTRL_PRESSED) || (uiKeystate & RIGHT_CTRL_PRESSED);
|
||||
s_expectedInput[s_expectedInput.size() - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0);
|
||||
}
|
||||
|
||||
if (fModifySequence)
|
||||
{
|
||||
size_t cch = 0;
|
||||
VERIFY_SUCCEEDED(StringCchLengthW(s_pwsInputBuffer, 8, &cch));
|
||||
if (cch > 1)
|
||||
{
|
||||
bool fShift = !!(uiKeystate & SHIFT_PRESSED);
|
||||
bool fAlt = (uiKeystate & LEFT_ALT_PRESSED) || (uiKeystate & RIGHT_ALT_PRESSED);
|
||||
bool fCtrl = (uiKeystate & LEFT_CTRL_PRESSED) || (uiKeystate & RIGHT_CTRL_PRESSED);
|
||||
s_pwsInputBuffer[cch - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0);
|
||||
}
|
||||
}
|
||||
s_pwszInputExpected = s_pwsInputBuffer;
|
||||
Log::Comment(NoThrowString().Format(L"Expected, Buffer = \"%s\", \"%s\"", s_pwszInputExpected, s_pwsInputBuffer));
|
||||
Log::Comment(NoThrowString().Format(L"Expected = \"%s\"", s_expectedInput.c_str()));
|
||||
|
||||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
// Send key into object (will trigger callback and verification)
|
||||
|
@ -557,7 +540,7 @@ void InputTest::TerminalInputNullKeyTests()
|
|||
|
||||
unsigned int uiKeystate = LEFT_CTRL_PRESSED;
|
||||
|
||||
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestNullCallback);
|
||||
TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestNullCallback);
|
||||
|
||||
Log::Comment(L"Sending every possible VKEY at the input stream for interception during key DOWN.");
|
||||
|
||||
|
@ -593,16 +576,9 @@ void InputTest::TerminalInputNullKeyTests()
|
|||
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
|
||||
inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(true, pInput->HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
|
||||
uiKeystate = LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED;
|
||||
// This is AltGr, this ISN'T handled.
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
irTest.Event.KeyEvent.dwControlKeyState = uiKeystate;
|
||||
inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(false, pInput->HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
}
|
||||
|
||||
void TestKey(const TerminalInput* const pInput, const unsigned int uiKeystate, const BYTE vkey, const wchar_t wch)
|
||||
void TestKey(TerminalInput* const pInput, const unsigned int uiKeystate, const BYTE vkey, const wchar_t wch = 0)
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(L"Testing key, state =0x%x, 0x%x", vkey, uiKeystate));
|
||||
|
||||
|
@ -618,69 +594,64 @@ void TestKey(const TerminalInput* const pInput, const unsigned int uiKeystate, c
|
|||
auto inputEvent = IInputEvent::Create(irTest);
|
||||
VERIFY_ARE_EQUAL(true, pInput->HandleKey(inputEvent.get()), L"Verify key was handled if it should have been.");
|
||||
}
|
||||
void TestKey(const TerminalInput* const pInput, const unsigned int uiKeystate, const BYTE vkey)
|
||||
{
|
||||
// Callers of this version don't expect the wchar to matter.
|
||||
TestKey(pInput, uiKeystate, vkey, 0);
|
||||
}
|
||||
|
||||
void InputTest::DifferentModifiersTest()
|
||||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
|
||||
Log::Comment(L"Sending a bunch of keystrokes that are a little weird.");
|
||||
|
||||
unsigned int uiKeystate = 0;
|
||||
BYTE vkey = VK_BACK;
|
||||
s_pwszInputExpected = L"\x7f";
|
||||
s_expectedInput = L"\x7f";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
uiKeystate = LEFT_CTRL_PRESSED;
|
||||
vkey = VK_BACK;
|
||||
s_pwszInputExpected = L"\x8";
|
||||
s_expectedInput = L"\x8";
|
||||
TestKey(pInput, uiKeystate, vkey, L'\x8');
|
||||
uiKeystate = RIGHT_CTRL_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey, L'\x8');
|
||||
|
||||
uiKeystate = LEFT_ALT_PRESSED;
|
||||
vkey = VK_BACK;
|
||||
s_pwszInputExpected = L"\x1b\x7f";
|
||||
s_expectedInput = L"\x1b\x7f";
|
||||
TestKey(pInput, uiKeystate, vkey, L'\x8');
|
||||
uiKeystate = RIGHT_ALT_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey, L'\x8');
|
||||
|
||||
uiKeystate = LEFT_CTRL_PRESSED;
|
||||
vkey = VK_DELETE;
|
||||
s_pwszInputExpected = L"\x1b[3;5~";
|
||||
s_expectedInput = L"\x1b[3;5~";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
uiKeystate = RIGHT_CTRL_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
uiKeystate = LEFT_ALT_PRESSED;
|
||||
vkey = VK_DELETE;
|
||||
s_pwszInputExpected = L"\x1b[3;3~";
|
||||
s_expectedInput = L"\x1b[3;3~";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
uiKeystate = RIGHT_ALT_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
uiKeystate = LEFT_CTRL_PRESSED;
|
||||
vkey = VK_TAB;
|
||||
s_pwszInputExpected = L"\t";
|
||||
s_expectedInput = L"\t";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
uiKeystate = RIGHT_CTRL_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
uiKeystate = SHIFT_PRESSED;
|
||||
vkey = VK_TAB;
|
||||
s_pwszInputExpected = L"\x1b[Z";
|
||||
s_expectedInput = L"\x1b[Z";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
// C-/ -> C-_ -> 0x1f
|
||||
uiKeystate = LEFT_CTRL_PRESSED;
|
||||
vkey = LOBYTE(VkKeyScan(L'/'));
|
||||
s_pwszInputExpected = L"\x1f";
|
||||
s_expectedInput = L"\x1f";
|
||||
TestKey(pInput, uiKeystate, vkey, L'/');
|
||||
uiKeystate = RIGHT_CTRL_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey, L'/');
|
||||
|
@ -688,7 +659,7 @@ void InputTest::DifferentModifiersTest()
|
|||
// M-/ -> ESC /
|
||||
uiKeystate = LEFT_ALT_PRESSED;
|
||||
vkey = LOBYTE(VkKeyScan(L'/'));
|
||||
s_pwszInputExpected = L"\x1b/";
|
||||
s_expectedInput = L"\x1b/";
|
||||
TestKey(pInput, uiKeystate, vkey, L'/');
|
||||
uiKeystate = RIGHT_ALT_PRESSED;
|
||||
TestKey(pInput, uiKeystate, vkey, L'/');
|
||||
|
@ -698,7 +669,7 @@ void InputTest::DifferentModifiersTest()
|
|||
Log::Comment(NoThrowString().Format(L"Checking C-?"));
|
||||
// Use SHIFT_PRESSED to force us into differentiating between '/' and '?'
|
||||
vkey = LOBYTE(VkKeyScan(L'?'));
|
||||
s_pwszInputExpected = L"\x7f";
|
||||
s_expectedInput = L"\x7f";
|
||||
TestKey(pInput, SHIFT_PRESSED | LEFT_CTRL_PRESSED, vkey, L'?');
|
||||
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED, vkey, L'?');
|
||||
|
||||
|
@ -706,7 +677,7 @@ void InputTest::DifferentModifiersTest()
|
|||
Log::Comment(NoThrowString().Format(L"Checking C-M-/"));
|
||||
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
|
||||
vkey = LOBYTE(VkKeyScan(L'/'));
|
||||
s_pwszInputExpected = L"\x1b\x1f";
|
||||
s_expectedInput = L"\x1b\x1f";
|
||||
TestKey(pInput, LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/');
|
||||
TestKey(pInput, RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/');
|
||||
// LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr
|
||||
|
@ -716,7 +687,7 @@ void InputTest::DifferentModifiersTest()
|
|||
Log::Comment(NoThrowString().Format(L"Checking C-M-?"));
|
||||
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
|
||||
vkey = LOBYTE(VkKeyScan(L'?'));
|
||||
s_pwszInputExpected = L"\x1b\x7f";
|
||||
s_expectedInput = L"\x1b\x7f";
|
||||
TestKey(pInput, SHIFT_PRESSED | LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?');
|
||||
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?');
|
||||
// LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr
|
||||
|
@ -727,13 +698,13 @@ void InputTest::CtrlNumTest()
|
|||
{
|
||||
Log::Comment(L"Starting test...");
|
||||
|
||||
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
|
||||
|
||||
Log::Comment(L"Sending the various Ctrl+Num keys.");
|
||||
|
||||
unsigned int uiKeystate = LEFT_CTRL_PRESSED;
|
||||
BYTE vkey = static_cast<WORD>('1');
|
||||
s_pwszInputExpected = L"1";
|
||||
s_expectedInput = L"1";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
|
@ -741,30 +712,30 @@ void InputTest::CtrlNumTest()
|
|||
L"nicely with this test. Ctrl+2 is covered by other tests in this class."));
|
||||
|
||||
vkey = static_cast<WORD>('3');
|
||||
s_pwszInputExpected = L"\x1b";
|
||||
s_expectedInput = L"\x1b";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
vkey = static_cast<WORD>('4');
|
||||
s_pwszInputExpected = L"\x1c";
|
||||
s_expectedInput = L"\x1c";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
vkey = static_cast<WORD>('5');
|
||||
s_pwszInputExpected = L"\x1d";
|
||||
s_expectedInput = L"\x1d";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
vkey = static_cast<WORD>('6');
|
||||
s_pwszInputExpected = L"\x1e";
|
||||
s_expectedInput = L"\x1e";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
vkey = static_cast<WORD>('7');
|
||||
s_pwszInputExpected = L"\x1f";
|
||||
s_expectedInput = L"\x1f";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
vkey = static_cast<WORD>('8');
|
||||
s_pwszInputExpected = L"\x7f";
|
||||
s_expectedInput = L"\x7f";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
|
||||
vkey = static_cast<WORD>('9');
|
||||
s_pwszInputExpected = L"9";
|
||||
s_expectedInput = L"9";
|
||||
TestKey(pInput, uiKeystate, vkey);
|
||||
}
|
||||
|
|
|
@ -440,120 +440,150 @@ static bool _translateDefaultMapping(const KeyEvent& keyEvent,
|
|||
return match.has_value();
|
||||
}
|
||||
|
||||
bool TerminalInput::HandleKey(const IInputEvent* const pInEvent) const
|
||||
// Routine Description:
|
||||
// - Sends the given input event to the shell.
|
||||
// - The caller should attempt to fill the char data in pInEvent if possible.
|
||||
// The char data should already be translated in accordance to Ctrl/Alt/Shift
|
||||
// modifiers, like the characters given by the WM_CHAR event.
|
||||
// - The caller doesn't need to fill in any char data for:
|
||||
// - Tab key
|
||||
// - Alt+key combinations
|
||||
// - This method will alias Ctrl+Space as a synonym for Ctrl+@ - the null byte.
|
||||
// Arguments:
|
||||
// - keyEvent - Key event to translate
|
||||
// Return Value:
|
||||
// - True if the event was handled.
|
||||
bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
|
||||
{
|
||||
// By default, we fail to handle the key
|
||||
bool keyHandled = false;
|
||||
|
||||
// On key presses, prepare to translate to VT compatible sequences
|
||||
if (pInEvent->EventType() == InputEventType::KeyEvent)
|
||||
{
|
||||
const auto senderFunc = [this](const std::wstring_view seq) noexcept { _SendInputSequence(seq); };
|
||||
|
||||
auto keyEvent = *static_cast<const KeyEvent* const>(pInEvent);
|
||||
|
||||
// Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp)
|
||||
if (keyEvent.IsKeyDown())
|
||||
{
|
||||
// For AltGr enabled keyboards, the Windows system will
|
||||
// emit Left Ctrl + Right Alt as the modifier keys and
|
||||
// will have pretranslated the UnicodeChar to the proper
|
||||
// alternative value.
|
||||
// Through testing with Ubuntu, PuTTY, and Emacs for
|
||||
// Windows, it was discovered that any instance of Left
|
||||
// Ctrl + Right Alt will strip out those two modifiers and
|
||||
// send the unicode value straight through to the system.
|
||||
// Holding additional modifiers in addition to Left Ctrl +
|
||||
// Right Alt will then light those modifiers up again for
|
||||
// the unicode value.
|
||||
// Therefore to handle AltGr properly, our first step
|
||||
// needs to be to check if both Left Ctrl + Right Alt are
|
||||
// pressed...
|
||||
// ... and if they are both pressed, strip them out of the control key state.
|
||||
if (keyEvent.IsAltGrPressed())
|
||||
{
|
||||
keyEvent.DeactivateModifierKey(ModifierKeyState::LeftCtrl);
|
||||
keyEvent.DeactivateModifierKey(ModifierKeyState::RightAlt);
|
||||
}
|
||||
|
||||
if (keyEvent.IsAltPressed() &&
|
||||
keyEvent.IsCtrlPressed() &&
|
||||
(keyEvent.GetCharData() == 0 || keyEvent.GetCharData() == 0x20) &&
|
||||
((keyEvent.GetVirtualKeyCode() > 0x40 && keyEvent.GetVirtualKeyCode() <= 0x5A) ||
|
||||
keyEvent.GetVirtualKeyCode() == VK_SPACE))
|
||||
{
|
||||
// For Alt+Ctrl+Key messages, the UnicodeChar is NOT the Ctrl+key char, it's null.
|
||||
// So we need to get the char from the vKey.
|
||||
// EXCEPT for Alt+Ctrl+Space. Then the UnicodeChar is space, not NUL.
|
||||
auto wchPressedChar = gsl::narrow_cast<wchar_t>(MapVirtualKeyW(keyEvent.GetVirtualKeyCode(), MAPVK_VK_TO_CHAR));
|
||||
// This is a trick - C-Spc is supposed to send NUL. So quick change space -> @ (0x40)
|
||||
wchPressedChar = (wchPressedChar == UNICODE_SPACE) ? 0x40 : wchPressedChar;
|
||||
if (wchPressedChar >= 0x40 && wchPressedChar < 0x7F)
|
||||
{
|
||||
//shift the char to the ctrl range
|
||||
wchPressedChar -= 0x40;
|
||||
_SendEscapedInputSequence(wchPressedChar);
|
||||
keyHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If a modifier key was pressed, then we need to try and send the modified sequence.
|
||||
if (!keyHandled && keyEvent.IsModifierPressed())
|
||||
{
|
||||
// Translate the key using the modifier table
|
||||
keyHandled = _searchWithModifier(keyEvent, senderFunc);
|
||||
}
|
||||
// ALT is a sequence of ESC + KEY.
|
||||
if (!keyHandled && keyEvent.GetCharData() != 0 && keyEvent.IsAltPressed())
|
||||
{
|
||||
_SendEscapedInputSequence(keyEvent.GetCharData());
|
||||
keyHandled = true;
|
||||
}
|
||||
if (!keyHandled && keyEvent.IsCtrlPressed())
|
||||
{
|
||||
if ((keyEvent.GetCharData() == UNICODE_SPACE) || // Ctrl+Space
|
||||
// when Ctrl+@ comes through, the unicodechar
|
||||
// will be '\x0' (UNICODE_NULL), and the vkey will be
|
||||
// VkKeyScanW(0), the vkey for null
|
||||
(keyEvent.GetCharData() == UNICODE_NULL && keyEvent.GetVirtualKeyCode() == LOBYTE(VkKeyScanW(0))))
|
||||
{
|
||||
_SendNullInputSequence(keyEvent.GetActiveModifierKeys());
|
||||
keyHandled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyHandled)
|
||||
{
|
||||
// For perf optimization, filter out any typically printable Virtual Keys (e.g. A-Z)
|
||||
// This is in lieu of an O(1) sparse table or other such less-maintainable methods.
|
||||
// VK_CANCEL is an exception and we want to send the associated uChar as is.
|
||||
if ((keyEvent.GetVirtualKeyCode() < '0' || keyEvent.GetVirtualKeyCode() > 'Z') &&
|
||||
keyEvent.GetVirtualKeyCode() != VK_CANCEL)
|
||||
{
|
||||
keyHandled = _translateDefaultMapping(keyEvent, _getKeyMapping(keyEvent, _cursorApplicationMode, _keypadApplicationMode), senderFunc);
|
||||
}
|
||||
else
|
||||
{
|
||||
WCHAR rgwchSequence[2];
|
||||
rgwchSequence[0] = keyEvent.GetCharData();
|
||||
rgwchSequence[1] = UNICODE_NULL;
|
||||
_SendInputSequence(rgwchSequence);
|
||||
keyHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keyHandled;
|
||||
}
|
||||
|
||||
bool TerminalInput::HandleChar(const wchar_t ch)
|
||||
{
|
||||
if (ch == UNICODE_BACKSPACE || ch == UNICODE_DEL)
|
||||
if (!pInEvent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (Utf16Parser::IsLeadingSurrogate(ch))
|
||||
|
||||
// On key presses, prepare to translate to VT compatible sequences
|
||||
if (pInEvent->EventType() != InputEventType::KeyEvent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto keyEvent = *static_cast<const KeyEvent* const>(pInEvent);
|
||||
|
||||
// Only need to handle key down. See raw key handler (see RawReadWaitRoutine in stream.cpp)
|
||||
if (!keyEvent.IsKeyDown())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Many keyboard layouts have an AltGr key, which makes widely used characters accessible.
|
||||
// For instance on a German keyboard layout "[" is written by pressing AltGr+8.
|
||||
// Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows.
|
||||
// When AltGr is pressed, the caller needs to make sure to send us a pretranslated character in GetCharData().
|
||||
// --> Strip out the AltGr flags, in order for us to not step into the Alt/Ctrl conditions below.
|
||||
if (keyEvent.IsAltGrPressed())
|
||||
{
|
||||
keyEvent.DeactivateModifierKey(ModifierKeyState::LeftCtrl);
|
||||
keyEvent.DeactivateModifierKey(ModifierKeyState::RightAlt);
|
||||
}
|
||||
|
||||
// The Alt modifier initiates a so called "escape sequence".
|
||||
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences
|
||||
// See: ECMA-48, section 5.3, http://www.ecma-international.org/publications/standards/Ecma-048.htm
|
||||
//
|
||||
// This section in particular handles Alt+Ctrl combinations though.
|
||||
// The Ctrl modifier causes all of the char code's bits except
|
||||
// for the 5 least significant ones to be zeroed out.
|
||||
if (keyEvent.IsAltPressed() && keyEvent.IsCtrlPressed())
|
||||
{
|
||||
auto ch = keyEvent.GetCharData();
|
||||
if (ch == UNICODE_NULL)
|
||||
{
|
||||
// For Alt+Ctrl+Key messages GetCharData() returns 0.
|
||||
// -> Get the char from the virtual key.
|
||||
ch = LOWORD(MapVirtualKeyW(keyEvent.GetVirtualKeyCode(), MAPVK_VK_TO_CHAR));
|
||||
}
|
||||
if (ch == UNICODE_SPACE)
|
||||
{
|
||||
// Ctrl+@ and Ctrl+Space are supposed to send null bytes.
|
||||
// -> Change Ctrl+Space to Ctrl+@ for compatibility reasons.
|
||||
ch = 0x40;
|
||||
}
|
||||
if (ch >= 0x40 && ch < 0x7F)
|
||||
{
|
||||
// Pressing the control key causes all bits but the 5 least
|
||||
// significant ones to be zeroed out (when using ASCII).
|
||||
ch &= 0b11111;
|
||||
_SendEscapedInputSequence(ch);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto senderFunc = [this](const std::wstring_view seq) noexcept
|
||||
{
|
||||
_SendInputSequence(seq);
|
||||
};
|
||||
|
||||
// If a modifier key was pressed, then we need to try and send the modified sequence.
|
||||
if (keyEvent.IsModifierPressed() && _searchWithModifier(keyEvent, senderFunc))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// This section is similar to the Alt modifier section above,
|
||||
// but handles cases without Ctrl modifiers.
|
||||
if (keyEvent.IsAltPressed() && keyEvent.GetCharData() != 0)
|
||||
{
|
||||
_SendEscapedInputSequence(keyEvent.GetCharData());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pressing the control key causes all bits but the 5 least
|
||||
// significant ones to be zeroed out (when using ASCII).
|
||||
// This results in Ctrl+Space and Ctrl+@ being equal to a null byte.
|
||||
// Normally the C0 control code set only defines Ctrl+@,
|
||||
// but Ctrl+Space is also widely accepted by most terminals.
|
||||
// -> Send a "null input sequence" in that case.
|
||||
// We don't need to handle other kinds of Ctrl combinations,
|
||||
// as we rely on the caller to pretranslate those to characters for us.
|
||||
if (keyEvent.IsCtrlPressed())
|
||||
{
|
||||
const auto ch = keyEvent.GetCharData();
|
||||
const auto vkey = keyEvent.GetVirtualKeyCode();
|
||||
|
||||
// Currently, when we're called with Ctrl+@, ch will be 0, since Ctrl+@ equals a null byte.
|
||||
// VkKeyScanW(0) in turn returns the vkey for the null character (ASCII @).
|
||||
// -> Use the vkey to alternatively determine if Ctrl+@ is being pressed.
|
||||
if (ch == UNICODE_SPACE || (ch == UNICODE_NULL && vkey == LOBYTE(VkKeyScanW(0))))
|
||||
{
|
||||
_SendNullInputSequence(keyEvent.GetActiveModifierKeys());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check any other key mappings (like those for the F1-F12 keys).
|
||||
const auto mapping = _getKeyMapping(keyEvent, _cursorApplicationMode, _keypadApplicationMode);
|
||||
if (_translateDefaultMapping(keyEvent, mapping, senderFunc))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If all else fails we can finally try to send the character itself if there is any.
|
||||
if (keyEvent.GetCharData() != 0)
|
||||
{
|
||||
_SendChar(keyEvent.GetCharData());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sends the given character to the shell.
|
||||
// - Surrogate pairs are being aggregated by this function before being sent.
|
||||
// Arguments:
|
||||
// - ch: The UTF-16 character to send.
|
||||
void TerminalInput::_SendChar(const wchar_t ch)
|
||||
{
|
||||
if (Utf16Parser::IsLeadingSurrogate(ch))
|
||||
{
|
||||
if (_leadingSurrogate.has_value())
|
||||
{
|
||||
|
@ -567,20 +597,14 @@ bool TerminalInput::HandleChar(const wchar_t ch)
|
|||
}
|
||||
else if (_leadingSurrogate.has_value())
|
||||
{
|
||||
std::wstring wstr;
|
||||
wstr.reserve(2);
|
||||
wstr.push_back(_leadingSurrogate.value());
|
||||
wstr.push_back(ch);
|
||||
std::array<wchar_t, 2> wstr{ { _leadingSurrogate.value(), ch } };
|
||||
_leadingSurrogate.reset();
|
||||
|
||||
_SendInputSequence(wstr);
|
||||
_SendInputSequence({ wstr.data(), wstr.size() });
|
||||
}
|
||||
else
|
||||
{
|
||||
_SendInputSequence({ &ch, 1 });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -33,8 +33,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
|
||||
~TerminalInput() = default;
|
||||
|
||||
bool HandleKey(const IInputEvent* const pInEvent) const;
|
||||
bool HandleChar(const wchar_t ch);
|
||||
bool HandleKey(const IInputEvent* const pInEvent);
|
||||
void ChangeKeypadMode(const bool applicationMode) noexcept;
|
||||
void ChangeCursorKeysMode(const bool applicationMode) noexcept;
|
||||
|
||||
|
@ -71,6 +70,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
bool _keypadApplicationMode = false;
|
||||
bool _cursorApplicationMode = false;
|
||||
|
||||
void _SendChar(const wchar_t ch);
|
||||
void _SendNullInputSequence(const DWORD dwControlKeyState) const;
|
||||
void _SendInputSequence(const std::wstring_view sequence) const noexcept;
|
||||
void _SendEscapedInputSequence(const wchar_t wch) const;
|
||||
|
|
Loading…
Reference in a new issue