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:
Leonard Hecker 2020-04-07 21:09:28 +02:00 committed by GitHub
parent 52d6c03e64
commit a9c9714295
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 449 additions and 352 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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:

View file

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