SDL: Initial support for hotplugging mice and keyboards

From 2fe1a6a27960126041590d7bbf8080fb5228b1fb Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 20 Mar 2024 06:58:00 -0700
Subject: [PATCH] Initial support for hotplugging mice and keyboards

---
 include/SDL3/SDL_events.h                   |  33 +++-
 include/SDL3/SDL_gamepad.h                  |  12 ++
 include/SDL3/SDL_joystick.h                 |  12 ++
 include/SDL3/SDL_keyboard.h                 |  27 +++
 include/SDL3/SDL_mouse.h                    |  25 +++
 src/core/haiku/SDL_BApp.h                   |   2 +-
 src/core/linux/SDL_evdev.c                  |   4 +-
 src/core/openbsd/SDL_wscons_kbd.c           |  12 +-
 src/core/windows/SDL_hid.c                  | 172 +++++++++++++++++++
 src/core/windows/SDL_hid.h                  |  17 +-
 src/core/winrt/SDL_winrtapp_direct3d.cpp    |   4 +-
 src/dynapi/SDL_dynapi.sym                   |   6 +
 src/dynapi/SDL_dynapi_overrides.h           |   6 +
 src/dynapi/SDL_dynapi_procs.h               |   6 +
 src/events/SDL_events.c                     |  18 ++
 src/events/SDL_keyboard.c                   | 146 ++++++++++++++--
 src/events/SDL_keyboard_c.h                 |  15 +-
 src/events/SDL_mouse.c                      | 145 +++++++++++++++-
 src/events/SDL_mouse_c.h                    |  11 +-
 src/joystick/SDL_gamepad.c                  |  20 +++
 src/joystick/SDL_joystick.c                 |  19 +++
 src/joystick/android/SDL_sysjoystick.c      |   4 +-
 src/joystick/windows/SDL_windowsjoystick.c  | 175 +++-----------------
 src/test/SDL_test_common.c                  |  20 ++-
 src/video/android/SDL_androidkeyboard.c     |   4 +-
 src/video/cocoa/SDL_cocoakeyboard.m         |  10 +-
 src/video/cocoa/SDL_cocoawindow.m           |   4 +-
 src/video/emscripten/SDL_emscriptenevents.c |   2 +-
 src/video/haiku/SDL_BApp.h                  |   2 +-
 src/video/ngage/SDL_ngageevents.cpp         |   4 +-
 src/video/psp/SDL_pspevents.c               |   4 +-
 src/video/qnx/SDL_qnxkeyboard.c             |   4 +-
 src/video/riscos/SDL_riscosevents.c         |   4 +-
 src/video/uikit/SDL_uikitevents.h           |   2 -
 src/video/uikit/SDL_uikitevents.m           |  46 ++---
 src/video/uikit/SDL_uikitview.m             |  16 +-
 src/video/vita/SDL_vitakeyboard.c           |  56 +++----
 src/video/wayland/SDL_waylandevents.c       |   6 +-
 src/video/windows/SDL_windowsevents.c       | 154 +++++++++++++++--
 src/video/windows/SDL_windowsevents.h       |   1 +
 src/video/windows/SDL_windowsvideo.c        |   4 +
 src/video/winrt/SDL_winrtkeyboard.cpp       |   2 +-
 src/video/x11/SDL_x11events.c               |  12 +-
 test/testhotplug.c                          |  24 ++-
 44 files changed, 956 insertions(+), 316 deletions(-)

diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 4a615f61b8614..619197361f8e2 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -143,12 +143,16 @@ typedef enum
     SDL_EVENT_TEXT_INPUT,              /**< Keyboard text input */
     SDL_EVENT_KEYMAP_CHANGED,          /**< Keymap changed due to a system event such as an
                                             input language or keyboard layout change. */
+    SDL_EVENT_KEYBOARD_ADDED,          /**< A new keyboard has been inserted into the system */
+    SDL_EVENT_KEYBOARD_REMOVED,        /**< A keyboard has been removed */
 
     /* Mouse events */
     SDL_EVENT_MOUSE_MOTION    = 0x400, /**< Mouse moved */
     SDL_EVENT_MOUSE_BUTTON_DOWN,       /**< Mouse button pressed */
     SDL_EVENT_MOUSE_BUTTON_UP,         /**< Mouse button released */
     SDL_EVENT_MOUSE_WHEEL,             /**< Mouse wheel motion */
