This MR improves the handling of numpad keys for drivers using KBDTABLES (only the Wayland driver at this point). It achieves this by: 1. Allowing drivers to send only the scancode in keyboard events, with win32u performing the scan->vkey mapping internally. A nice side effect of this change is that it fixes a few user32 input test TODOs. 2. Enhancing wineserver to read extended KBD vkey attributes and perform numpad key mapping depending on modifier state. 3. Providing default VK_NUMPAD* -> WCHAR mappings in win32u.
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/win32u/input.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 04532e7d015..38350861c6b 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -521,6 +521,7 @@ static WCHAR kbd_tables_vkey_to_wchar( const KBDTABLES *tables, UINT vkey, const if (vkey >= 'A' && vkey <= 'Z') return vkey - 'A' + 1; tables = &kbdus_tables; } + if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9) tables = &kbdus_tables;
mod = caps_mod = kbd_tables_get_mod_num( tables, state, FALSE ); if (caps) caps_mod = kbd_tables_get_mod_num( tables, state, TRUE );
From: Alexandros Frantzis alexandros.frantzis@collabora.com
The implementation will map the scan code to a virtual key code internally. --- dlls/user32/tests/input.c | 12 ++++-------- dlls/win32u/message.c | 19 ++++++++++++++++--- dlls/winewayland.drv/wayland_keyboard.c | 9 ++++----- 3 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/dlls/user32/tests/input.c b/dlls/user32/tests/input.c index fcaa4229b02..1e7e5cb32f2 100644 --- a/dlls/user32/tests/input.c +++ b/dlls/user32/tests/input.c @@ -1070,16 +1070,13 @@ static void test_SendInput_keyboard_messages( WORD vkey, WORD scan, WCHAR wch, W struct send_input_keyboard_test rshift_scan[] = { {.scan = 0x36, .flags = KEYEVENTF_SCANCODE, .expect_state = {[VK_SHIFT] = 0x80, [VK_LSHIFT] = 0x80}, - .todo_state = {[0] = TRUE, [VK_SHIFT] = TRUE, [VK_LSHIFT] = TRUE}, - .expect = {KEY_HOOK(WM_KEYDOWN, 0x36, VK_RSHIFT, .todo_value = TRUE), KEY_MSG(WM_KEYDOWN, 0x36, VK_SHIFT, .todo_value = TRUE), {0}}}, + .expect = {KEY_HOOK(WM_KEYDOWN, 0x36, VK_RSHIFT), KEY_MSG(WM_KEYDOWN, 0x36, VK_SHIFT), {0}}}, {.scan = scan, .flags = KEYEVENTF_SCANCODE, .expect_state = {[VK_SHIFT] = 0x80, [VK_LSHIFT] = 0x80, /*[vkey] = 0x80*/}, - .todo_state = {[0] = TRUE, [VK_SHIFT] = TRUE, [VK_LSHIFT] = TRUE, /*[vkey] = TRUE*/}, - .expect = {KEY_HOOK(WM_KEYDOWN, scan, vkey, .todo_value = TRUE), KEY_MSG(WM_KEYDOWN, scan, vkey, .todo_value = TRUE), WIN_MSG(WM_CHAR, wch_shift, MAKELONG(1, scan), .todo = TRUE), {0}}}, + .expect = {KEY_HOOK(WM_KEYDOWN, scan, vkey), KEY_MSG(WM_KEYDOWN, scan, vkey), WIN_MSG(WM_CHAR, wch_shift, MAKELONG(1, scan)), {0}}}, {.scan = scan, .flags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, .expect_state = {[VK_SHIFT] = 0x80, [VK_LSHIFT] = 0x80}, - .todo_state = {[VK_SHIFT] = TRUE, [VK_LSHIFT] = TRUE}, - .expect = {KEY_HOOK(WM_KEYUP, scan, vkey, .todo_value = TRUE), KEY_MSG(WM_KEYUP, scan, vkey, .todo_value = TRUE), {0}}}, + .expect = {KEY_HOOK(WM_KEYUP, scan, vkey), KEY_MSG(WM_KEYUP, scan, vkey), {0}}}, {.scan = 0x36, .flags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, - .expect = {KEY_HOOK(WM_KEYUP, 0x36, VK_RSHIFT, .todo_value = TRUE), KEY_MSG(WM_KEYUP, 0x36, VK_SHIFT, .todo_value = TRUE), {0}}}, + .expect = {KEY_HOOK(WM_KEYUP, 0x36, VK_RSHIFT), KEY_MSG(WM_KEYUP, 0x36, VK_SHIFT), {0}}}, {0}, };
@@ -1175,7 +1172,6 @@ static void test_SendInput_keyboard_messages( WORD vkey, WORD scan, WCHAR wch, W lmenu_lcontrol_vkey[2].expect_state[vkey] = 0x80; shift_vkey[1].expect_state[vkey] = 0x80; rshift_scan[1].expect_state[vkey] = 0x80; - rshift_scan[1].todo_state[vkey] = TRUE; unicode_vkey[0].expect_state[vkey] = 0x80;
/* test peeked messages */ diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index 4a368ffb589..ea5ff42d7fe 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -3558,9 +3558,22 @@ NTSTATUS send_hardware_message( HWND hwnd, UINT flags, const INPUT *input, LPARA MOUSEEVENTF_XDOWN | MOUSEEVENTF_XUP)); break; case INPUT_KEYBOARD: - req->input.kbd.vkey = input->ki.wVk; - req->input.kbd.scan = input->ki.wScan; - req->input.kbd.flags = input->ki.dwFlags; + if (input->ki.dwFlags & KEYEVENTF_SCANCODE) + { + UINT scan = input->ki.wScan; + /* TODO: Use the keyboard layout of the target hwnd, once + * NtUserGetKeyboardLayout supports non-current threads. */ + HKL layout = NtUserGetKeyboardLayout( 0 ); + if (input->ki.dwFlags & KEYEVENTF_EXTENDEDKEY) scan |= 0xe000; + req->input.kbd.vkey = NtUserMapVirtualKeyEx( scan, MAPVK_VSC_TO_VK_EX, layout ); + req->input.kbd.scan = input->ki.wScan & 0xff; + } + else + { + req->input.kbd.vkey = input->ki.wVk; + req->input.kbd.scan = input->ki.wScan; + } + req->input.kbd.flags = input->ki.dwFlags & ~KEYEVENTF_SCANCODE; req->input.kbd.time = input->ki.time; req->input.kbd.info = input->ki.dwExtraInfo; affects_key_state = TRUE; diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 04a1c0a9fc3..55b14b1dfd5 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -801,9 +801,8 @@ static void send_right_control(HWND hwnd, uint32_t state) { INPUT input = {0}; input.type = INPUT_KEYBOARD; - input.ki.wScan = key2scan(KEY_RIGHTCTRL); - input.ki.wVk = VK_RCONTROL; - input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + input.ki.wScan = 0xe000 | (key2scan(KEY_RIGHTCTRL) & 0xff); + input.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY; if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP; NtUserSendHardwareInput(hwnd, 0, &input, 0); } @@ -824,8 +823,8 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, if (key == KEY_RIGHTALT) send_right_control(hwnd, state);
input.type = INPUT_KEYBOARD; - input.ki.wScan = scan & 0xff; - input.ki.wVk = NtUserMapVirtualKeyEx(scan, MAPVK_VSC_TO_VK_EX, keyboard_hkl); + input.ki.wScan = (scan & 0x300) ? scan + 0xdf00 : scan; + input.ki.dwFlags = KEYEVENTF_SCANCODE; if (scan & ~0xff) input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY;
if (state == WL_KEYBOARD_KEY_STATE_RELEASED) input.ki.dwFlags |= KEYEVENTF_KEYUP;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/win32u/input.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 38350861c6b..85f4867fff7 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -410,27 +410,27 @@ LONG global_key_state_counter = 0; BOOL grab_pointer = TRUE; BOOL grab_fullscreen = FALSE;
-static void kbd_tables_init_vsc2vk( const KBDTABLES *tables, BYTE vsc2vk[0x300] ) +static void kbd_tables_init_vsc2vk( const KBDTABLES *tables, USHORT vsc2vk[0x300] ) { const VSC_VK *entry; WORD vsc;
- memset( vsc2vk, 0, 0x300 ); + memset( vsc2vk, 0, 0x300 * sizeof(USHORT) );
for (vsc = 0; tables->pusVSCtoVK && vsc <= tables->bMaxVSCtoVK; ++vsc) { if (tables->pusVSCtoVK[vsc] == VK__none_) continue; - vsc2vk[vsc] = (BYTE)tables->pusVSCtoVK[vsc]; + vsc2vk[vsc] = tables->pusVSCtoVK[vsc]; } for (entry = tables->pVSCtoVK_E0; entry && entry->Vsc; entry++) { if (entry->Vk == VK__none_) continue; - vsc2vk[entry->Vsc + 0x100] = (BYTE)entry->Vk; + vsc2vk[entry->Vsc + 0x100] = entry->Vk; } for (entry = tables->pVSCtoVK_E1; entry && entry->Vsc; entry++) { if (entry->Vk == VK__none_) continue; - vsc2vk[entry->Vsc + 0x200] = (BYTE)entry->Vk; + vsc2vk[entry->Vsc + 0x200] = entry->Vk; } }
@@ -1033,7 +1033,8 @@ WORD WINAPI NtUserVkKeyScanEx( WCHAR chr, HKL layout ) */ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout ) { - BYTE vsc2vk[0x300], vk2char[0x100]; + USHORT vsc2vk[0x300]; + BYTE vk2char[0x100]; const KBDTABLES *kbd_tables; UINT ret = 0;
@@ -1066,7 +1067,7 @@ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout ) }
kbd_tables_init_vsc2vk( kbd_tables, vsc2vk ); - for (ret = 0; ret < ARRAY_SIZE(vsc2vk); ++ret) if (vsc2vk[ret] == code) break; + for (ret = 0; ret < ARRAY_SIZE(vsc2vk); ++ret) if ((BYTE)vsc2vk[ret] == code) break; if (ret >= ARRAY_SIZE(vsc2vk)) ret = 0;
if (type == MAPVK_VK_TO_VSC) @@ -1082,7 +1083,7 @@ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout )
if (code & 0xe000) code -= 0xdf00; if (code >= ARRAY_SIZE(vsc2vk)) ret = 0; - else ret = vsc2vk[code]; + else ret = (BYTE)vsc2vk[code];
if (type == MAPVK_VSC_TO_VK) { @@ -1130,15 +1131,15 @@ INT WINAPI NtUserGetKeyNameText( LONG lparam, WCHAR *buffer, INT size )
if (lparam & 0x2000000) { - BYTE vsc2vk[0x300]; + USHORT vsc2vk[0x300]; kbd_tables_init_vsc2vk( kbd_tables, vsc2vk ); - switch ((vkey = vsc2vk[code])) + switch ((vkey = (BYTE)vsc2vk[code])) { case VK_RSHIFT: case VK_RCONTROL: case VK_RMENU: for (code = 0; code < ARRAY_SIZE(vsc2vk); ++code) - if (vsc2vk[code] == (vkey - 1)) break; + if ((BYTE)vsc2vk[code] == (vkey - 1)) break; break; } }
From: Alexandros Frantzis alexandros.frantzis@collabora.com
If the hardware keyboard event has KBD virtual key information, use it to detect numpad key events and translate them to the appropriate virtual keys depending on NumLock (and Shift) state. --- dlls/win32u/input.c | 25 +++++++++++++++++++++++++ dlls/win32u/message.c | 2 +- dlls/win32u/win32u_private.h | 1 + server/queue.c | 22 ++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/dlls/win32u/input.c b/dlls/win32u/input.c index 85f4867fff7..2b391cbe9e7 100644 --- a/dlls/win32u/input.c +++ b/dlls/win32u/input.c @@ -1112,6 +1112,31 @@ UINT WINAPI NtUserMapVirtualKeyEx( UINT code, UINT type, HKL layout ) return ret; }
+/*********************************************************************** + * map_scan_to_kbd_vkey + * + * Map a scancode to a virtual key with KBD information. + */ +USHORT map_scan_to_kbd_vkey( USHORT scan, HKL layout ) +{ + const KBDTABLES *kbd_tables; + USHORT vsc2vk[0x300]; + UINT vkey; + + if ((vkey = user_driver->pMapVirtualKeyEx( scan, MAPVK_VSC_TO_VK_EX, layout )) != -1) return vkey; + + if (!(kbd_tables = user_driver->pKbdLayerDescriptor( layout ))) kbd_tables = &kbdus_tables; + + kbd_tables_init_vsc2vk( kbd_tables, vsc2vk ); + if (scan & 0xe000) scan -= 0xdf00; + if (scan >= ARRAY_SIZE(vsc2vk)) vkey = 0; + else vkey = vsc2vk[scan]; + + if (kbd_tables != &kbdus_tables) user_driver->pReleaseKbdTables( kbd_tables ); + + return vkey; +} + /**************************************************************************** * NtUserGetKeyNameText (win32u.@) */ diff --git a/dlls/win32u/message.c b/dlls/win32u/message.c index ea5ff42d7fe..459a26998b0 100644 --- a/dlls/win32u/message.c +++ b/dlls/win32u/message.c @@ -3565,7 +3565,7 @@ NTSTATUS send_hardware_message( HWND hwnd, UINT flags, const INPUT *input, LPARA * NtUserGetKeyboardLayout supports non-current threads. */ HKL layout = NtUserGetKeyboardLayout( 0 ); if (input->ki.dwFlags & KEYEVENTF_EXTENDEDKEY) scan |= 0xe000; - req->input.kbd.vkey = NtUserMapVirtualKeyEx( scan, MAPVK_VSC_TO_VK_EX, layout ); + req->input.kbd.vkey = map_scan_to_kbd_vkey( scan, layout ); req->input.kbd.scan = input->ki.wScan & 0xff; } else diff --git a/dlls/win32u/win32u_private.h b/dlls/win32u/win32u_private.h index 24e8b1b498d..94b9d00b4fb 100644 --- a/dlls/win32u/win32u_private.h +++ b/dlls/win32u/win32u_private.h @@ -108,6 +108,7 @@ extern void update_mouse_tracking_info( HWND hwnd ); extern BOOL get_clip_cursor( RECT *rect ); extern BOOL process_wine_clipcursor( HWND hwnd, UINT flags, BOOL reset ); extern BOOL clip_fullscreen_window( HWND hwnd, BOOL reset ); +extern USHORT map_scan_to_kbd_vkey( USHORT scan, HKL layout );
/* menu.c */ extern HMENU create_menu( BOOL is_popup ); diff --git a/server/queue.c b/server/queue.c index ed099b3b989..80d9dedbd90 100644 --- a/server/queue.c +++ b/server/queue.c @@ -36,6 +36,7 @@ #include "winternl.h" #include "ntuser.h" #include "hidusage.h" +#include "kbd.h"
#include "handle.h" #include "file.h" @@ -2171,6 +2172,27 @@ static int queue_keyboard_message( struct desktop *desktop, user_handle_t win, c break; }
+ /* send numpad vkeys if NumLock is active */ + if ((input->kbd.vkey & KBDNUMPAD) && (desktop->keystate[VK_NUMLOCK] & 0x01) && + !(desktop->keystate[VK_SHIFT] & 0x80)) + { + switch (vkey) + { + case VK_INSERT: vkey = VK_NUMPAD0; break; + case VK_END: vkey = VK_NUMPAD1; break; + case VK_DOWN: vkey = VK_NUMPAD2; break; + case VK_NEXT: vkey = VK_NUMPAD3; break; + case VK_LEFT: vkey = VK_NUMPAD4; break; + case VK_CLEAR: vkey = VK_NUMPAD5; break; + case VK_RIGHT: vkey = VK_NUMPAD6; break; + case VK_HOME: vkey = VK_NUMPAD7; break; + case VK_UP: vkey = VK_NUMPAD8; break; + case VK_PRIOR: vkey = VK_NUMPAD9; break; + case VK_DELETE: vkey = VK_DECIMAL; break; + default: break; + } + } + if ((foreground = get_foreground_thread( desktop, win ))) { struct rawinput_message raw_msg = {0};
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland_keyboard.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/dlls/winewayland.drv/wayland_keyboard.c b/dlls/winewayland.drv/wayland_keyboard.c index 55b14b1dfd5..bdef56e8f0c 100644 --- a/dlls/winewayland.drv/wayland_keyboard.c +++ b/dlls/winewayland.drv/wayland_keyboard.c @@ -358,7 +358,7 @@ static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap
unsigned int mod, keyc, len, names_len, min_keycode, max_keycode; struct xkb_state *xkb_state = xkb_state_new(xkb_keymap); - xkb_mod_mask_t shift_mask, control_mask, altgr_mask, capslock_mask; + xkb_mod_mask_t shift_mask, control_mask, altgr_mask, capslock_mask, numlock_mask; VSC_LPWSTR *names_entry, *names_ext_entry; VSC_VK *vsc2vk_e0_entry, *vsc2vk_e1_entry; VK_TO_WCHARS8 *vk2wchars_entry; @@ -493,6 +493,7 @@ static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap control_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_CTRL); capslock_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_CAPS); altgr_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, "Mod5"); + numlock_mask = 1 << xkb_keymap_mod_get_index(xkb_keymap, XKB_MOD_NAME_NUM);
for (keyc = min_keycode; keyc <= max_keycode; keyc++) { @@ -502,6 +503,19 @@ static void add_xkb_layout(const char *xkb_layout, struct xkb_keymap *xkb_keymap uint32_t caps_ret, shift_ret; unsigned int mod;
+ if ((vkey & KBDNUMPAD) && (vkey & 0xff) == VK_DELETE) + { + VK_TO_WCHARS8 num_vkey2wch = {.VirtualKey = VK_DECIMAL}; + + xkb_state_update_mask(xkb_state, 0, 0, numlock_mask, 0, 0, xkb_group); + if (!(num_vkey2wch.wch[0] = xkb_state_key_get_utf32(xkb_state, keyc))) + num_vkey2wch.wch[0] = WCH_NONE; + for (mod = 1; mod < 8; ++mod) num_vkey2wch.wch[mod] = WCH_NONE; + num_vkey2wch.Attributes = 0; + TRACE("vkey %#06x -> %s\n", num_vkey2wch.VirtualKey, debugstr_wn(num_vkey2wch.wch, 8)); + *vk2wchars_entry++ = num_vkey2wch; + } + for (mod = 0; mod < 8; ++mod) { xkb_mod_mask_t mod_mask = 0;
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=145460
Your paranoid android.
=== debian11 (build log) ===
error: patch failed: dlls/winemac.drv/cocoa_app.m:304 error: patch failed: dlls/win32u/message.c:3565 Task: Patch failed to apply
=== debian11b (build log) ===
error: patch failed: dlls/winemac.drv/cocoa_app.m:304 error: patch failed: dlls/win32u/message.c:3565 Task: Patch failed to apply
What about doing that conditionally, as the driver can provide the full scan code and wouldn't need the KEYEVENTF_EXTENDEDKEY flag.
Something like:
```suggestion:-15+0 if (input->ki.dwFlags & KEYEVENTF_SCANCODE) { UINT scan = input->ki.wScan; /* TODO: Use the keyboard layout of the target hwnd, once * NtUserGetKeyboardLayout supports non-current threads. */ HKL layout = NtUserGetKeyboardLayout( 0 ); if (flags & SEND_HWMSG_INJECTED) { scan = scan & 0xff; if (input->ki.dwFlags & KEYEVENTF_EXTENDEDKEY) scan |= 0xe000; } req->input.kbd.vkey = NtUserMapVirtualKeyEx( scan, MAPVK_VSC_TO_VK_EX, layout ); req->input.kbd.scan = input->ki.wScan & 0xff; } else { req->input.kbd.vkey = input->ki.wVk; req->input.kbd.scan = input->ki.wScan; } req->input.kbd.flags = input->ki.dwFlags & ~KEYEVENTF_SCANCODE; ```
And then add a test to the SendInput tests above showing that this flag what is used for injected input? Something like:
```c struct send_input_keyboard_test rctrl_scan[] = { {.scan = 0xe01d, .flags = KEYEVENTF_SCANCODE, .expect_state = {[VK_CONTROL] = 0x80, [VK_LCONTROL] = 0x80}, .todo_state = {[0] = TRUE, [VK_CONTROL] = TRUE, [VK_LCONTROL] = TRUE}, .expect = {KEY_HOOK(WM_KEYDOWN, 0x1d, VK_LCONTROL, .todo_value = TRUE), KEY_MSG(WM_KEYDOWN, 0x1d, VK_CONTROL, .todo_value = TRUE), {0}}}, {.scan = 0xe01d, .flags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP, .expect = {KEY_HOOK(WM_KEYUP, 0x1d, VK_LCONTROL, .todo_value = TRUE), KEY_MSG(WM_KEYUP, 0x1d, VK_CONTROL, .todo_value = TRUE), {0}}}, {.scan = 0x1d, .flags = KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY, .expect_state = {[VK_CONTROL] = 0x80, [VK_RCONTROL] = 0x80}, .todo_state = {[0] = TRUE, [VK_CONTROL] = TRUE, [VK_RCONTROL] = TRUE}, .expect = {KEY_HOOK_(WM_KEYDOWN, 0x1d, VK_RCONTROL, LLKHF_EXTENDED, .todo_value = TRUE), KEY_MSG(WM_KEYDOWN, 0x11d, VK_CONTROL, .todo_value = TRUE), {0}}}, {.scan = 0x1d, .flags = KEYEVENTF_SCANCODE | KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, .expect = {KEY_HOOK_(WM_KEYUP, 0x1d, VK_RCONTROL, LLKHF_EXTENDED, .todo_value = TRUE), KEY_MSG(WM_KEYUP, 0x11d, VK_CONTROL, .todo_value = TRUE), {0}}}, {0}, }; ```
IMO a mask is more appropriate, it's not even like code is a BYTE itself. Same for the other instances below.