SDL: windows: enable raw keyboard input when raw mouse input is enabled

From 012fc1e32b4a8b7fb79ab769566bb34c3f6502e6 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 25 Mar 2024 09:49:35 -0700
Subject: [PATCH] windows: enable raw keyboard input when raw mouse input is
 enabled

---
 src/video/windows/SDL_windowsevents.c | 54 +++++++++++----
 src/video/windows/SDL_windowsevents.h |  2 +-
 src/video/windows/SDL_windowsmouse.c  | 96 +++++++++++++++------------
 src/video/windows/SDL_windowsvideo.h  |  2 +
 4 files changed, 96 insertions(+), 58 deletions(-)

diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 9409c3fbf46a7..96dfcb07ca4cb 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -512,9 +512,13 @@ WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
     }
 
     if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
-        SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, scanCode);
+        if (data->raw_input_enable_count == 0) {
+            SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, scanCode);
+        }
     } else {
-        SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_RELEASED, scanCode);
+        if (data->raw_input_enable_count == 0) {
+            SDL_SendKeyboardKey(0, SDL_GLOBAL_KEYBOARD_ID, SDL_RELEASED, scanCode);
+        }
 
         /* If the key was down prior to our hook being installed, allow the
            key up message to pass normally the first time. This ensures other
@@ -532,8 +536,14 @@ WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
 
 static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_WindowData *data, HANDLE hDevice, RAWMOUSE *rawmouse)
 {
+    SDL_Mouse *mouse = SDL_GetMouse();
     SDL_MouseID mouseID;
 
+    /* We only use raw mouse input in relative mode */
+    if (!mouse->relative_mode || mouse->relative_mode_warp) {
+        return;
+    }
+
     if (GetMouseMessageSource(rawmouse->ulExtraInformation) == SDL_MOUSE_EVENT_SOURCE_TOUCH) {
         return;
     }
@@ -619,17 +629,34 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_WindowData *data, HAND
     WIN_CheckRawMouseButtons(timestamp, hDevice, rawmouse->usButtonFlags, data, mouseID);
 }
 
-void WIN_PollRawMouseInput(void)
+static void WIN_HandleRawKeyboardInput(Uint64 timestamp, SDL_WindowData *data, HANDLE hDevice, RAWKEYBOARD *rawkeyboard)
+{
+    SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)hDevice;
+
+    Uint8 state = (rawkeyboard->Flags & RI_KEY_BREAK) ? SDL_RELEASED : SDL_PRESSED;
+    Uint16 scanCode = rawkeyboard->MakeCode;
+    if (rawkeyboard->Flags & RI_KEY_E0) {
+        scanCode |= (0xE0 << 8);
+    } else if (rawkeyboard->Flags & RI_KEY_E1) {
+        scanCode |= (0xE1 << 8);
+    }
+
+    // Pack scan code into one byte to make the index
+    Uint8 index = LOBYTE(scanCode) | (HIBYTE(scanCode) ? 0x80 : 0x00);
+    SDL_Scancode code = windows_scancode_table[index];
+
+    SDL_SendKeyboardKey(timestamp, keyboardID, state, code);
+}
+
+void WIN_PollRawInput(SDL_VideoDevice *_this)
 {
-    SDL_Mouse *mouse = SDL_GetMouse();
     SDL_Window *window;
     SDL_WindowData *data;
     UINT size, count, i, total = 0;
     RAWINPUT *input;
     Uint64 now;
 
-    /* We only use raw mouse input in relative mode */
-    if (!mouse->relative_mode || mouse->relative_mode_warp) {
+    if (_this->driverdata->raw_input_enable_count == 0) {
         return;
     }
 
@@ -695,6 +722,9 @@ void WIN_PollRawMouseInput(void)
             if (input->header.dwType == RIM_TYPEMOUSE) {
                 RAWMOUSE *rawmouse = (RAWMOUSE *)((BYTE *)input + data->rawinput_offset);
                 WIN_HandleRawMouseInput(timestamp, window->driverdata, input->header.hDevice, rawmouse);
+            } else if (input->header.dwType == RIM_TYPEKEYBOARD) {
+                RAWKEYBOARD *rawkeyboard = (RAWKEYBOARD *)((BYTE *)input + data->rawinput_offset);
+                WIN_HandleRawKeyboardInput(timestamp, window->driverdata, input->header.hDevice, rawkeyboard);
             }
         }
     }