+    SDL_EVENT_MOUSE_ADDED,             /**< A new mouse has been inserted into the system */
+    SDL_EVENT_MOUSE_REMOVED,           /**< A mouse has been removed */
 
     /* Joystick events */
     SDL_EVENT_JOYSTICK_AXIS_MOTION  = 0x600, /**< Joystick axis motion */
@@ -270,6 +274,17 @@ typedef struct SDL_WindowEvent
     Sint32 data2;       /**< event dependent data */
 } SDL_WindowEvent;
 
+/**
+ *  Keyboard device event structure (event.kdevice.*)
+ */
+typedef struct SDL_KeyboardDeviceEvent
+{
+    SDL_EventType type; /**< ::SDL_EVENT_KEYBOARD_ADDED or ::SDL_EVENT_KEYBOARD_REMOVED */
+    Uint32 reserved;
+    Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
+    SDL_KeyboardID which;   /**< The keyboard instance id */
+} SDL_KeyboardDeviceEvent;
+
 /**
  *  Keyboard button event structure (event.key.*)
  */
@@ -279,6 +294,7 @@ typedef struct SDL_KeyboardEvent
     Uint32 reserved;
     Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
     SDL_WindowID windowID; /**< The window with keyboard focus, if any */
+    SDL_KeyboardID which;  /**< The keyboard instance id, or 0 if unknown or virtual */
     Uint8 state;        /**< ::SDL_PRESSED or ::SDL_RELEASED */
     Uint8 repeat;       /**< Non-zero if this is a key repeat */
     Uint8 padding2;
@@ -320,6 +336,17 @@ typedef struct SDL_TextInputEvent
     char *text;         /**< The input text */
 } SDL_TextInputEvent;
 
+/**
+ *  Mouse device event structure (event.mdevice.*)
+ */
+typedef struct SDL_MouseDeviceEvent
+{
+    SDL_EventType type; /**< ::SDL_EVENT_MOUSE_ADDED or ::SDL_EVENT_MOUSE_REMOVED */
+    Uint32 reserved;
+    Uint64 timestamp;   /**< In nanoseconds, populated using SDL_GetTicksNS() */
+    SDL_MouseID which;  /**< The mouse instance id */
+} SDL_MouseDeviceEvent;
+
 /**
  *  Mouse motion event structure (event.motion.*)
  */
@@ -718,21 +745,23 @@ typedef union SDL_Event
     SDL_CommonEvent common;                 /**< Common event data */
     SDL_DisplayEvent display;               /**< Display event data */
     SDL_WindowEvent window;                 /**< Window event data */
+    SDL_KeyboardDeviceEvent kdevice;        /**< Keyboard device change event data */
     SDL_KeyboardEvent key;                  /**< Keyboard event data */
     SDL_TextEditingEvent edit;              /**< Text editing event data */
     SDL_TextInputEvent text;                /**< Text input event data */
+    SDL_MouseDeviceEvent mdevice;           /**< Mouse device change event data */
     SDL_MouseMotionEvent motion;            /**< Mouse motion event data */
     SDL_MouseButtonEvent button;            /**< Mouse button event data */
     SDL_MouseWheelEvent wheel;              /**< Mouse wheel event data */
+    SDL_JoyDeviceEvent jdevice;             /**< Joystick device change event data */
     SDL_JoyAxisEvent jaxis;                 /**< Joystick axis event data */
     SDL_JoyBallEvent jball;                 /**< Joystick ball event data */
     SDL_JoyHatEvent jhat;                   /**< Joystick hat event data */
     SDL_JoyButtonEvent jbutton;             /**< Joystick button event data */
-    SDL_JoyDeviceEvent jdevice;             /**< Joystick device change event data */
     SDL_JoyBatteryEvent jbattery;           /**< Joystick battery event data */
+    SDL_GamepadDeviceEvent gdevice;         /**< Gamepad device event data */
     SDL_GamepadAxisEvent gaxis;             /**< Gamepad axis event data */
     SDL_GamepadButtonEvent gbutton;         /**< Gamepad button event data */
-    SDL_GamepadDeviceEvent gdevice;         /**< Gamepad device event data */
     SDL_GamepadTouchpadEvent gtouchpad;     /**< Gamepad touchpad event data */
     SDL_GamepadSensorEvent gsensor;         /**< Gamepad sensor event data */
     SDL_AudioDeviceEvent adevice;           /**< Audio device event data */
diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index 1b2f25f59ad21..7a9b7e82603ab 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -389,6 +389,17 @@ extern DECLSPEC char * SDLCALL SDL_GetGamepadMapping(SDL_Gamepad *gamepad);
  */
 extern DECLSPEC int SDLCALL SDL_SetGamepadMapping(SDL_JoystickID instance_id, const char *mapping);
 
