d4d59fa339
This commit introduces all of the Windows Terminal and Console Host source, under the MIT license.
554 lines
23 KiB
C++
554 lines
23 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include <windows.h>
|
|
#include "terminalInput.hpp"
|
|
|
|
#include "strsafe.h"
|
|
|
|
#define WIL_SUPPORT_BITOPERATION_PASCAL_NAMES
|
|
#include <wil\Common.h>
|
|
|
|
#ifdef BUILD_ONECORE_INTERACTIVITY
|
|
#include "..\..\interactivity\inc\VtApiRedirection.hpp"
|
|
#endif
|
|
|
|
#include "..\..\inc\unicode.hpp"
|
|
|
|
using namespace Microsoft::Console::VirtualTerminal;
|
|
|
|
DWORD const dwAltGrFlags = LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED;
|
|
|
|
TerminalInput::TerminalInput(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn)
|
|
{
|
|
_pfnWriteEvents = pfn;
|
|
}
|
|
|
|
TerminalInput::~TerminalInput()
|
|
{
|
|
|
|
}
|
|
|
|
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
|
|
// For the source for these tables.
|
|
// Also refer to the values in terminfo for kcub1, kcud1, kcuf1, kcuu1, kend, khome.
|
|
// the 'xterm' setting lists the application mode versions of these sequences.
|
|
const TerminalInput::_TermKeyMap TerminalInput::s_rgCursorKeysNormalMapping[]
|
|
{
|
|
{ VK_UP, L"\x1b[A" },
|
|
{ VK_DOWN, L"\x1b[B" },
|
|
{ VK_RIGHT, L"\x1b[C" },
|
|
{ VK_LEFT, L"\x1b[D" },
|
|
{ VK_HOME, L"\x1b[H" },
|
|
{ VK_END, L"\x1b[F" },
|
|
};
|
|
|
|
const TerminalInput::_TermKeyMap TerminalInput::s_rgCursorKeysApplicationMapping[]
|
|
{
|
|
{ VK_UP, L"\x1bOA" },
|
|
{ VK_DOWN, L"\x1bOB" },
|
|
{ VK_RIGHT, L"\x1bOC" },
|
|
{ VK_LEFT, L"\x1bOD" },
|
|
{ VK_HOME, L"\x1bOH" },
|
|
{ VK_END, L"\x1bOF" },
|
|
};
|
|
|
|
const TerminalInput::_TermKeyMap TerminalInput::s_rgKeypadNumericMapping[]
|
|
{
|
|
// HEY YOU. UPDATE THE MAX LENGTH DEF WHEN YOU MAKE CHANGES HERE.
|
|
{ VK_TAB, L"\x09"},
|
|
{ VK_BACK, L"\x7f"},
|
|
{ VK_PAUSE, L"\x1a" },
|
|
{ VK_ESCAPE, L"\x1b" },
|
|
{ VK_INSERT, L"\x1b[2~" },
|
|
{ VK_DELETE, L"\x1b[3~" },
|
|
{ VK_PRIOR, L"\x1b[5~" },
|
|
{ VK_NEXT, L"\x1b[6~" },
|
|
{ VK_F1, L"\x1bOP" }, // also \x1b[11~, PuTTY uses \x1b\x1b[A
|
|
{ VK_F2, L"\x1bOQ" }, // also \x1b[12~, PuTTY uses \x1b\x1b[B
|
|
{ VK_F3, L"\x1bOR" }, // also \x1b[13~, PuTTY uses \x1b\x1b[C
|
|
{ VK_F4, L"\x1bOS" }, // also \x1b[14~, PuTTY uses \x1b\x1b[D
|
|
{ VK_F5, L"\x1b[15~" },
|
|
{ VK_F6, L"\x1b[17~" },
|
|
{ VK_F7, L"\x1b[18~" },
|
|
{ VK_F8, L"\x1b[19~" },
|
|
{ VK_F9, L"\x1b[20~" },
|
|
{ VK_F10, L"\x1b[21~" },
|
|
{ VK_F11, L"\x1b[23~" },
|
|
{ VK_F12, L"\x1b[24~" },
|
|
};
|
|
|
|
//Application mode - Some terminals support both a "Numeric" input mode, and an "Application" mode
|
|
// The standards vary on what each key translates to in the various modes, so I tried to make it as close
|
|
// to the VT220 standard as possible.
|
|
// The notable difference is in the arrow keys, which in application mode translate to "^[0A" (etc) as opposed to "^[[A" in numeric
|
|
//Some very unclear documentation at http://invisible-island.net/xterm/ctlseqs/ctlseqs.html also suggests alternate encodings for F1-4
|
|
// which I have left in the comments on those entries as something to possibly add in the future, if need be.
|
|
//It seems to me as though this was used for early numpad implementations, where presently numlock would enable
|
|
// "numeric" mode, outputting the numbers on the keys, while "application" mode does things like pgup/down, arrow keys, etc.
|
|
//These keys aren't translated at all in numeric mode, so I figured I'd leave them out of the numeric table.
|
|
const TerminalInput::_TermKeyMap TerminalInput::s_rgKeypadApplicationMapping[]
|
|
{
|
|
// HEY YOU. UPDATE THE MAX LENGTH DEF WHEN YOU MAKE CHANGES HERE.
|
|
{ VK_TAB, L"\x09" },
|
|
{ VK_BACK, L"\x7f" },
|
|
{ VK_PAUSE, L"\x1a" },
|
|
{ VK_ESCAPE, L"\x1b" },
|
|
{ VK_INSERT, L"\x1b[2~" },
|
|
{ VK_DELETE, L"\x1b[3~" },
|
|
{ VK_PRIOR, L"\x1b[5~" },
|
|
{ VK_NEXT, L"\x1b[6~" },
|
|
{ VK_F1, L"\x1bOP" }, // also \x1b[11~, PuTTY uses \x1b\x1b[A
|
|
{ VK_F2, L"\x1bOQ" }, // also \x1b[12~, PuTTY uses \x1b\x1b[B
|
|
{ VK_F3, L"\x1bOR" }, // also \x1b[13~, PuTTY uses \x1b\x1b[C
|
|
{ VK_F4, L"\x1bOS" }, // also \x1b[14~, PuTTY uses \x1b\x1b[D
|
|
{ VK_F5, L"\x1b[15~" },
|
|
{ VK_F6, L"\x1b[17~" },
|
|
{ VK_F7, L"\x1b[18~" },
|
|
{ VK_F8, L"\x1b[19~" },
|
|
{ VK_F9, L"\x1b[20~" },
|
|
{ VK_F10, L"\x1b[21~" },
|
|
{ VK_F11, L"\x1b[23~" },
|
|
{ VK_F12, L"\x1b[24~" },
|
|
// The numpad has a variety of mappings, none of which seem standard or really configurable by the OS.
|
|
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
|
|
// to see just how convoluted this all is.
|
|
// PuTTY uses a set of mappings that don't work in ViM without reamapping them back to the numpad
|
|
// (see http://vim.wikia.com/wiki/PuTTY_numeric_keypad_mappings#Comments)
|
|
// I think the best solution is to just not do any for the time being.
|
|
// Putty also provides configuration for choosing which of the 5 mappings it has through the settings, which is more work than we can manage now.
|
|
// { VK_MULTIPLY, L"\x1bOj" }, // PuTTY: \x1bOR (I believe putty is treating the top row of the numpad as PF1-PF4)
|
|
// { VK_ADD, L"\x1bOk" }, // PuTTY: \x1bOl, \x1bOm (with shift)
|
|
// { VK_SEPARATOR, L"\x1bOl" }, // ? I'm not sure which key this is...
|
|
// { VK_SUBTRACT, L"\x1bOm" }, // \x1bOS
|
|
// { VK_DECIMAL, L"\x1bOn" }, // \x1bOn
|
|
// { VK_DIVIDE, L"\x1bOo" }, // \x1bOQ
|
|
// { VK_NUMPAD0, L"\x1bOp" },
|
|
// { VK_NUMPAD1, L"\x1bOq" },
|
|
// { VK_NUMPAD2, L"\x1bOr" },
|
|
// { VK_NUMPAD3, L"\x1bOs" },
|
|
// { VK_NUMPAD4, L"\x1bOt" },
|
|
// { VK_NUMPAD5, L"\x1bOu" }, // \x1b0E
|
|
// { VK_NUMPAD5, L"\x1bOE" }, // PuTTY \x1b[G
|
|
// { VK_NUMPAD6, L"\x1bOv" },
|
|
// { VK_NUMPAD7, L"\x1bOw" },
|
|
// { VK_NUMPAD8, L"\x1bOx" },
|
|
// { VK_NUMPAD9, L"\x1bOy" },
|
|
// { '=', L"\x1bOX" }, // I've also seen these codes mentioned in some documentation,
|
|
// { VK_SPACE, L"\x1bO " }, // but I wasn't really sure if they should be included or not...
|
|
// { VK_TAB, L"\x1bOI" }, // So I left them here as a reference just in case.
|
|
};
|
|
|
|
// Sequences to send when a modifier is pressed with any of these keys
|
|
// Basically, the 'm' will be replaced with a character indicating which
|
|
// modifier keys are pressed.
|
|
const TerminalInput::_TermKeyMap TerminalInput::s_rgModifierKeyMapping[]
|
|
{
|
|
// HEY YOU. UPDATE THE MAX LENGTH DEF WHEN YOU MAKE CHANGES HERE.
|
|
{ VK_UP, L"\x1b[1;mA" },
|
|
{ VK_DOWN, L"\x1b[1;mB" },
|
|
{ VK_RIGHT, L"\x1b[1;mC" },
|
|
{ VK_LEFT, L"\x1b[1;mD" },
|
|
{ VK_HOME, L"\x1b[1;mH" },
|
|
{ VK_END, L"\x1b[1;mF" },
|
|
{ VK_F1, L"\x1b[1;mP" },
|
|
{ VK_F2, L"\x1b[1;mQ" },
|
|
{ VK_F3, L"\x1b[1;mR" },
|
|
{ VK_F4, L"\x1b[1;mS" },
|
|
{ VK_INSERT, L"\x1b[2;m~" },
|
|
{ VK_DELETE, L"\x1b[3;m~" },
|
|
{ VK_PRIOR, L"\x1b[5;m~" },
|
|
{ VK_NEXT, L"\x1b[6;m~" },
|
|
{ VK_F5, L"\x1b[15;m~" },
|
|
{ VK_F6, L"\x1b[17;m~" },
|
|
{ VK_F7, L"\x1b[18;m~" },
|
|
{ VK_F8, L"\x1b[19;m~" },
|
|
{ VK_F9, L"\x1b[20;m~" },
|
|
{ VK_F10, L"\x1b[21;m~" },
|
|
{ VK_F11, L"\x1b[23;m~" },
|
|
{ VK_F12, L"\x1b[24;m~" },
|
|
// Ubuntu's inputrc also defines \x1b[5C, \x1b\x1bC (and D) as 'forward/backward-word' mappings
|
|
// I believe '\x1b\x1bC' is listed because the C1 ESC (x9B) gets encoded as
|
|
// \xC2\x9B, but then translated to \x1b\x1b if the C1 codepoint isn't supported by the current encoding
|
|
};
|
|
|
|
// Sequences to send when a modifier is pressed with any of these keys
|
|
// These sequences are not later updated to encode the modifier state in the
|
|
// sequence itself, they are just weird exceptional cases to the general
|
|
// rules above.
|
|
const TerminalInput::_TermKeyMap TerminalInput::s_rgSimpleModifedKeyMapping[]
|
|
{
|
|
// HEY YOU. UPDATE THE MAX LENGTH DEF WHEN YOU MAKE CHANGES HERE.
|
|
{ VK_BACK, CTRL_PRESSED, L"\x8"},
|
|
{ VK_BACK, ALT_PRESSED, L"\x1b\x7f"},
|
|
{ VK_BACK, CTRL_PRESSED | ALT_PRESSED, L"\x1b\x8"},
|
|
{ VK_TAB, CTRL_PRESSED, L"\t"},
|
|
{ VK_TAB, SHIFT_PRESSED, L"\x1b[Z"},
|
|
{ VK_DIVIDE, CTRL_PRESSED, L"\x1F"},
|
|
// These two are not implemented here, because they are system keys.
|
|
// { VK_TAB, ALT_PRESSED, L""}, This is the Windows system shortcut for switching windows.
|
|
// { VK_ESCAPE, ALT_PRESSED, L""}, This is another Windows system shortcut for switching windows.
|
|
};
|
|
|
|
const wchar_t* const CTRL_SLASH_SEQUENCE = L"\x1f";
|
|
|
|
// Do NOT include the null terminator in the count.
|
|
const size_t TerminalInput::_TermKeyMap::s_cchMaxSequenceLength = 7; // UPDATE THIS DEF WHEN THE LONGEST MAPPED STRING CHANGES
|
|
|
|
const size_t TerminalInput::s_cCursorKeysNormalMapping = ARRAYSIZE(s_rgCursorKeysNormalMapping);
|
|
const size_t TerminalInput::s_cCursorKeysApplicationMapping = ARRAYSIZE(s_rgCursorKeysApplicationMapping);
|
|
const size_t TerminalInput::s_cKeypadNumericMapping = ARRAYSIZE(s_rgKeypadNumericMapping);
|
|
const size_t TerminalInput::s_cKeypadApplicationMapping = ARRAYSIZE(s_rgKeypadApplicationMapping);
|
|
const size_t TerminalInput::s_cModifierKeyMapping = ARRAYSIZE(s_rgModifierKeyMapping);
|
|
const size_t TerminalInput::s_cSimpleModifedKeyMapping = ARRAYSIZE(s_rgSimpleModifedKeyMapping);
|
|
|
|
void TerminalInput::ChangeKeypadMode(const bool fApplicationMode)
|
|
{
|
|
_fKeypadApplicationMode = fApplicationMode;
|
|
}
|
|
|
|
void TerminalInput::ChangeCursorKeysMode(const bool fApplicationMode)
|
|
{
|
|
_fCursorApplicationMode = fApplicationMode;
|
|
}
|
|
|
|
const size_t TerminalInput::GetKeyMappingLength(const KeyEvent& keyEvent) const
|
|
{
|
|
size_t length = 0;
|
|
if (keyEvent.IsCursorKey())
|
|
{
|
|
length = (_fCursorApplicationMode) ? s_cCursorKeysApplicationMapping : s_cCursorKeysNormalMapping;
|
|
}
|
|
else
|
|
{
|
|
length = (_fKeypadApplicationMode) ? s_cKeypadApplicationMapping : s_cKeypadNumericMapping;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
const TerminalInput::_TermKeyMap* TerminalInput::GetKeyMapping(const KeyEvent& keyEvent) const
|
|
{
|
|
const TerminalInput::_TermKeyMap* mapping = nullptr;
|
|
|
|
if (keyEvent.IsCursorKey())
|
|
{
|
|
mapping = (_fCursorApplicationMode) ? s_rgCursorKeysApplicationMapping : s_rgCursorKeysNormalMapping;
|
|
}
|
|
else
|
|
{
|
|
mapping = (_fKeypadApplicationMode) ? s_rgKeypadApplicationMapping : s_rgKeypadNumericMapping;
|
|
}
|
|
return mapping;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Searches the s_ModifierKeyMapping for a entry corresponding to this key event.
|
|
// Changes the second to last byte to correspond to the currently pressed modifier keys
|
|
// before sending to the input.
|
|
// Arguments:
|
|
// - keyEvent - Key event to translate
|
|
// Return Value:
|
|
// - True if there was a match to a key translation, and we successfully modified and sent it to the input
|
|
bool TerminalInput::_SearchWithModifier(const KeyEvent& keyEvent) const
|
|
{
|
|
|
|
const TerminalInput::_TermKeyMap* pMatchingMapping;
|
|
bool fSuccess = _SearchKeyMapping(keyEvent,
|
|
s_rgModifierKeyMapping,
|
|
s_cModifierKeyMapping,
|
|
&pMatchingMapping);
|
|
if (fSuccess)
|
|
{
|
|
size_t cch = 0;
|
|
if (SUCCEEDED(StringCchLengthW(pMatchingMapping->pwszSequence, _TermKeyMap::s_cchMaxSequenceLength + 1, &cch)) &&
|
|
cch > 0)
|
|
{
|
|
wchar_t* rwchModifiedSequence = new(std::nothrow) wchar_t[cch + 1];
|
|
if (rwchModifiedSequence != nullptr)
|
|
{
|
|
memcpy(rwchModifiedSequence, pMatchingMapping->pwszSequence, cch * sizeof(wchar_t));
|
|
const bool fShift = keyEvent.IsShiftPressed();
|
|
const bool fAlt = keyEvent.IsAltPressed();
|
|
const bool fCtrl = keyEvent.IsCtrlPressed();
|
|
rwchModifiedSequence[cch - 2] = L'1' + (fShift ? 1 : 0) + (fAlt ? 2 : 0) + (fCtrl ? 4 : 0);
|
|
rwchModifiedSequence[cch] = 0;
|
|
_SendInputSequence(rwchModifiedSequence);
|
|
fSuccess = true;
|
|
delete [] rwchModifiedSequence;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We didn't find the key in the map of modified keys that need editing,
|
|
// maybe it's in the other map of modified keys with sequences that
|
|
// don't need editing before sending.
|
|
fSuccess = _SearchKeyMapping(keyEvent,
|
|
s_rgSimpleModifedKeyMapping,
|
|
s_cSimpleModifedKeyMapping,
|
|
&pMatchingMapping);
|
|
if (fSuccess)
|
|
{
|
|
// This mapping doesn't need to be changed at all.
|
|
_SendInputSequence(pMatchingMapping->pwszSequence);
|
|
fSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
// One last check: C-/ is supposed to be C-_
|
|
// But '/' is not the same VKEY on all keyboards. So we have to
|
|
// figure out the vkey at runtime.
|
|
const BYTE slashVkey = LOBYTE(VkKeyScan(L'/'));
|
|
if (keyEvent.GetVirtualKeyCode() == slashVkey && keyEvent.IsCtrlPressed())
|
|
{
|
|
// This mapping doesn't need to be changed at all.
|
|
_SendInputSequence(CTRL_SLASH_SEQUENCE);
|
|
fSuccess = true;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return fSuccess;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Searches the keyMapping for a entry corresponding to this key event, and returns it.
|
|
// Arguments:
|
|
// - keyEvent - Key event to translate
|
|
// - keyMapping - Array of key mappings to search
|
|
// - cKeyMapping - number of entries in keyMapping
|
|
// - pMatchingMapping - Where to put the pointer to the found match
|
|
// Return Value:
|
|
// - True if there was a match to a key translation
|
|
bool TerminalInput::_SearchKeyMapping(const KeyEvent& keyEvent,
|
|
_In_reads_(cKeyMapping) const TerminalInput::_TermKeyMap* keyMapping,
|
|
const size_t cKeyMapping,
|
|
_Out_ const TerminalInput::_TermKeyMap** pMatchingMapping) const
|
|
{
|
|
bool fKeyTranslated = false;
|
|
for (size_t i = 0; i < cKeyMapping; i++)
|
|
{
|
|
const _TermKeyMap* const pMap = &(keyMapping[i]);
|
|
|
|
if (pMap->wVirtualKey == keyEvent.GetVirtualKeyCode())
|
|
{
|
|
// If the mapping has no modifiers set, then it doesn't really care
|
|
// what the modifiers are on the key. The caller will likely do
|
|
// something with them.
|
|
// However, if there are modifiers set, then we only want to match
|
|
// if the key's modifiers are the same as the modifiers in the
|
|
// mapping.
|
|
bool modifiersMatch = WI_AreAllFlagsClear(pMap->dwModifiers, MOD_PRESSED);
|
|
if (!modifiersMatch)
|
|
{
|
|
// The modifier mapping expects certain modifier keys to be
|
|
// pressed. Check those as well.
|
|
modifiersMatch =
|
|
(WI_IsFlagSet(pMap->dwModifiers, SHIFT_PRESSED) == keyEvent.IsShiftPressed()) &&
|
|
(WI_IsAnyFlagSet(pMap->dwModifiers, ALT_PRESSED) == keyEvent.IsAltPressed()) &&
|
|
(WI_IsAnyFlagSet(pMap->dwModifiers, CTRL_PRESSED) == keyEvent.IsCtrlPressed());
|
|
}
|
|
|
|
if (modifiersMatch)
|
|
{
|
|
fKeyTranslated = true;
|
|
*pMatchingMapping = pMap;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return fKeyTranslated;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Searches the input array of mappings, and sends it to the input if a match was found.
|
|
// Arguments:
|
|
// - keyEvent - Key event to translate
|
|
// - keyMapping - Array of key mappings to search
|
|
// - cKeyMapping - number of entries in keyMapping
|
|
// Return Value:
|
|
// - True if there was a match to a key translation, and we successfully sent it to the input
|
|
bool TerminalInput::_TranslateDefaultMapping(const KeyEvent& keyEvent,
|
|
_In_reads_(cKeyMapping) const TerminalInput::_TermKeyMap* keyMapping,
|
|
const size_t cKeyMapping) const
|
|
{
|
|
const TerminalInput::_TermKeyMap* pMatchingMapping;
|
|
bool fSuccess = _SearchKeyMapping(keyEvent, keyMapping, cKeyMapping, &pMatchingMapping);
|
|
if (fSuccess)
|
|
{
|
|
_SendInputSequence(pMatchingMapping->pwszSequence);
|
|
fSuccess = true;
|
|
}
|
|
return fSuccess;
|
|
}
|
|
|
|
bool TerminalInput::HandleKey(const IInputEvent* const pInEvent) const
|
|
{
|
|
// By default, we fail to handle the key
|
|
bool fKeyHandled = false;
|
|
|
|
// On key presses, prepare to translate to VT compatible sequences
|
|
if (pInEvent->EventType() == InputEventType::KeyEvent)
|
|
{
|
|
KeyEvent 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.
|
|
wchar_t wchPressedChar = static_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);
|
|
fKeyHandled = true;
|
|
}
|
|
}
|
|
|
|
// If a modifier key was pressed, then we need to try and send the modified sequence.
|
|
if (!fKeyHandled && keyEvent.IsModifierPressed())
|
|
{
|
|
// Translate the key using the modifier table
|
|
fKeyHandled = _SearchWithModifier(keyEvent);
|
|
}
|
|
// ALT is a sequence of ESC + KEY.
|
|
if (!fKeyHandled && keyEvent.GetCharData() != 0 && keyEvent.IsAltPressed())
|
|
{
|
|
_SendEscapedInputSequence(keyEvent.GetCharData());
|
|
fKeyHandled = true;
|
|
}
|
|
if (!fKeyHandled && 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());
|
|
fKeyHandled = true;
|
|
}
|
|
}
|
|
|
|
if (!fKeyHandled)
|
|
{
|
|
// 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-maintanable 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)
|
|
{
|
|
fKeyHandled = _TranslateDefaultMapping(keyEvent, GetKeyMapping(keyEvent), GetKeyMappingLength(keyEvent));
|
|
}
|
|
else
|
|
{
|
|
WCHAR rgwchSequence[2];
|
|
rgwchSequence[0] = keyEvent.GetCharData();
|
|
rgwchSequence[1] = UNICODE_NULL;
|
|
_SendInputSequence(rgwchSequence);
|
|
fKeyHandled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fKeyHandled;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Sends the given char as a sequence representing Alt+wch, also the same as
|
|
// Meta+wch.
|
|
// Arguments:
|
|
// - wch - character to send to input paired with Esc
|
|
// Return Value:
|
|
// - None
|
|
void TerminalInput::_SendEscapedInputSequence(const wchar_t wch) const
|
|
{
|
|
try
|
|
{
|
|
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
|
|
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, L'\x1b', 0));
|
|
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
|
|
_pfnWriteEvents(inputEvents);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
|
|
void TerminalInput::_SendNullInputSequence(const DWORD dwControlKeyState) const
|
|
{
|
|
try
|
|
{
|
|
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
|
|
inputEvents.push_back(std::make_unique<KeyEvent>(true,
|
|
1ui16,
|
|
LOBYTE(VkKeyScanW(0)),
|
|
0ui16,
|
|
L'\x0',
|
|
dwControlKeyState));
|
|
_pfnWriteEvents(inputEvents);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
|
|
void TerminalInput::_SendInputSequence(_In_ PCWSTR const pwszSequence) const
|
|
{
|
|
size_t cch = 0;
|
|
// + 1 to max sequence length for null terminator count which is required by StringCchLengthW
|
|
if (SUCCEEDED(StringCchLengthW(pwszSequence, _TermKeyMap::s_cchMaxSequenceLength + 1, &cch)) && cch > 0)
|
|
{
|
|
try
|
|
{
|
|
std::deque<std::unique_ptr<IInputEvent>> inputEvents;
|
|
for (size_t i = 0; i < cch; i++)
|
|
{
|
|
inputEvents.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, pwszSequence[i], 0));
|
|
}
|
|
_pfnWriteEvents(inputEvents);
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_HR(wil::ResultFromCaughtException());
|
|
}
|
|
}
|
|
}
|