Fix SendInput handling (#7900)

While not explicitly permitted, a wide range of software (including
Windows' own touch keyboard) sets the `wScan` member of the `KEYBDINPUT`
structure to 0, resulting in `scanCode` being 0 as well.  In these
situations we'll now use the `vkey` to get a `scanCode`.

Validation
----------
* AutoHotkey
  * Use a keyboard layout with `AltGr` key
  * Execute the following script:
    ```ahk
    #NoEnv
    #Warn
    SendMode Input
    SetWorkingDir %A_ScriptDir%
    <^>!8::SendInput {Raw}»
    ```
  * Press `AltGr+8` while the Terminal is in the foreground
  * Ensure » is being echoed ✔️
* PowerToys
  * Add a `Ctrl+I -> ↑ (up arrow)` keyboard shortcut
  * Press `Ctrl+I` while the Terminal is in the foreground
  * Ensure the shell history is being navigated backwards ✔️
* Windows Touch Keyboard
  * Right-click or tap and hold the taskbar and select "Show touch
    keyboard" button
  * Open touch keyboard
  * Ensure keyboard works like a regular keyboard ✔️
  * Ensure unicode characters are echoed on the Terminal as well (except
    for Emojis) ✔️

Closes #7438
Closes #7495
Closes #7843
This commit is contained in:
Leonard Hecker 2020-10-27 20:06:29 +01:00 committed by GitHub
parent 1df3182865
commit d51d8dc768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 23 additions and 43 deletions

View File

@ -0,0 +1,7 @@
autogenerated
CPPCORECHECK
Debian
filepath
inplace
KEYBDINPUT
WINVER

View File

@ -33,7 +33,6 @@ AHelper
ahz ahz
AImpl AImpl
AInplace AInplace
akb
ALIGNRIGHT ALIGNRIGHT
alloc alloc
allocing allocing
@ -62,7 +61,6 @@ apiset
apos apos
APPBARDATA APPBARDATA
appconsult appconsult
appdata
APPICON APPICON
appium appium
applet applet
@ -109,10 +107,8 @@ aumid
Authenticode Authenticode
AUTOBUDDY AUTOBUDDY
AUTOCHECKBOX AUTOCHECKBOX
Autogenerated
autohide autohide
AUTOHSCROLL AUTOHSCROLL
autologin
automagically automagically
autopositioning autopositioning
AUTORADIOBUTTON AUTORADIOBUTTON
@ -393,7 +389,6 @@ CPINFOEX
cplinfo cplinfo
cplusplus cplusplus
cpp cpp
cppcorecheck
cppcorecheckrules cppcorecheckrules
cpprest cpprest
cpprestsdk cpprestsdk
@ -510,10 +505,8 @@ DDESHARE
DDevice DDevice
DEADCHAR DEADCHAR
dealloc dealloc
debian
debolden debolden
debounce debounce
debugbreak
DECALN DECALN
DECANM DECANM
DECAUPSS DECAUPSS
@ -521,11 +514,9 @@ DECAWM
DECCKM DECCKM
DECCOLM DECCOLM
DECEKBD DECEKBD
decf
DECKPAM DECKPAM
DECKPM DECKPM
DECKPNM DECKPNM
DECLL
DECLRMM DECLRMM
decls decls
declspec declspec
@ -552,7 +543,6 @@ DECSEL
DECSET DECSET
DECSLPP DECSLPP
DECSLRM DECSLRM
DECSMBV
DECSMKR DECSMKR
DECSR DECSR
decstandar decstandar
@ -716,7 +706,6 @@ EPres
ERASEBKGND ERASEBKGND
errno errno
errorlevel errorlevel
esa
ETB ETB
etcoreapp etcoreapp
ETW ETW
@ -771,7 +760,6 @@ fgetwc
fgidx fgidx
FILEDESCRIPTION FILEDESCRIPTION
fileno fileno
FILEPATH
FILESUBTYPE FILESUBTYPE
FILESYSPATH FILESYSPATH
filesystem filesystem
@ -932,7 +920,6 @@ GTP
guc guc
gui gui
guidatom guidatom
guidgenerator
GValue GValue
GWL GWL
GWLP GWLP
@ -1105,7 +1092,6 @@ Inlines
INotify INotify
inout inout
INPATHROOT INPATHROOT
Inplace
inproc inproc
Inputkeyinfo Inputkeyinfo
INPUTPROCESSORPROFILE INPUTPROCESSORPROFILE
@ -1194,11 +1180,9 @@ kcud
kcuf kcuf
kcuu kcuu
Kd Kd
keith
kernelbase kernelbase
kernelbasestaging kernelbasestaging
keybinding keybinding
keybound
keychord keychord
keydown keydown
keyevent keyevent
@ -1471,7 +1455,6 @@ namestream
Namquiseratal Namquiseratal
nano nano
natvis natvis
naws
nbsp nbsp
Nc Nc
NCCALCSIZE NCCALCSIZE
@ -1553,7 +1536,6 @@ NOTNULL
NOTOPMOST NOTOPMOST
NOTRACK NOTRACK
NOTSUPPORTED NOTSUPPORTED
notypeopt
nouicompat nouicompat
nounihan nounihan
NOUPDATE NOUPDATE
@ -1628,7 +1610,6 @@ opencon
openconsole openconsole
OPENIF OPENIF
OPENLINK OPENLINK
openlogo
openps openps
opensource opensource
openvt openvt
@ -1968,7 +1949,6 @@ resheader
resizable resizable
resmimetype resmimetype
restrictedcapabilities restrictedcapabilities
restrictederrorinfo
resw resw
resx resx
retval retval
@ -1995,7 +1975,6 @@ rgw
rgwch rgwch
rhs rhs
ri ri
richturn
RIGHTALIGN RIGHTALIGN
RIGHTBUTTON RIGHTBUTTON
riid riid
@ -2069,7 +2048,6 @@ SCROLLSCALE
SCROLLSCREENBUFFER SCROLLSCREENBUFFER
Scrollup Scrollup
Scrolluppage Scrolluppage
Scs
scursor scursor
sddl sddl
sdeleted sdeleted
@ -2243,7 +2221,6 @@ subkey
SUBLANG SUBLANG
sublicensable sublicensable
submenu submenu
subnegotiation
subresource subresource
subspan subspan
substr substr
@ -2254,7 +2231,6 @@ svg
swapchain swapchain
swapchainpanel swapchainpanel
swappable swappable
Switchto
SWMR SWMR
SWP SWP
swprintf swprintf
@ -2309,7 +2285,6 @@ technet
tellp tellp
telnet telnet
telnetd telnetd
telnetpp
templated templated
terminalcore terminalcore
TERMINALSCROLLING TERMINALSCROLLING
@ -2706,12 +2681,10 @@ wintelnet
winternl winternl
winuser winuser
winuserp winuserp
winver
wistd wistd
wixproj wixproj
wline wline
wlinestream wlinestream
Wlk
wmain wmain
WMSZ WMSZ
wnd wnd
@ -2754,7 +2727,6 @@ WRunoff
WScript WScript
wsl wsl
WSLENV WSLENV
wslhome
wsmatch wsmatch
WSpace WSpace
wss wss

View File

@ -472,15 +472,20 @@ bool Terminal::SendKeyEvent(const WORD vkey,
_StoreKeyEvent(vkey, scanCode); _StoreKeyEvent(vkey, scanCode);
// As a Terminal we're mostly interested in getting key events from physical hardware (mouse & keyboard). // Certain applications like AutoHotKey and its keyboard remapping feature,
// We're thus ignoring events whose values are outside the valid range and unlikely to be generated by the current keyboard. // send us key events using SendInput() whose values are outside of the valid range.
// It's very likely that a proper followup character event will be sent to us.
// This prominently happens using AutoHotKey's keyboard remapping feature,
// which sends input events whose vkey is 0xff and scanCode is 0.
// We need to check for this early, as _CharacterFromKeyEvent() always returns 0 for such invalid values,
// making us believe that this is an actual non-character input, while it usually isn't.
// GH#7064 // GH#7064
if (vkey == 0 || vkey >= 0xff || scanCode == 0) if (vkey == 0 || vkey >= 0xff)
{
return false;
}
// While not explicitly permitted, a wide range of software, including Windows' own touch keyboard,
// sets the wScan member of the KEYBDINPUT structure to 0, resulting in scanCode being 0 as well.
// --> Alternatively get the scanCode from the vkey if possible.
// GH#7495
const auto sc = scanCode ? scanCode : _ScanCodeFromVirtualKey(vkey);
if (sc == 0)
{ {
return false; return false;
} }
@ -506,7 +511,7 @@ bool Terminal::SendKeyEvent(const WORD vkey,
// is the underlying ASCII character (e.g. A-Z) on the keyboard in our case. // is the underlying ASCII character (e.g. A-Z) on the keyboard in our case.
// See GH#5525/GH#6211 for more details // See GH#5525/GH#6211 for more details
const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed() && !states.IsAltGrPressed(); const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed() && !states.IsAltGrPressed();
const auto ch = isSuppressedAltGrAlias ? UNICODE_NULL : _CharacterFromKeyEvent(vkey, scanCode, states); const auto ch = isSuppressedAltGrAlias ? UNICODE_NULL : _CharacterFromKeyEvent(vkey, sc, states);
// Delegate it to the character event handler if this key event can be // Delegate it to the character event handler if this key event can be
// mapped to one (see method description above). For Alt+key combinations // mapped to one (see method description above). For Alt+key combinations
@ -521,7 +526,7 @@ bool Terminal::SendKeyEvent(const WORD vkey,
return false; return false;
} }
KeyEvent keyEv{ keyDown, 1, vkey, scanCode, ch, states.Value() }; KeyEvent keyEv{ keyDown, 1, vkey, sc, ch, states.Value() };
return _terminalInput->HandleKey(&keyEv); return _terminalInput->HandleKey(&keyEv);
} }
@ -638,8 +643,6 @@ WORD Terminal::_VirtualKeyFromCharacter(const wchar_t ch) noexcept
wchar_t Terminal::_CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept wchar_t Terminal::_CharacterFromKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states) noexcept
try try
{ {
const auto sc = scanCode != 0 ? scanCode : _ScanCodeFromVirtualKey(vkey);
// We might want to use GetKeyboardState() instead of building our own keyState. // We might want to use GetKeyboardState() instead of building our own keyState.
// The question is whether that's necessary though. For now it seems to work fine as it is. // The question is whether that's necessary though. For now it seems to work fine as it is.
std::array<BYTE, 256> keyState = {}; std::array<BYTE, 256> keyState = {};
@ -658,7 +661,7 @@ try
// * If bit 0 is set, a menu is active. // * If bit 0 is set, a menu is active.
// If this flag is not specified ToUnicodeEx will send us character events on certain Alt+Key combinations (e.g. Alt+Arrow-Up). // If this flag is not specified ToUnicodeEx will send us character events on certain Alt+Key combinations (e.g. Alt+Arrow-Up).
// * If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer) // * If bit 2 is set, keyboard state is not changed (Windows 10, version 1607 and newer)
const auto result = ToUnicodeEx(vkey, sc, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b101, nullptr); const auto result = ToUnicodeEx(vkey, scanCode, keyState.data(), buffer.data(), gsl::narrow_cast<int>(buffer.size()), 0b101, nullptr);
// TODO:GH#2853 We're only handling single UTF-16 code points right now, since that's the only thing KeyEvent supports. // TODO:GH#2853 We're only handling single UTF-16 code points right now, since that's the only thing KeyEvent supports.
return result == 1 || result == -1 ? buffer.at(0) : 0; return result == 1 || result == -1 ? buffer.at(0) : 0;

View File

@ -71,9 +71,7 @@ namespace TerminalCoreUnitTests
{ {
// Certain applications like AutoHotKey and its keyboard remapping feature, // Certain applications like AutoHotKey and its keyboard remapping feature,
// send us key events using SendInput() whose values are outside of the valid range. // send us key events using SendInput() whose values are outside of the valid range.
// We don't want to handle those events as we're probably going to get a proper followup character event.
VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true)); VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true));
VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true)); VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true));
VERIFY_IS_FALSE(term.SendKeyEvent(123, 0, {}, true));
} }
} }