+/**
+ * Return whether a gamepad is currently connected.
+ *
+ * \returns SDL_TRUE if a gamepad is connected, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetGamepads
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasGamepad(void);
+
 /**
  * Get a list of currently connected gamepads.
  *
@@ -399,6 +410,7 @@ extern DECLSPEC int SDLCALL SDL_SetGamepadMapping(SDL_JoystickID instance_id, co
  *
  * \since This function is available since SDL 3.0.0.
  *
+ * \sa SDL_HasGamepad
  * \sa SDL_OpenGamepad
  */
 extern DECLSPEC SDL_JoystickID *SDLCALL SDL_GetGamepads(int *count);
diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h
index b0135c0243c3b..4708aa7ba40bf 100644
--- a/include/SDL3/SDL_joystick.h
+++ b/include/SDL3/SDL_joystick.h
@@ -137,6 +137,17 @@ extern DECLSPEC void SDLCALL SDL_LockJoysticks(void) SDL_ACQUIRE(SDL_joystick_lo
  */
 extern DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_lock);
 
+/**
+ * Return whether a joystick is currently connected.
+ *
+ * \returns SDL_TRUE if a joystick is connected, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetJoysticks
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasJoystick(void);
+
 /**
  * Get a list of currently connected joysticks.
  *
@@ -147,6 +158,7 @@ extern DECLSPEC void SDLCALL SDL_UnlockJoysticks(void) SDL_RELEASE(SDL_joystick_
  *
  * \since This function is available since SDL 3.0.0.
  *
+ * \sa SDL_HasJoystick
  * \sa SDL_OpenJoystick
  */
 extern DECLSPEC SDL_JoystickID *SDLCALL SDL_GetJoysticks(int *count);
diff --git a/include/SDL3/SDL_keyboard.h b/include/SDL3/SDL_keyboard.h
index 08ca35c30a430..d2a8b1cfbaeef 100644
--- a/include/SDL3/SDL_keyboard.h
+++ b/include/SDL3/SDL_keyboard.h
@@ -39,6 +39,8 @@
 extern "C" {
 #endif
 
+typedef Uint32 SDL_KeyboardID;
+
 /**
  *  The SDL keysym structure, used in key events.
  *
@@ -54,6 +56,31 @@ typedef struct SDL_Keysym
 
 /* Function prototypes */
 
+/**
+ * Return whether a keyboard is currently connected.
+ *
+ * \returns SDL_TRUE if a keyboard is connected, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetKeyboards
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasKeyboard(void);
+
+/**
+ * Get a list of currently connected keyboards.
+ *
+ * \param count a pointer filled in with the number of keyboards returned
+ * \returns a 0 terminated array of keyboards instance IDs which should be
+ *          freed with SDL_free(), or NULL on error; call SDL_GetError() for
+ *          more details.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasKeyboard
+ */
+extern DECLSPEC SDL_KeyboardID *SDLCALL SDL_GetKeyboards(int *count);
+
 /**
  * Query the window which currently has keyboard focus.
  *
diff --git a/include/SDL3/SDL_mouse.h b/include/SDL3/SDL_mouse.h
index 92a8eab0a17e7..da1eb3b907d51 100644
--- a/include/SDL3/SDL_mouse.h
+++ b/include/SDL3/SDL_mouse.h
@@ -81,6 +81,31 @@ typedef enum
 
 /* Function prototypes */
 
+/**
+ * Return whether a mouse is currently connected.
+ *
+ * \returns SDL_TRUE if a mouse is connected, SDL_FALSE otherwise.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_GetMice
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_HasMouse(void);
+
+/**
+ * Get a list of currently connected mice.
+ *
+ * \param count a pointer filled in with the number of mice returned
+ * \returns a 0 terminated array of mouse instance IDs which should be
+ *          freed with SDL_free(), or NULL on error; call SDL_GetError() for
+ *          more details.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_HasMouse
+ */
+extern DECLSPEC SDL_MouseID *SDLCALL SDL_GetMice(int *count);
+
 /**
  * Get the window which currently has mouse focus.
  *
diff --git a/src/core/haiku/SDL_BApp.h b/src/core/haiku/SDL_BApp.h
index 39fec77829cdd..2d5be1e2b493c 100644
--- a/src/core/haiku/SDL_BApp.h
+++ b/src/core/haiku/SDL_BApp.h
@@ -303,7 +303,7 @@ class SDL_BLooper : public BLooper
             return;
         }
         HAIKU_SetKeyState(scancode, state);
-        SDL_SendKeyboardKey(0, state, HAIKU_GetScancodeFromBeKey(scancode));
+        SDL_SendKeyboardKey(0, 0, state, HAIKU_GetScancodeFromBeKey(scancode));
 
         if (state == SDL_PRESSED && SDL_EventEnabled(SDL_EVENT_TEXT_INPUT)) {
             const int8 *keyUtf8;
diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c
index 9a435db8d1d43..5a8c6e5e5e4b1 100644
--- a/src/core/linux/SDL_evdev.c
+++ b/src/core/linux/SDL_evdev.c
@@ -375,9 +375,9 @@ void SDL_EVDEV_Poll(void)
                     scan_code = SDL_EVDEV_translate_keycode(event->code);
                     if (scan_code != SDL_SCANCODE_UNKNOWN) {
                         if (event->value == 0) {
-                            SDL_SendKeyboardKey(SDL_EVDEV_GetEventTimestamp(event), SDL_RELEASED, scan_code);
+                            SDL_SendKeyboardKey(SDL_EVDEV_GetEventTimestamp(event), (SDL_KeyboardID)item->fd, SDL_RELEASED, scan_code);
                         } else if (event->value == 1 || event->value == 2 /* key repeated */) {
-                            SDL_SendKeyboardKey(SDL_EVDEV_GetEventTimestamp(event), SDL_PRESSED, scan_code);
+                            SDL_SendKeyboardKey(SDL_EVDEV_GetEventTimestamp(event), (SDL_KeyboardID)item->fd, SDL_PRESSED, scan_code);
                         }
                     }
                     SDL_EVDEV_kbd_keycode(_this->kbd, event->code, event->value);
