From 8e1e7bc540e79317bec8158e15514b1d79180de6 Mon Sep 17 00:00:00 2001 From: Matthew Kilgore Date: Tue, 25 Apr 2023 18:39:31 -0400 Subject: [PATCH] Fix keyboard _Device on Windows Currently the Keyboard _Devices entry on Windows does not report all the key presses and releases. This is due to missing some messages in the form of WM_SYSKEYDOWN and WM_SYSKEYUP. Additionally Windows is weird about report the state of the individual shift keys, so we add some logic using GetAsyncKeyState() to fix that up. Fixes: #333 --- internal/c/libqb.cpp | 117 +++++------ .../keyboard/devices_windows.bas | 187 ++++++++++++++++++ .../keyboard/devices_windows.output | 96 +++++++++ tests/compile_tests/keyboard/keyconsts.bi | 126 ++++++++++++ tests/compile_tests/keyboard/sendinput.bi | 28 +++ tests/compile_tests/keyboard/sendinput.bm | 29 +++ 6 files changed, 517 insertions(+), 66 deletions(-) create mode 100644 tests/compile_tests/keyboard/devices_windows.bas create mode 100644 tests/compile_tests/keyboard/devices_windows.output create mode 100644 tests/compile_tests/keyboard/keyconsts.bi create mode 100644 tests/compile_tests/keyboard/sendinput.bi create mode 100644 tests/compile_tests/keyboard/sendinput.bm diff --git a/internal/c/libqb.cpp b/internal/c/libqb.cpp index 6438b13f6..9bb2e2e7f 100644 --- a/internal/c/libqb.cpp +++ b/internal/c/libqb.cpp @@ -34267,7 +34267,6 @@ void GLUT_KEYBOARD_FUNC(unsigned char key, int x, int y) { void GLUT_KEYBOARDUP_FUNC(unsigned char key, int x, int y) { GLUT_key_ascii(key, 0); } void GLUT_key_special(int32 key, int32 down) { - #ifdef QB64_GLUT # ifndef CORE_FREEGLUT /* @@ -39236,78 +39235,64 @@ void scancodeup(uint8 scancode) { #define OS_EVENT_POST_PROCESSING 2 #define OS_EVENT_RETURN_IMMEDIATELY 3 +static void reportKeyState(int code, int down) { + static device_struct *d; + d = &devices[1]; // keyboard + + // don't add message if state already matches what we're reporting + if (getDeviceEventButtonValue(d, d->queued_events - 1, code) != down) { + int32 eventIndex = createDeviceEvent(d); + setDeviceEventButtonValue(d, eventIndex, code, down); + commitDeviceEvent(d); + } +} + + #ifdef QB64_WINDOWS + +// Windows only reports one WM_KEYUP event when both shift keys are held. To +// fix that somewhat we're manually check on each window message and reporting +// the keyup ourselves. +static void checkShiftKeys() { + static int rightShift = 0, leftShift = 0; + + // GetAsyncKeyState() indicates the key state in the most significant bit + if (rightShift != !!(GetAsyncKeyState(VK_RSHIFT) & 0x8000)) { + rightShift = !rightShift; + reportKeyState(0x36, rightShift); + } + + if (leftShift != !!(GetAsyncKeyState(VK_LSHIFT) & 0x8000)) { + leftShift = !leftShift; + reportKeyState(0x2A, leftShift); + } +} + extern "C" LRESULT qb64_os_event_windows(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, int *qb64_os_event_info) { if (*qb64_os_event_info == OS_EVENT_PRE_PROCESSING) { - // example usage - /* - if (uMsg==WM_CLOSE){ - alert("goodbye"); - *qb64_os_event_info=OS_EVENT_RETURN_IMMEDIATELY; + if (func__hasfocus()) + checkShiftKeys(); + + if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) { + if (device_last) { // core devices required? + /* + 16-23 The scan code. The value depends on the OEM. + 24 Indicates whether the key is an extended key, such as the right-hand ALT and CTRL keys that appear on an enhanced 101- or 102-key + keyboard. The value is 1 if it is an extended key; otherwise, it is 0. + */ + uint32_t code = (lParam >> 16) & 0x1FF; + + reportKeyState(code, 1); } - */ - - if (uMsg == WM_KEYDOWN) { + } + if (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP) { if (device_last) { // core devices required? + uint32_t code = (lParam >> 16) & 0x1FF; - /* - 16-23 The scan code. The value depends on the OEM. - 24 Indicates whether the key is an extended key, such as the right-hand ALT and CTRL keys that appear on an enhanced 101- or 102-key - keyboard. The value is 1 if it is an extended key; otherwise, it is 0. - */ - - static int32 code, special; - special = 0; // set to 2 for keys which we cannot detect a release for - code = (lParam >> 16) & 511; - - keydown_special: - static device_struct *d; - d = &devices[1]; // keyboard - if (getDeviceEventButtonValue(d, d->queued_events - 1, code) != 1) { // don't add message if already on - - int32 eventIndex = createDeviceEvent(d); - setDeviceEventButtonValue(d, eventIndex, code, 1); - if (special == 2) { - special = 1; - commitDeviceEvent(d); - goto keydown_special; - } // jump to ~5 lines above to add a 2nd key event - if (special == 1) - setDeviceEventButtonValue(d, eventIndex, code, 0); - commitDeviceEvent(d); - - } // not 1 - } // core devices required - - } // WM_KEYDOWN - - if (uMsg == WM_KEYUP) { - - if (device_last) { // core devices required? - - /* - 16-23 The scan code. The value depends on the OEM. - 24 Indicates whether the key is an extended key, such as the right-hand ALT and CTRL keys that appear on an enhanced 101- or 102-key - keyboard. The value is 1 if it is an extended key; otherwise, it is 0. - */ - - static int32 code; - - code = (lParam >> 16) & 511; - - static device_struct *d; - d = &devices[1]; // keyboard - if (getDeviceEventButtonValue(d, d->queued_events - 1, code) != 0) { // don't add message if already off - - int32 eventIndex = createDeviceEvent(d); - setDeviceEventButtonValue(d, eventIndex, code, 0); - commitDeviceEvent(d); - - } // not 1 - } // core devices required - - } // WM_KEYUP + reportKeyState(code, 0); + } + } } if (*qb64_os_event_info == OS_EVENT_POST_PROCESSING) { } diff --git a/tests/compile_tests/keyboard/devices_windows.bas b/tests/compile_tests/keyboard/devices_windows.bas new file mode 100644 index 000000000..5fbb68c1c --- /dev/null +++ b/tests/compile_tests/keyboard/devices_windows.bas @@ -0,0 +1,187 @@ +$Console +_Dest _Console + +ChDir _StartDir$ + +$IF LNX THEN + +'$include:'keyconsts.bi' +'$include:'sendinput.bi' + +Dim Shared testCount As Long + +deviceCount& = _Devices + +AssertPress EscScanCode +AssertPress EnterScancode +AssertPress TabScancode +AssertPress OneScancode +AssertPress AScanCode +AssertPress WScanCode +AssertPress SpaceScanCode + +AssertPress PgUpScanCode +AssertPress PgDownScanCode +AssertPress HomeScanCode +AssertPress InsertScanCode +AssertPress DeleteScanCode +AssertPress EndScanCode + +AssertPress LCtrlScancode +AssertPress LShiftScancode +AssertPress LAltScancode + +AssertPress RCtrlScancode +AssertPress RShiftScancode +AssertPress RAltScancode + +AssertPress NumLockScancode +AssertPress ScrollLockScancode +AssertPress KeypadUpScancode + +AssertPress F1Scancode +AssertPress F2Scancode +AssertPress F3Scancode +AssertPress F4Scancode +AssertPress F5Scancode +AssertPress F6Scancode +AssertPress F7Scancode +AssertPress F8Scancode +AssertPress F9Scancode +AssertPress F10Scancode +AssertPress F11Scancode +AssertPress F12Scancode + +' Test multiple keys at the same time +' Modifiers in paticular are important to test +emulateScancode LShiftScancode, Down +emulateScancode LCtrlScancode, Down +emulateScancode LAltScancode, Down +emulateScancode AScanCode, Down + +AssertDown LShiftScancode +AssertDown LCtrlScancode +AssertDown LAltScancode +AssertDown AScanCode + +' These keys are released in same order they were pressed +emulateScancode LShiftScancode, Up +AssertUp LShiftScancode +emulateScancode LCtrlScancode, Up +AssertUp LCtrlScancode +emulateScancode LAltScancode, Up +AssertUp LAltScancode +emulateScancode AScanCode, Up +AssertUp AScanCode + +' Same test, but release keys in opposite order they were pressed +emulateScancode LShiftScancode, Down +emulateScancode LCtrlScancode, Down +emulateScancode LAltScancode, Down +emulateScancode AScanCode, Down + +AssertDown LShiftScancode +AssertDown LCtrlScancode +AssertDown LAltScancode +AssertDown AScanCode + +emulateScancode AScanCode, Up +AssertUp AScanCode +emulateScancode LAltScancode, Up +AssertUp LAltScancode +emulateScancode LCtrlScancode, Up +AssertUp LCtrlScancode +emulateScancode LShiftScancode, Up +AssertUp LShiftScancode + +' Test holding and releasing both left/right modifiers + +' Shift +emulateScancode LShiftScancode, Down +AssertDown LShiftScancode +emulateScancode RShiftScancode, Down +AssertDown RShiftScancode + +emulateScancode LShiftScancode, Up +AssertUp LShiftScancode +emulateScancode RShiftScancode, Up +AssertUp RShiftScancode + +' Control +emulateScancode LCtrlScancode, Down +AssertDown LCtrlScancode +emulateScancode RCtrlScancode, Down +AssertDown RCtrlScancode + +emulateScancode LCtrlScancode, Up +AssertUp LCtrlScancode +emulateScancode RCtrlScancode, Up +AssertUp RCtrlScancode + +' Alt +emulateScancode LAltScancode, Down +AssertDown LAltScancode +emulateScancode RAltScancode, Down +AssertDown RAltScancode + +emulateScancode LAltScancode, Up +AssertUp LAltScancode +emulateScancode RAltScancode, Up +AssertUp RAltScancode + +System + +'$include:'sendinput.bm' + +' Convert scan code into the device button number +Function GetDeviceCode&(scan As Long) + Dim deviceCode As Long + + ' "Extended" keys will have the 9th bit set, so add 256 + deviceCode = (scan And &H7FFF) + 1 + If scan And &H8000 Then deviceCode = deviceCode + 256 + + GetDeviceCode& = deviceCode +End Function + +Sub AssertPress(scan As Long) + testCount = testCount + 1 + + emulateScancode scan, Down + AssertDown scan + + emulateScancode scan, Up + AssertUp scan +End Sub + +Sub AssertDown(scan As Long) + testCount = testCount + 1 + deviceCode = GetDeviceCode&(scan) + + While _DeviceInput(1): Wend + Print "Test button down:"; testCount; ": "; + If _Button(deviceCode) Then Print "PASS!" Else Print "FAIL! Key="; scan And &H7FFF; ", state="; _Button(deviceCode) +End Sub + +Sub AssertUp(scan As Long) + testCount = testCount + 1 + deviceCode = GetDeviceCode&(scan) + + While _DeviceInput(1): Wend + Print "Test button up :"; testCount; ": "; + If Not _Button(deviceCode) Then Print "PASS!" Else Print "FAIL! Key="; scan And &H7FFF; ", state="; _Button(deviceCode) +End Sub + +$Else + +' Not supported for other platforms at the moment, output is simulated +Open "devices_windows.output" For Input As #1 + +While Not Eof(1) + Line Input #1, l$ + Print l$ +Wend + +System + +$End If diff --git a/tests/compile_tests/keyboard/devices_windows.output b/tests/compile_tests/keyboard/devices_windows.output new file mode 100644 index 000000000..9ea40ecfd --- /dev/null +++ b/tests/compile_tests/keyboard/devices_windows.output @@ -0,0 +1,96 @@ +Test button down: 2 : PASS! +Test button up : 3 : PASS! +Test button down: 5 : PASS! +Test button up : 6 : PASS! +Test button down: 8 : PASS! +Test button up : 9 : PASS! +Test button down: 11 : PASS! +Test button up : 12 : PASS! +Test button down: 14 : PASS! +Test button up : 15 : PASS! +Test button down: 17 : PASS! +Test button up : 18 : PASS! +Test button down: 20 : PASS! +Test button up : 21 : PASS! +Test button down: 23 : PASS! +Test button up : 24 : PASS! +Test button down: 26 : PASS! +Test button up : 27 : PASS! +Test button down: 29 : PASS! +Test button up : 30 : PASS! +Test button down: 32 : PASS! +Test button up : 33 : PASS! +Test button down: 35 : PASS! +Test button up : 36 : PASS! +Test button down: 38 : PASS! +Test button up : 39 : PASS! +Test button down: 41 : PASS! +Test button up : 42 : PASS! +Test button down: 44 : PASS! +Test button up : 45 : PASS! +Test button down: 47 : PASS! +Test button up : 48 : PASS! +Test button down: 50 : PASS! +Test button up : 51 : PASS! +Test button down: 53 : PASS! +Test button up : 54 : PASS! +Test button down: 56 : PASS! +Test button up : 57 : PASS! +Test button down: 59 : PASS! +Test button up : 60 : PASS! +Test button down: 62 : PASS! +Test button up : 63 : PASS! +Test button down: 65 : PASS! +Test button up : 66 : PASS! +Test button down: 68 : PASS! +Test button up : 69 : PASS! +Test button down: 71 : PASS! +Test button up : 72 : PASS! +Test button down: 74 : PASS! +Test button up : 75 : PASS! +Test button down: 77 : PASS! +Test button up : 78 : PASS! +Test button down: 80 : PASS! +Test button up : 81 : PASS! +Test button down: 83 : PASS! +Test button up : 84 : PASS! +Test button down: 86 : PASS! +Test button up : 87 : PASS! +Test button down: 89 : PASS! +Test button up : 90 : PASS! +Test button down: 92 : PASS! +Test button up : 93 : PASS! +Test button down: 95 : PASS! +Test button up : 96 : PASS! +Test button down: 98 : PASS! +Test button up : 99 : PASS! +Test button down: 101 : PASS! +Test button up : 102 : PASS! +Test button down: 103 : PASS! +Test button down: 104 : PASS! +Test button down: 105 : PASS! +Test button down: 106 : PASS! +Test button up : 107 : PASS! +Test button up : 108 : PASS! +Test button up : 109 : PASS! +Test button up : 110 : PASS! +Test button down: 111 : PASS! +Test button down: 112 : PASS! +Test button down: 113 : PASS! +Test button down: 114 : PASS! +Test button up : 115 : PASS! +Test button up : 116 : PASS! +Test button up : 117 : PASS! +Test button up : 118 : PASS! +Test button down: 119 : PASS! +Test button down: 120 : PASS! +Test button up : 121 : PASS! +Test button up : 122 : PASS! +Test button down: 123 : PASS! +Test button down: 124 : PASS! +Test button up : 125 : PASS! +Test button up : 126 : PASS! +Test button down: 127 : PASS! +Test button down: 128 : PASS! +Test button up : 129 : PASS! +Test button up : 130 : PASS! diff --git a/tests/compile_tests/keyboard/keyconsts.bi b/tests/compile_tests/keyboard/keyconsts.bi new file mode 100644 index 000000000..7a88681aa --- /dev/null +++ b/tests/compile_tests/keyboard/keyconsts.bi @@ -0,0 +1,126 @@ +' _KEYDOWN Keyboard Values +' +'Esc F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 Sys ScL Pause +' 27 15104 15360 15616 15872 16128 16384 16640 16896 17152 17408 34048 34304 +316 +302 +019 +'`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+ BkSp Ins Hme PUp NumL / * - +'126 33 64 35 36 37 94 38 42 40 41 95 43 8 20992 18176 18688 +300 47 42 45 +' 96 49 50 51 52 53 54 55 56 57 48 45 61 +'Tab Q W E R T Y U I O P [{ ]} \| Del End PDn 7Hme 8/^ 9PU + +' 9 81 87 69 82 84 89 85 73 79 80 123 125 124 21248 20224 20736 18176 18432 18688 43 +' 113 119 101 114 116 121 117 105 111 112 91 93 92 55 56 57 +'CapL A S D F G H J K L ;: '" Enter 4/<- 5 6/-> +'+301 65 83 68 70 71 72 74 75 76 58 34 13 19200 19456 19712 E +' 97 115 100 102 103 104 106 107 108 59 39 52 53 54 n +'Shift Z X C V B N M ,< .> /? Shift ^ 1End 2/V 3PD t +'+304 90 88 67 86 66 78 77 60 62 63 +303 18432 20224 20480 20736 e +' 122 120 99 118 98 110 109 44 46 47 49 50 51 r +'Ctrl Win Alt Spacebar Alt Win Menu Ctrl <- V -> 0Ins .Del +'+306 +311 +308 32 +307 +312 +319 +305 19200 20480 19712 20992 21248 13 +' 48 46 +' +' Lower value = LCase/NumLock On __________________ + = add 100000 + +CONST KEY_TAB = 9 +CONST KEY_ENTER = 13 +CONST KEY_EXCLAIM = 33 +CONST KEY_ONE = 49 +CONST KEY_F1 = 15104 +CONST KEY_F10 = 17408 + +CONST KEY_SHIFT = 16 +CONST KEY_CTRL = 17 +CONST KEY_ALT = 18 + +CONST KEY_PAUSE& = 100019 +CONST KEY_NUMLOCK& = 100300 +CONST KEY_CAPSLOCK& = 100301 +CONST KEY_SCROLLOCK& = 100302 +CONST KEY_RSHIFT& = 100303 +CONST KEY_LSHIFT& = 100304 +CONST KEY_RCTRL& = 100305 +CONST KEY_LCTRL& = 100306 +CONST KEY_RALT& = 100307 +CONST KEY_LALT& = 100308 +CONST KEY_RMETA& = 100309 'Left 'Apple' key (macOS) +CONST KEY_LMETA& = 100310 'Right 'Apple' key (macOS) +CONST KEY_LSUPER& = 100311 'Left "Windows" key +CONST KEY_RSUPER& = 100312 'Right "Windows"key +CONST KEY_MODE& = 100313 '"AltGr" key +CONST KEY_COMPOSE& = 100314 +CONST KEY_HELP& = 100315 +CONST KEY_PRINT& = 100316 +CONST KEY_SYSREQ& = 100317 +CONST KEY_BREAK& = 100318 +CONST KEY_MENU& = 100319 +CONST KEY_POWER& = 100320 +CONST KEY_EURO& = 100321 +CONST KEY_UNDO& = 100322 +CONST KEY_KP0& = 100256 +CONST KEY_KP1& = 100257 +CONST KEY_KP2& = 100258 +CONST KEY_KP3& = 100259 +CONST KEY_KP4& = 100260 +CONST KEY_KP5& = 100261 +CONST KEY_KP6& = 100262 +CONST KEY_KP7& = 100263 +CONST KEY_KP8& = 100264 +CONST KEY_KP9& = 100265 +CONST KEY_KP_PERIOD& = 100266 +CONST KEY_KP_DIVIDE& = 100267 +CONST KEY_KP_MULTIPLY& = 100268 +CONST KEY_KP_MINUS& = 100269 +CONST KEY_KP_PLUS& = 100270 +CONST KEY_KP_ENTER& = 100271 +CONST KEY_KP_INSERT& = 200000 +CONST KEY_KP_END& = 200001 +CONST KEY_KP_DOWN& = 200002 +CONST KEY_KP_PAGE_DOWN& = 200003 +CONST KEY_KP_LEFT& = 200004 +CONST KEY_KP_MIDDLE& = 200005 +CONST KEY_KP_RIGHT& = 200006 +CONST KEY_KP_HOME& = 200007 +CONST KEY_KP_UP& = 200008 +CONST KEY_KP_PAGE_UP& = 200009 +CONST KEY_KP_DELETE& = 200010 +CONST KEY_SCROLL_LOCK_MODE& = 200011 +CONST KEY_INSERT_MODE& = 200012 + +Const EscScanCode = &H01 +Const EnterScancode = &H1C +Const TabScancode = &H0F +Const OneScancode = &H02 +Const AScanCode = &H1E ' A key +Const WScanCode = &H11 +Const SpaceScanCode = &H39 + +Const PgUpScanCode = &H49 Or &H8000 +Const PgDownScanCode = &H51 Or &H8000 +Const HomeScanCode = &H47 Or &H8000 +Const InsertScanCode = &H52 Or &H8000 +Const DeleteScanCode = &H53 Or &H8000 +Const EndScanCode = &H4F Or &H8000 + +Const LCtrlScancode = &H1D +Const LShiftScancode = &H2A +Const LAltScancode = &H38 +Const RCtrlScancode = &H1D Or &H8000 ' Extended +Const RShiftScancode = &H36 +Const RAltScancode = &H38 Or &H8000 + +Const NumLockScancode = &H45 +Const ScrollLockScancode = &H45 +Const KeypadUpScancode = &H48 + +Const F1Scancode = &H3B +Const F2Scancode = &H3C +Const F3Scancode = &H3D +Const F4Scancode = &H3E +Const F5Scancode = &H3F +Const F6Scancode = &H40 +Const F7Scancode = &H41 +Const F8Scancode = &H42 +Const F9Scancode = &H43 +Const F10Scancode = &H44 +Const F11Scancode = &H57 +Const F12Scancode = &H58 + diff --git a/tests/compile_tests/keyboard/sendinput.bi b/tests/compile_tests/keyboard/sendinput.bi new file mode 100644 index 000000000..edd41e2d4 --- /dev/null +++ b/tests/compile_tests/keyboard/sendinput.bi @@ -0,0 +1,28 @@ + +Const INPUT_TYPE_KEYBOARD = 1 + +Const KBD_FLAG_EXTENDED = 1 +Const KBD_FLAG_UP = 2 +Const KBD_FLAG_UNICODE = 4 +Const KBD_FLAG_SCANCODE = 8 + +Const Down = 1 +Const Up = 0 + +Type KbdInput + typ As Long + pad As Long + vk As Integer + scancode As Integer + flags As _Unsigned Long + tim As _Unsigned Long + pad2 As Long + extraInfo As _Offset + padding As String * 8 +End Type + +Declare CustomType Library + Function SendInput~&(ByVal inputCount As _Unsigned Long, ByVal inputArray As _Offset, ByVal inputTypeSize As _Unsigned Long) + Function GetLastError&() +End Declare + diff --git a/tests/compile_tests/keyboard/sendinput.bm b/tests/compile_tests/keyboard/sendinput.bm new file mode 100644 index 000000000..b1e45d546 --- /dev/null +++ b/tests/compile_tests/keyboard/sendinput.bm @@ -0,0 +1,29 @@ + +Sub emulateScancode(scancode As Integer, isDown As Long) + Dim kbd As KbdInput + + kbd.typ = INPUT_TYPE_KEYBOARD + kbd.scancode = scancode And &H7FFF ' Strip off extended flag + kbd.flags = KBD_FLAG_SCANCODE + If isDown = 0 Then kbd.flags = kbd.flags Or KBD_FLAG_UP + If scancode And &H8000 Then kbd.flags = kbd.flags Or KBD_FLAG_EXTENDED + + e& = SendInput~&(1, _Offset(kbd), LEN(kbd)) + + If e& <> 1 Then Print "ERROR: e:"; e&; ", GetLastError:"; GetLastError + _Delay .05 +End Sub + +Sub emulateVirtualKey(vk As Integer, isDown As Long) + Dim kbd As KbdInput + + kbd.typ = INPUT_TYPE_KEYBOARD + kbd.vk = vk + If isDown = 0 Then kbd.flags = kbd.flags Or KBD_FLAG_UP + + e& = SendInput~&(1, _Offset(kbd), LEN(kbd)) + + If e& <> 1 Then Print "ERROR: e:"; e&; ", GetLastError:"; GetLastError + _Delay .05 +End Sub +