@@ -1024,13 +1054,11 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
 #if 0   /* We handle raw input all at once instead of using a syscall for each mouse event */
     case WM_INPUT:
     {
-        SDL_Mouse *mouse = SDL_GetMouse();
         HRAWINPUT hRawInput = (HRAWINPUT)lParam;
         RAWINPUT inp;
         UINT size = sizeof(inp);
 
-        /* We only use raw mouse input in relative mode */
-        if (!mouse->relative_mode || mouse->relative_mode_warp) {
+        if (data->raw_input_enable_count == 0) {
             break;
         }
 
@@ -1040,10 +1068,10 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
         }
 
         GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
-
-        /* Mouse data (ignoring synthetic mouse events generated for touchscreens) */
         if (inp.header.dwType == RIM_TYPEMOUSE) {
             WIN_HandleRawMouseInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.mouse);
+        } else if (inp.header.dwType == RIM_TYPEKEYBOARD) {
+            WIN_HandleRawKeyboardInput(WIN_GetEventTimestamp(), data, inp.header.hDevice, &inp.data.keyboard);
         }
     } break;
 #endif
@@ -1107,7 +1135,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
             }
         }
 
-        if (code != SDL_SCANCODE_UNKNOWN) {
+        if (data->videodata->raw_input_enable_count == 0 && code != SDL_SCANCODE_UNKNOWN) {
             SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, code);
         }
     }
@@ -1121,7 +1149,7 @@ LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPara
         SDL_Scancode code = WindowsScanCodeToSDLScanCode(lParam, wParam);
         const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
 
-        if (code != SDL_SCANCODE_UNKNOWN) {
+        if (data->videodata->raw_input_enable_count == 0 && code != SDL_SCANCODE_UNKNOWN) {
             if (code == SDL_SCANCODE_PRINTSCREEN &&
                 keyboardState[code] == SDL_RELEASED) {
                 SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_GLOBAL_KEYBOARD_ID, SDL_PRESSED, code);
diff --git a/src/video/windows/SDL_windowsevents.h b/src/video/windows/SDL_windowsevents.h
index 1b01ba4bdd6d4..5acd69b12c221 100644
--- a/src/video/windows/SDL_windowsevents.h
+++ b/src/video/windows/SDL_windowsevents.h
@@ -30,7 +30,7 @@ extern HINSTANCE SDL_Instance;
 extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam);
 extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam,
                                        LPARAM lParam);
-extern void WIN_PollRawMouseInput(void);
+extern void WIN_PollRawInput(SDL_VideoDevice *_this);
 extern void WIN_CheckKeyboardAndMouseHotplug(SDL_VideoDevice *_this, SDL_bool initial_check);
 extern void WIN_PumpEvents(SDL_VideoDevice *_this);
 extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index 5485f0213211a..74ddae62472e3 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -33,24 +33,24 @@ DWORD SDL_last_warp_time = 0;
 HCURSOR SDL_cursor = NULL;
 static SDL_Cursor *SDL_blank_cursor = NULL;
 
-static int rawInputEnableCount = 0;
-
 typedef struct
 {
     HANDLE ready_event;
     HANDLE done_event;
     HANDLE thread;
-} RawMouseThreadData;
+} RawInputThreadData;
 