diff --git a/src/core/openbsd/SDL_wscons_kbd.c b/src/core/openbsd/SDL_wscons_kbd.c
index 8b08f05d04091..1cc361b7697bd 100644
--- a/src/core/openbsd/SDL_wscons_kbd.c
+++ b/src/core/openbsd/SDL_wscons_kbd.c
@@ -553,22 +553,22 @@ static void Translate_to_keycode(SDL_WSCONS_input_data *input, int type, keysym_
     switch (keyDesc.command) {
     case KS_Cmd_ScrollBack:
     {
-        SDL_SendKeyboardKey(0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_PAGEUP);
+        SDL_SendKeyboardKey(0, 0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_PAGEUP);
         return;
     }
     case KS_Cmd_ScrollFwd:
     {
-        SDL_SendKeyboardKey(0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_PAGEDOWN);
+        SDL_SendKeyboardKey(0, 0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_PAGEDOWN);
         return;
     }
     }
     for (i = 0; i < sizeof(conversion_table) / sizeof(struct wscons_keycode_to_SDL); i++) {
         if (conversion_table[i].sourcekey == group[0]) {
-            SDL_SendKeyboardKey(0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, conversion_table[i].targetKey);
+            SDL_SendKeyboardKey(0, 0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, conversion_table[i].targetKey);
             return;
         }
     }
-    SDL_SendKeyboardKey(0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_UNKNOWN);
+    SDL_SendKeyboardKey(0, 0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, SDL_SCANCODE_UNKNOWN);
 }
 
 static void updateKeyboard(SDL_WSCONS_input_data *input)
@@ -802,13 +802,13 @@ static void updateKeyboard(SDL_WSCONS_input_data *input)
             } break;
             case WSCONS_EVENT_ALL_KEYS_UP:
                 for (i = 0; i < SDL_NUM_SCANCODES; i++) {
-                    SDL_SendKeyboardKey(0, SDL_RELEASED, i);
+                    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, i);
                 }
                 break;
             }
 
             if (input->type == WSKBD_TYPE_USB && events[i].value <= 0xE7)
-                SDL_SendKeyboardKey(0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)events[i].value);
+                SDL_SendKeyboardKey(0, 0, type == WSCONS_EVENT_KEY_DOWN ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)events[i].value);
             else
                 Translate_to_keycode(input, type, events[i].value);
 
diff --git a/src/core/windows/SDL_hid.c b/src/core/windows/SDL_hid.c
index 9c86b69d5e549..0e42bb6671467 100644
--- a/src/core/windows/SDL_hid.c
+++ b/src/core/windows/SDL_hid.c
@@ -35,6 +35,7 @@ HidP_GetData_t SDL_HidP_GetData;
 static HMODULE s_pHIDDLL = 0;
 static int s_HIDDLLRefCount = 0;
 
+
 int WIN_LoadHIDDLL(void)
 {
     if (s_pHIDDLL) {
@@ -82,3 +83,174 @@ void WIN_UnloadHIDDLL(void)
 }
 
 #endif /* !SDL_PLATFORM_WINRT */
+
+#if !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
+
+/* CM_Register_Notification definitions */
+
+#define CR_SUCCESS 0
+
+DECLARE_HANDLE(HCMNOTIFICATION);
+typedef HCMNOTIFICATION *PHCMNOTIFICATION;
+
+typedef enum _CM_NOTIFY_FILTER_TYPE
+{
+    CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE = 0,
+    CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE,
+    CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE,
+    CM_NOTIFY_FILTER_TYPE_MAX
+} CM_NOTIFY_FILTER_TYPE, *PCM_NOTIFY_FILTER_TYPE;
+
+typedef struct _CM_NOTIFY_FILTER
+{
+    DWORD cbSize;
+    DWORD Flags;
+    CM_NOTIFY_FILTER_TYPE FilterType;
+    DWORD Reserved;
+    union
+    {
+        struct
+        {
+            GUID ClassGuid;
+        } DeviceInterface;
+        struct
+        {
+            HANDLE hTarget;
+        } DeviceHandle;
+        struct
+        {
+            WCHAR InstanceId[200];
+        } DeviceInstance;
+    } u;
+} CM_NOTIFY_FILTER, *PCM_NOTIFY_FILTER;
+
+typedef enum _CM_NOTIFY_ACTION
+{
+    CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL = 0,
+    CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL,
+    CM_NOTIFY_ACTION_DEVICEQUERYREMOVE,
+    CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED,
+    CM_NOTIFY_ACTION_DEVICEREMOVEPENDING,
+    CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE,
+    CM_NOTIFY_ACTION_DEVICECUSTOMEVENT,
+    CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED,
+    CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED,
+    CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED,
+    CM_NOTIFY_ACTION_MAX
+} CM_NOTIFY_ACTION, *PCM_NOTIFY_ACTION;
+
+typedef struct _CM_NOTIFY_EVENT_DATA
+{
+    CM_NOTIFY_FILTER_TYPE FilterType;
+    DWORD Reserved;
+    union
+    {
+        struct
+        {
+            GUID ClassGuid;
+            WCHAR SymbolicLink[ANYSIZE_ARRAY];
+        } DeviceInterface;
+        struct
+        {
+            GUID EventGuid;
+            LONG NameOffset;
+            DWORD DataSize;
+            BYTE Data[ANYSIZE_ARRAY];
+        } DeviceHandle;
+        struct
+        {
+            WCHAR InstanceId[ANYSIZE_ARRAY];
+        } DeviceInstance;
+    } u;
+} CM_NOTIFY_EVENT_DATA, *PCM_NOTIFY_EVENT_DATA;
+
+typedef DWORD (CALLBACK *PCM_NOTIFY_CALLBACK)(HCMNOTIFICATION hNotify, PVOID Context, CM_NOTIFY_ACTION Action, PCM_NOTIFY_EVENT_DATA EventData, DWORD EventDataSize);
+
+typedef DWORD (WINAPI *CM_Register_NotificationFunc)(PCM_NOTIFY_FILTER pFilter, PVOID pContext, PCM_NOTIFY_CALLBACK pCallback, PHCMNOTIFICATION pNotifyContext);
+typedef DWORD (WINAPI *CM_Unregister_NotificationFunc)(HCMNOTIFICATION NotifyContext);
+
+static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
+
+static int s_DeviceNotificationsRequested;
+static HMODULE cfgmgr32_lib_handle;
+static CM_Register_NotificationFunc CM_Register_Notification;
+static CM_Unregister_NotificationFunc CM_Unregister_Notification;
+static HCMNOTIFICATION s_DeviceNotificationFuncHandle;
+static Uint64 s_LastDeviceNotification = 1;
+
+static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size)
+{
+    if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
+        action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) {
+        s_LastDeviceNotification = SDL_GetTicksNS();
+    }
+    return ERROR_SUCCESS;
+}
+
+void WIN_InitDeviceNotification(void)
+{
+    ++s_DeviceNotificationsRequested;
+    if (s_DeviceNotificationsRequested > 1) {
+        return;
+    }
+
+    cfgmgr32_lib_handle = LoadLibraryA("cfgmgr32.dll");
+    if (cfgmgr32_lib_handle) {
+        CM_Register_Notification = (CM_Register_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Register_Notification");
+        CM_Unregister_Notification = (CM_Unregister_NotificationFunc)GetProcAddress(cfgmgr32_lib_handle, "CM_Unregister_Notification");
+        if (CM_Register_Notification && CM_Unregister_Notification) {
+            CM_NOTIFY_FILTER notify_filter;
+
+            SDL_zero(notify_filter);
+            notify_filter.cbSize = sizeof(notify_filter);
+            notify_filter.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE;
+            notify_filter.u.DeviceInterface.ClassGuid = GUID_DEVINTERFACE_HID;
+            if (CM_Register_Notification(&notify_filter, NULL, SDL_DeviceNotificationFunc, &s_DeviceNotificationFuncHandle) == CR_SUCCESS) {
+                return;
+            }
+        }
+    }
+
+    // FIXME: Should we log errors?
+}
+
+Uint64 WIN_GetLastDeviceNotification(void)
+{
+    return s_LastDeviceNotification;
+}
+
+void WIN_QuitDeviceNotification(void)
+{
+    if (--s_DeviceNotificationsRequested > 0) {
+        return;
+    }
+    /* Make sure we have balanced calls to init/quit */
+    SDL_assert(s_DeviceNotificationsRequested == 0);
+
+    if (cfgmgr32_lib_handle) {
+        if (s_DeviceNotificationFuncHandle && CM_Unregister_Notification) {
+            CM_Unregister_Notification(s_DeviceNotificationFuncHandle);
+            s_DeviceNotificationFuncHandle = NULL;
+        }
+
+        FreeLibrary(cfgmgr32_lib_handle);
+        cfgmgr32_lib_handle = NULL;
+    }
+}
+
+#else
+
+void WIN_InitDeviceNotification(void)
+{
+}
+
+Uint64 WIN_GetLastDeviceNotification( void )
+{
+    return 0;
+}
+
+void WIN_QuitDeviceNotification(void)
+{
+}
+
+#endif // !SDL_PLATFORM_WINRT && !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES
diff --git a/src/core/windows/SDL_hid.h b/src/core/windows/SDL_hid.h
index e747e46eae082..3d78211265d27 100644
--- a/src/core/windows/SDL_hid.h
+++ b/src/core/windows/SDL_hid.h
@@ -193,12 +193,12 @@ typedef struct
 extern int WIN_LoadHIDDLL(void);
 extern void WIN_UnloadHIDDLL(void);
 
-typedef BOOLEAN(WINAPI *HidD_GetString_t)(HANDLE HidDeviceObject, PVOID Buffer, ULONG BufferLength);
-typedef NTSTATUS(WINAPI *HidP_GetCaps_t)(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities);
-typedef NTSTATUS(WINAPI *HidP_GetButtonCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
-typedef NTSTATUS(WINAPI *HidP_GetValueCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
-typedef ULONG(WINAPI *HidP_MaxDataListLength_t)(HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData);
-typedef NTSTATUS(WINAPI *HidP_GetData_t)(HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength);
+typedef BOOLEAN (WINAPI *HidD_GetString_t)(HANDLE HidDeviceObject, PVOID Buffer, ULONG BufferLength);
+typedef NTSTATUS (WINAPI *HidP_GetCaps_t)(PHIDP_PREPARSED_DATA PreparsedData, PHIDP_CAPS Capabilities);
+typedef NTSTATUS (WINAPI *HidP_GetButtonCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_BUTTON_CAPS ButtonCaps, PUSHORT ButtonCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
+typedef NTSTATUS (WINAPI *HidP_GetValueCaps_t)(HIDP_REPORT_TYPE ReportType, PHIDP_VALUE_CAPS ValueCaps, PUSHORT ValueCapsLength, PHIDP_PREPARSED_DATA PreparsedData);
+typedef ULONG (WINAPI *HidP_MaxDataListLength_t)(HIDP_REPORT_TYPE ReportType, PHIDP_PREPARSED_DATA PreparsedData);
+typedef NTSTATUS (WINAPI *HidP_GetData_t)(HIDP_REPORT_TYPE ReportType, PHIDP_DATA DataList, PULONG DataLength, PHIDP_PREPARSED_DATA PreparsedData, PCHAR Report, ULONG ReportLength);
 
 extern HidD_GetString_t SDL_HidD_GetManufacturerString;
 extern HidD_GetString_t SDL_HidD_GetProductString;
@@ -210,4 +210,9 @@ extern HidP_GetData_t SDL_HidP_GetData;
 
 #endif /* !SDL_PLATFORM_WINRT */
 
+
+void WIN_InitDeviceNotification(void);
+Uint64 WIN_GetLastDeviceNotification(void);
+void WIN_QuitDeviceNotification(void);
+
 #endif /* SDL_hid_h_ */
diff --git a/src/core/winrt/SDL_winrtapp_direct3d.cpp b/src/core/winrt/SDL_winrtapp_direct3d.cpp
index 0459ab107f19a..9fddd3d106c30 100644
--- a/src/core/winrt/SDL_winrtapp_direct3d.cpp
+++ b/src/core/winrt/SDL_winrtapp_direct3d.cpp
@@ -724,8 +724,8 @@ void SDL_WinRTApp::OnCharacterReceived(Windows::UI::Core::CoreWindow ^ sender, W
 template <typename BackButtonEventArgs>
 static void WINRT_OnBackButtonPressed(BackButtonEventArgs ^ args)
 {
-    SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_AC_BACK);
-    SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_AC_BACK);
+    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_AC_BACK);
+    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_AC_BACK);
 
     if (SDL_GetHintBoolean(SDL_HINT_WINRT_HANDLE_BACK_BUTTON, SDL_FALSE)) {
         args->Handled = true;
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index e651008fc923d..be498a820f930 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -320,11 +320,13 @@ SDL3_0.0.0 {
     SDL_GetKeyName;
     SDL_GetKeyboardFocus;
     SDL_GetKeyboardState;
+    SDL_GetKeyboards;
     SDL_GetLogOutputFunction;
     SDL_GetMasksForPixelFormatEnum;
     SDL_GetMaxHapticEffects;
     SDL_GetMaxHapticEffectsPlaying;
     SDL_GetMemoryFunctions;
+    SDL_GetMice;
     SDL_GetModState;
     SDL_GetMouseFocus;
     SDL_GetMouseState;
@@ -487,9 +489,13 @@ SDL3_0.0.0 {
     SDL_HasClipboardText;
     SDL_HasEvent;
     SDL_HasEvents;
+    SDL_HasGamepad;
+    SDL_HasJoystick;
+    SDL_HasKeyboard;
     SDL_HasLASX;
     SDL_HasLSX;
     SDL_HasMMX;
+    SDL_HasMouse;
     SDL_HasNEON;
     SDL_HasPrimarySelectionText;
     SDL_HasProperty;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index ac9310c489162..8f140360d0603 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -345,11 +345,13 @@
 #define SDL_GetKeyName SDL_GetKeyName_REAL
 #define SDL_GetKeyboardFocus SDL_GetKeyboardFocus_REAL
 #define SDL_GetKeyboardState SDL_GetKeyboardState_REAL
+#define SDL_GetKeyboards SDL_GetKeyboards_REAL
 #define SDL_GetLogOutputFunction SDL_GetLogOutputFunction_REAL
 #define SDL_GetMasksForPixelFormatEnum SDL_GetMasksForPixelFormatEnum_REAL
 #define SDL_GetMaxHapticEffects SDL_GetMaxHapticEffects_REAL
 #define SDL_GetMaxHapticEffectsPlaying SDL_GetMaxHapticEffectsPlaying_REAL
 #define SDL_GetMemoryFunctions SDL_GetMemoryFunctions_REAL
+#define SDL_GetMice SDL_GetMice_REAL
 #define SDL_GetModState SDL_GetModState_REAL
 #define SDL_GetMouseFocus SDL_GetMouseFocus_REAL
 #define SDL_GetMouseState SDL_GetMouseState_REAL
@@ -512,9 +514,13 @@
 #define SDL_HasClipboardText SDL_HasClipboardText_REAL
 #define SDL_HasEvent SDL_HasEvent_REAL
 #define SDL_HasEvents SDL_HasEvents_REAL
+#define SDL_HasGamepad SDL_HasGamepad_REAL
+#define SDL_HasJoystick SDL_HasJoystick_REAL
+#define SDL_HasKeyboard SDL_HasKeyboard_REAL
 #define SDL_HasLASX SDL_HasLASX_REAL
 #define SDL_HasLSX SDL_HasLSX_REAL
 #define SDL_HasMMX SDL_HasMMX_REAL
+#define SDL_HasMouse SDL_HasMouse_REAL
 #define SDL_HasNEON SDL_HasNEON_REAL
 #define SDL_HasPrimarySelectionText SDL_HasPrimarySelectionText_REAL
 #define SDL_HasProperty SDL_HasProperty_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index f1ff330c6114c..8a2804a249efb 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -383,11 +383,13 @@ SDL_DYNAPI_PROC(SDL_Keycode,SDL_GetKeyFromScancode,(SDL_Scancode a),(a),return)
 SDL_DYNAPI_PROC(const char*,SDL_GetKeyName,(SDL_Keycode a),(a),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_GetKeyboardFocus,(void),(),return)
 SDL_DYNAPI_PROC(const Uint8*,SDL_GetKeyboardState,(int *a),(a),return)
+SDL_DYNAPI_PROC(SDL_MouseID*,SDL_GetKeyboards,(int *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_GetLogOutputFunction,(SDL_LogOutputFunction *a, void **b),(a,b),)
 SDL_DYNAPI_PROC(SDL_bool,SDL_GetMasksForPixelFormatEnum,(SDL_PixelFormatEnum a, int *b, Uint32 *c, Uint32 *d, Uint32 *e, Uint32 *f),(a,b,c,d,e,f),return)
 SDL_DYNAPI_PROC(int,SDL_GetMaxHapticEffects,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(int,SDL_GetMaxHapticEffectsPlaying,(SDL_Haptic *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_GetMemoryFunctions,(SDL_malloc_func *a, SDL_calloc_func *b, SDL_realloc_func *c, SDL_free_func *d),(a,b,c,d),)
+SDL_DYNAPI_PROC(SDL_MouseID*,SDL_GetMice,(int *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Keymod,SDL_GetModState,(void),(),return)
 SDL_DYNAPI_PROC(SDL_Window*,SDL_GetMouseFocus,(void),(),return)
 SDL_DYNAPI_PROC(Uint32,SDL_GetMouseState,(float *a, float *b),(a,b),return)
@@ -543,9 +545,13 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_HasClipboardData,(const char *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasClipboardText,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasEvent,(Uint32 a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasEvents,(Uint32 a, Uint32 b),(a,b),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_HasGamepad,(void),(),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_HasJoystick,(void),(),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_HasKeyboard,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasLASX,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasLSX,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasMMX,(void),(),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_HasMouse,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasNEON,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasPrimarySelectionText,(void),(),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 305a4744c9373..d1b2776413c92 100644
--- a/src/events/SDL_events.c
+++ b/src/events/SDL_events.c
@@ -325,6 +325,15 @@ static void SDL_LogEvent(const SDL_Event *event)
         SDL_WINDOWEVENT_CASE(SDL_EVENT_WINDOW_DESTROYED);
 #undef SDL_WINDOWEVENT_CASE
 
+#define PRINT_KEYDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->kdevice.timestamp, (uint)event->kdevice.which)
+        SDL_EVENT_CASE(SDL_EVENT_KEYBOARD_ADDED)
+        PRINT_KEYDEV_EVENT(event);
+        break;
+        SDL_EVENT_CASE(SDL_EVENT_KEYBOARD_REMOVED)
+        PRINT_KEYDEV_EVENT(event);
+        break;
+#undef PRINT_KEYDEV_EVENT
+
 #define PRINT_KEY_EVENT(event)                                                                                                   \
     (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u state=%s repeat=%s scancode=%u keycode=%u mod=%u)", \
                        (uint)event->key.timestamp, (uint)event->key.windowID,                                                    \
@@ -351,6 +360,15 @@ static void SDL_LogEvent(const SDL_Event *event)
         (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u text='%s')", (uint)event->text.timestamp, (uint)event->text.windowID, event->text.text);
         break;
 
+#define PRINT_MOUSEDEV_EVENT(event) (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u which=%u)", (uint)event->mdevice.timestamp, (uint)event->mdevice.which)
+        SDL_EVENT_CASE(SDL_EVENT_MOUSE_ADDED)
+        PRINT_MOUSEDEV_EVENT(event);
+        break;
+        SDL_EVENT_CASE(SDL_EVENT_MOUSE_REMOVED)
+        PRINT_MOUSEDEV_EVENT(event);
+        break;
+#undef PRINT_MOUSEDEV_EVENT
+
         SDL_EVENT_CASE(SDL_EVENT_MOUSE_MOTION)
         (void)SDL_snprintf(details, sizeof(details), " (timestamp=%u windowid=%u which=%u state=%u x=%g y=%g xrel=%g yrel=%g)",
                            (uint)eve

(Patch may be truncated, please check the link at the top of this post.)