-static RawMouseThreadData thread_data = {
+static RawInputThreadData thread_data = {
     INVALID_HANDLE_VALUE,
     INVALID_HANDLE_VALUE,
     INVALID_HANDLE_VALUE
 };
 
-static DWORD WINAPI WIN_RawMouseThread(LPVOID param)
+static DWORD WINAPI WIN_RawInputThread(LPVOID param)
 {
-    RAWINPUTDEVICE rawMouse;
+    SDL_VideoDevice *_this = SDL_GetVideoDevice();
+    RawInputThreadData *data = (RawInputThreadData *)param;
+    RAWINPUTDEVICE devices[2];
     HWND window;
 
     window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
@@ -58,76 +58,83 @@ static DWORD WINAPI WIN_RawMouseThread(LPVOID param)
         return 0;
     }
 
-    rawMouse.usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
-    rawMouse.usUsage = USB_USAGE_GENERIC_MOUSE;
-    rawMouse.dwFlags = 0;
-    rawMouse.hwndTarget = window;
+    devices[0].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
+    devices[0].usUsage = USB_USAGE_GENERIC_MOUSE;
+    devices[0].dwFlags = 0;
+    devices[0].hwndTarget = window;
+
+    devices[1].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
+    devices[1].usUsage = USB_USAGE_GENERIC_KEYBOARD;
+    devices[1].dwFlags = 0;
+    devices[1].hwndTarget = window;
 
-    if (!RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse))) {
+    if (!RegisterRawInputDevices(devices, SDL_arraysize(devices), sizeof(devices[0]))) {
         DestroyWindow(window);
         return 0;
     }
 
-    /* Make sure we get mouse events as soon as possible */
+    /* Make sure we get events as soon as possible */
     SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
 
     /* Tell the parent we're ready to go! */
-    SetEvent(thread_data.ready_event);
+    SetEvent(data->ready_event);
 
     for ( ; ; ) {
-        if (MsgWaitForMultipleObjects(1, &thread_data.done_event, 0, INFINITE, QS_RAWINPUT) != WAIT_OBJECT_0 + 1) {
+        if (MsgWaitForMultipleObjects(1, &data->done_event, 0, INFINITE, QS_RAWINPUT) != WAIT_OBJECT_0 + 1) {
             break;
         }
 
         /* Clear the queue status so MsgWaitForMultipleObjects() will wait again */
         (void)GetQueueStatus(QS_RAWINPUT);
 
-        WIN_PollRawMouseInput();
+        WIN_PollRawInput(_this);
     }
 
-    rawMouse.dwFlags |= RIDEV_REMOVE;
-    RegisterRawInputDevices(&rawMouse, 1, sizeof(rawMouse));
+    devices[0].dwFlags |= RIDEV_REMOVE;
+    devices[1].dwFlags |= RIDEV_REMOVE;
+    RegisterRawInputDevices(devices, SDL_arraysize(devices), sizeof(devices[0]));
 
     DestroyWindow(window);
 
     return 0;
 }
 
-static void CleanupRawMouseThreadData(void)
+static void CleanupRawInputThreadData(RawInputThreadData *data)
 {
-    if (thread_data.thread != INVALID_HANDLE_VALUE) {
-        SetEvent(thread_data.done_event);
-        WaitForSingleObject(thread_data.thread, 500);
-        CloseHandle(thread_data.thread);
-        thread_data.thread = INVALID_HANDLE_VALUE;
+    if (data->thread != INVALID_HANDLE_VALUE) {
+        SetEvent(data->done_event);
+        WaitForSingleObject(data->thread, 500);
+        CloseHandle(data->thread);
+        data->thread = INVALID_HANDLE_VALUE;
     }
 
-    if (thread_data.ready_event != INVALID_HANDLE_VALUE) {
-        CloseHandle(thread_data.ready_event);
-        thread_data.ready_event = INVALID_HANDLE_VALUE;
+    if (data->ready_event != INVALID_HANDLE_VALUE) {
+        CloseHandle(data->ready_event);
+        data->ready_event = INVALID_HANDLE_VALUE;
     }
 
-    if (thread_data.done_event != INVALID_HANDLE_VALUE) {
-        CloseHandle(thread_data.done_event);
-        thread_data.done_event = INVALID_HANDLE_VALUE;
+    if (data->done_event != INVALID_HANDLE_VALUE) {
+        CloseHandle(data->done_event);
+        data->done_event = INVALID_HANDLE_VALUE;
     }
 }
 
-static int ToggleRawInput(SDL_bool enabled)
+static int ToggleRawInput(SDL_VideoDevice *_this, SDL_bool enabled)
 {
+    SDL_VideoData *data = _this->driverdata;
     int result = -1;
 
     if (enabled) {
-        rawInputEnableCount++;
-        if (rawInputEnableCount > 1) {
+        ++data->raw_input_enable_count;
+        if (data->raw_input_enable_count > 1) {
             return 0; /* already done. */
         }
     } else {
-        if (rawInputEnableCount == 0) {
+        if (data->raw_input_enable_count == 0) {
             return 0; /* already done. */
         }
-        rawInputEnableCount--;
-        if (rawInputEnableCount > 0) {
+        --data->raw_input_enable_count;
+        if (data->raw_input_enable_count > 0) {
             return 0; /* not time to disable yet */
         }
     }
@@ -147,7 +154,7 @@ static int ToggleRawInput(SDL_bool enabled)
             goto done;
         }
 
-        thread_data.thread = CreateThread(NULL, 0, WIN_RawMouseThread, &thread_data, 0, NULL);
+        thread_data.thread = CreateThread(NULL, 0, WIN_RawInputThread, &thread_data, 0, NULL);
         if (thread_data.thread == INVALID_HANDLE_VALUE) {
             WIN_SetError("CreateThread");
             goto done;
@@ -162,16 +169,16 @@ static int ToggleRawInput(SDL_bool enabled)
         }
         result = 0;
     } else {
-        CleanupRawMouseThreadData();
+        CleanupRawInputThreadData(&thread_data);
         result = 0;
     }
 
 done:
     if (enabled && result < 0) {
-        CleanupRawMouseThreadData();
+        CleanupRawInputThreadData(&thread_data);
 
-        /* Reset rawInputEnableCount so we can try again */
-        rawInputEnableCount = 0;
+        /* Reset so we can try again */
+        data->raw_input_enable_count = 0;
     }
     return result;
 }
@@ -509,7 +516,7 @@ static int WIN_WarpMouseGlobal(float x, float y)
 
 static int WIN_SetRelativeMouseMode(SDL_bool enabled)
 {
-    return ToggleRawInput(enabled);
+    return ToggleRawInput(SDL_GetVideoDevice(), enabled);
 }
 
 static int WIN_CaptureMouse(SDL_Window *window)
@@ -574,9 +581,10 @@ void WIN_InitMouse(SDL_VideoDevice *_this)
 
 void WIN_QuitMouse(SDL_VideoDevice *_this)
 {
-    if (rawInputEnableCount) { /* force RAWINPUT off here. */
-        rawInputEnableCount = 1;
-        ToggleRawInput(SDL_FALSE);
+    SDL_VideoData *data = _this->driverdata;
+    if (data->raw_input_enable_count) { /* force RAWINPUT off here. */
+        data->raw_input_enable_count = 1;
+        ToggleRawInput(_this, SDL_FALSE);
     }
 
     if (SDL_blank_cursor) {
diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h
index 4b3d3c27acbfa..9dbcc01006f96 100644
--- a/src/video/windows/SDL_windowsvideo.h
+++ b/src/video/windows/SDL_windowsvideo.h
@@ -406,6 +406,8 @@ struct SDL_VideoData
 
     SDL_bool cleared;
 
+    int raw_input_enable_count;
+
 #ifndef SDL_DISABLE_WINDOWS_IME
     SDL_bool ime_com_initialized;
     struct ITfThreadMgr *ime_threadmgr;