Browse Source

Initial support for hotplugging mice and keyboards

Sam Lantinga 1 year ago
parent
commit
2fe1a6a279
44 changed files with 956 additions and 316 deletions
  1. 31 2
      include/SDL3/SDL_events.h
  2. 12 0
      include/SDL3/SDL_gamepad.h
  3. 12 0
      include/SDL3/SDL_joystick.h
  4. 27 0
      include/SDL3/SDL_keyboard.h
  5. 25 0
      include/SDL3/SDL_mouse.h
  6. 1 1
      src/core/haiku/SDL_BApp.h
  7. 2 2
      src/core/linux/SDL_evdev.c
  8. 6 6
      src/core/openbsd/SDL_wscons_kbd.c
  9. 172 0
      src/core/windows/SDL_hid.c
  10. 11 6
      src/core/windows/SDL_hid.h
  11. 2 2
      src/core/winrt/SDL_winrtapp_direct3d.cpp
  12. 6 0
      src/dynapi/SDL_dynapi.sym
  13. 6 0
      src/dynapi/SDL_dynapi_overrides.h
  14. 6 0
      src/dynapi/SDL_dynapi_procs.h
  15. 18 0
      src/events/SDL_events.c
  16. 131 15
      src/events/SDL_keyboard.c
  17. 12 3
      src/events/SDL_keyboard_c.h
  18. 140 5
      src/events/SDL_mouse.c
  19. 10 1
      src/events/SDL_mouse_c.h
  20. 20 0
      src/joystick/SDL_gamepad.c
  21. 19 0
      src/joystick/SDL_joystick.c
  22. 2 2
      src/joystick/android/SDL_sysjoystick.c
  23. 21 154
      src/joystick/windows/SDL_windowsjoystick.c
  24. 18 2
      src/test/SDL_test_common.c
  25. 2 2
      src/video/android/SDL_androidkeyboard.c
  26. 5 5
      src/video/cocoa/SDL_cocoakeyboard.m
  27. 2 2
      src/video/cocoa/SDL_cocoawindow.m
  28. 1 1
      src/video/emscripten/SDL_emscriptenevents.c
  29. 1 1
      src/video/haiku/SDL_BApp.h
  30. 2 2
      src/video/ngage/SDL_ngageevents.cpp
  31. 2 2
      src/video/psp/SDL_pspevents.c
  32. 2 2
      src/video/qnx/SDL_qnxkeyboard.c
  33. 2 2
      src/video/riscos/SDL_riscosevents.c
  34. 0 2
      src/video/uikit/SDL_uikitevents.h
  35. 16 30
      src/video/uikit/SDL_uikitevents.m
  36. 8 8
      src/video/uikit/SDL_uikitview.m
  37. 28 28
      src/video/vita/SDL_vitakeyboard.c
  38. 3 3
      src/video/wayland/SDL_waylandevents.c
  39. 138 16
      src/video/windows/SDL_windowsevents.c
  40. 1 0
      src/video/windows/SDL_windowsevents.h
  41. 4 0
      src/video/windows/SDL_windowsvideo.c
  42. 1 1
      src/video/winrt/SDL_winrtkeyboard.cpp
  43. 6 6
      src/video/x11/SDL_x11events.c
  44. 22 2
      test/testhotplug.c

+ 31 - 2
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 */

+ 12 - 0
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);

+ 12 - 0
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);

+ 27 - 0
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.
  *

+ 25 - 0
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.
  *

+ 1 - 1
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;

+ 2 - 2
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);

+ 6 - 6
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);
 

+ 172 - 0
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

+ 11 - 6
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_ */

+ 2 - 2
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;

+ 6 - 0
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;

+ 6 - 0
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

+ 6 - 0
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)

+ 18 - 0
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)event->motion.timestamp, (uint)event->motion.windowID,

+ 131 - 15
src/events/SDL_keyboard.c

@@ -55,6 +55,8 @@ struct SDL_Keyboard
 };
 
 static SDL_Keyboard SDL_keyboard;
+static int SDL_keyboard_count;
+static SDL_KeyboardID *SDL_keyboards;
 
 static const SDL_Keycode SDL_default_keymap[SDL_NUM_SCANCODES] = {
     /* 0 */ SDLK_UNKNOWN,
@@ -678,6 +680,109 @@ int SDL_InitKeyboard(void)
     return 0;
 }
 
+SDL_bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys)
+{
+    const int REAL_KEYBOARD_KEY_COUNT = 50;
+    if (num_keys > 0 && num_keys < REAL_KEYBOARD_KEY_COUNT) {
+        return SDL_FALSE;
+    }
+
+    /* Eventually we'll have a blacklist of devices that enumerate as keyboards but aren't really */
+    return SDL_TRUE;
+}
+
+void SDL_PrivateKeyboardAdded(SDL_KeyboardID keyboardID)
+{
+    int keyboard_index = -1;
+
+    SDL_assert(keyboardID != 0);
+
+    for (int i = 0; i < SDL_keyboard_count; ++i) {
+        if (keyboardID == SDL_keyboards[i]) {
+            keyboard_index = i;
+            break;
+        }
+    }
+
+    if (keyboard_index >= 0) {
+        /* We already know about this keyboard */
+        return;
+    }
+
+    SDL_KeyboardID *keyboards = (SDL_KeyboardID *)SDL_realloc(SDL_keyboards, (SDL_keyboard_count + 1) * sizeof(*keyboards));
+    if (!keyboards) {
+        return;
+    }
+    keyboards[SDL_keyboard_count] = keyboardID;
+    SDL_keyboards = keyboards;
+    ++SDL_keyboard_count;
+
+    SDL_Event event;
+    SDL_zero(event);
+    event.type = SDL_EVENT_KEYBOARD_ADDED;
+    event.kdevice.which = keyboardID;
+    SDL_PushEvent(&event);
+}
+
+void SDL_PrivateKeyboardRemoved(SDL_KeyboardID keyboardID)
+{
+    int keyboard_index = -1;
+
+    SDL_assert(keyboardID != 0);
+
+    for (int i = 0; i < SDL_keyboard_count; ++i) {
+        if (keyboardID == SDL_keyboards[i]) {
+            keyboard_index = i;
+            break;
+        }
+    }
+
+    if (keyboard_index < 0) {
+        /* We don't know about this keyboard */
+        return;
+    }
+
+    if (keyboard_index != SDL_keyboard_count - 1) {
+        SDL_memcpy(&SDL_keyboards[keyboard_index], &SDL_keyboards[keyboard_index + 1], (SDL_keyboard_count - keyboard_index - 1) * sizeof(SDL_keyboards[keyboard_index]));
+    }
+    --SDL_keyboard_count;
+
+    SDL_Event event;
+    SDL_zero(event);
+    event.type = SDL_EVENT_KEYBOARD_REMOVED;
+    event.kdevice.which = keyboardID;
+    SDL_PushEvent(&event);
+}
+
+SDL_bool SDL_HasKeyboard(void)
+{
+    return (SDL_keyboard_count > 0);
+}
+
+SDL_KeyboardID *SDL_GetKeyboards(int *count)
+{
+    int i;
+    SDL_KeyboardID *keyboards;
+
+    keyboards = (SDL_JoystickID *)SDL_malloc((SDL_keyboard_count + 1) * sizeof(*keyboards));
+    if (keyboards) {
+        if (count) {
+            *count = SDL_keyboard_count;
+        }
+
+        for (i = 0; i < SDL_keyboard_count; ++i) {
+            keyboards[i] = SDL_keyboards[i];
+        }
+        keyboards[i] = 0;
+    } else {
+        if (count) {
+            *count = 0;
+        }
+    }
+
+    return keyboards;
+}
+
 void SDL_ResetKeyboard(void)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
@@ -688,7 +793,7 @@ void SDL_ResetKeyboard(void)
 #endif
     for (scancode = (SDL_Scancode)0; scancode < SDL_NUM_SCANCODES; ++scancode) {
         if (keyboard->keystate[scancode] == SDL_PRESSED) {
-            SDL_SendKeyboardKey(0, SDL_RELEASED, scancode);
+            SDL_SendKeyboardKey(0, 0, SDL_RELEASED, scancode);
         }
     }
 }
@@ -822,7 +927,7 @@ int SDL_SetKeyboardFocus(SDL_Window *window)
     return 0;
 }
 
-static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, Uint8 state, SDL_Scancode scancode, SDL_Keycode keycode)
+static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode, SDL_Keycode keycode)
 {
     SDL_Keyboard *keyboard = &SDL_keyboard;
     int posted;
@@ -831,6 +936,13 @@ static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, Uint8 sta
     Uint8 repeat = SDL_FALSE;
     const Uint8 source = flags & KEYBOARD_SOURCE_MASK;
 
+    if (keyboardID == 0) {
+        if (source == KEYBOARD_HARDWARE && SDL_keyboard_count > 0) {
+            /* Assume it's from the first keyboard */
+            keyboardID = SDL_keyboards[0];
+        }
+    }
+
     if (scancode == SDL_SCANCODE_UNKNOWN || scancode >= SDL_NUM_SCANCODES) {
         return 0;
     }
@@ -949,6 +1061,7 @@ static int SDL_SendKeyboardKeyInternal(Uint64 timestamp, Uint32 flags, Uint8 sta
         event.key.keysym.sym = keycode;
         event.key.keysym.mod = keyboard->modstate;
         event.key.windowID = keyboard->focus ? keyboard->focus->id : 0;
+        event.key.which = keyboardID;
         posted = (SDL_PushEvent(&event) > 0);
     }
 
@@ -982,43 +1095,43 @@ int SDL_SendKeyboardUnicodeKey(Uint64 timestamp, Uint32 ch)
 
     if (mod & SDL_KMOD_SHIFT) {
         /* If the character uses shift, press shift down */
-        SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_PRESSED, SDL_SCANCODE_LSHIFT, SDLK_UNKNOWN);
+        SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, 0, SDL_PRESSED, SDL_SCANCODE_LSHIFT, SDLK_UNKNOWN);
     }
 
     /* Send a keydown and keyup for the character */
-    SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_PRESSED, code, SDLK_UNKNOWN);
-    SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_RELEASED, code, SDLK_UNKNOWN);
+    SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, 0, SDL_PRESSED, code, SDLK_UNKNOWN);
+    SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, 0, SDL_RELEASED, code, SDLK_UNKNOWN);
 
     if (mod & SDL_KMOD_SHIFT) {
         /* If the character uses shift, release shift */
-        SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, SDL_RELEASED, SDL_SCANCODE_LSHIFT, SDLK_UNKNOWN);
+        SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, 0, SDL_RELEASED, SDL_SCANCODE_LSHIFT, SDLK_UNKNOWN);
     }
     return 0;
 }
 
 int SDL_SendVirtualKeyboardKey(Uint64 timestamp, Uint8 state, SDL_Scancode scancode)
 {
-    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, state, scancode, SDLK_UNKNOWN);
+    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_VIRTUAL, 0, state, scancode, SDLK_UNKNOWN);
 }
 
-int SDL_SendKeyboardKey(Uint64 timestamp, Uint8 state, SDL_Scancode scancode)
+int SDL_SendKeyboardKey(Uint64 timestamp, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode)
 {
-    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, state, scancode, SDLK_UNKNOWN);
+    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, keyboardID, state, scancode, SDLK_UNKNOWN);
 }
 
-int SDL_SendKeyboardKeyAndKeycode(Uint64 timestamp, Uint8 state, SDL_Scancode scancode, SDL_Keycode keycode)
+int SDL_SendKeyboardKeyAndKeycode(Uint64 timestamp, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode, SDL_Keycode keycode)
 {
-    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, state, scancode, keycode);
+    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE, keyboardID, state, scancode, keycode);
 }
 
 int SDL_SendKeyboardKeyAutoRelease(Uint64 timestamp, SDL_Scancode scancode)
 {
-    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_AUTORELEASE, SDL_PRESSED, scancode, SDLK_UNKNOWN);
+    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_AUTORELEASE, 0, SDL_PRESSED, scancode, SDLK_UNKNOWN);
 }
 
-int SDL_SendKeyboardKeyIgnoreModifiers(Uint64 timestamp, Uint8 state, SDL_Scancode scancode)
+int SDL_SendKeyboardKeyIgnoreModifiers(Uint64 timestamp, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode)
 {
-    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE | KEYBOARD_IGNOREMODIFIERS, state, scancode, SDLK_UNKNOWN);
+    return SDL_SendKeyboardKeyInternal(timestamp, KEYBOARD_HARDWARE | KEYBOARD_IGNOREMODIFIERS, keyboardID, state, scancode, SDLK_UNKNOWN);
 }
 
 void SDL_ReleaseAutoReleaseKeys(void)
@@ -1029,7 +1142,7 @@ void SDL_ReleaseAutoReleaseKeys(void)
     if (keyboard->autorelease_pending) {
         for (scancode = SDL_SCANCODE_UNKNOWN; scancode < SDL_NUM_SCANCODES; ++scancode) {
             if (keyboard->keysource[scancode] == KEYBOARD_AUTORELEASE) {
-                SDL_SendKeyboardKeyInternal(0, KEYBOARD_AUTORELEASE, SDL_RELEASED, scancode, SDLK_UNKNOWN);
+                SDL_SendKeyboardKeyInternal(0, KEYBOARD_AUTORELEASE, 0, SDL_RELEASED, scancode, SDLK_UNKNOWN);
             }
         }
         keyboard->autorelease_pending = SDL_FALSE;
@@ -1117,6 +1230,9 @@ int SDL_SendEditingText(const char *text, int start, int length)
 
 void SDL_QuitKeyboard(void)
 {
+    SDL_keyboard_count = 0;
+    SDL_free(SDL_keyboards);
+    SDL_keyboards = NULL;
 }
 
 const Uint8 *SDL_GetKeyboardState(int *numkeys)

+ 12 - 3
src/events/SDL_keyboard_c.h

@@ -26,6 +26,15 @@
 /* Initialize the keyboard subsystem */
 extern int SDL_InitKeyboard(void);
 
+/* Return whether a device is actually a keyboard */
+extern SDL_bool SDL_IsKeyboard(Uint16 vendor, Uint16 product, int num_keys);
+
+/* A keyboard has been added to the system */
+extern void SDL_PrivateKeyboardAdded(SDL_KeyboardID keyboardID);
+
+/* A keyboard has been removed from the system */
+extern void SDL_PrivateKeyboardRemoved(SDL_KeyboardID keyboardID);
+
 /* Get the default keymap */
 extern void SDL_GetDefaultKeymap(SDL_Keycode *keymap);
 
@@ -53,13 +62,13 @@ extern int SDL_SendKeyboardUnicodeKey(Uint64 timestamp, Uint32 ch);
 extern int SDL_SendVirtualKeyboardKey(Uint64 timestamp, Uint8 state, SDL_Scancode scancode);
 
 /* Send a keyboard key event */
-extern int SDL_SendKeyboardKey(Uint64 timestamp, Uint8 state, SDL_Scancode scancode);
+extern int SDL_SendKeyboardKey(Uint64 timestamp, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode);
 extern int SDL_SendKeyboardKeyAutoRelease(Uint64 timestamp, SDL_Scancode scancode);
-extern int SDL_SendKeyboardKeyIgnoreModifiers(Uint64 timestamp, Uint8 state, SDL_Scancode scancode);
+extern int SDL_SendKeyboardKeyIgnoreModifiers(Uint64 timestamp, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode);
 
 /* This is for platforms that don't know the keymap but can report scancode and keycode directly.
    Most platforms should prefer to optionally call SDL_SetKeymap and then use SDL_SendKeyboardKey. */
-extern int SDL_SendKeyboardKeyAndKeycode(Uint64 timestamp, Uint8 state, SDL_Scancode scancode, SDL_Keycode keycode);
+extern int SDL_SendKeyboardKeyAndKeycode(Uint64 timestamp, SDL_KeyboardID keyboardID, Uint8 state, SDL_Scancode scancode, SDL_Keycode keycode);
 
 /* Release all the autorelease keys */
 extern void SDL_ReleaseAutoReleaseKeys(void);

+ 140 - 5
src/events/SDL_mouse.c

@@ -35,6 +35,8 @@
 
 /* The mouse state */
 static SDL_Mouse SDL_mouse;
+static int SDL_mouse_count;
+static SDL_MouseID *SDL_mice;
 
 /* for mapping mouse events to touch */
 static SDL_bool track_mouse_down = SDL_FALSE;
@@ -227,6 +229,118 @@ void SDL_PostInitMouse(void)
     SDL_PenInit();
 }
 
+SDL_bool SDL_IsMouse(Uint16 vendor, Uint16 product)
+{
+    /* Eventually we'll have a blacklist of devices that enumerate as mice but aren't really */
+    return SDL_TRUE;
+}
+
+void SDL_PrivateMouseAdded(SDL_MouseID mouseID)
+{
+    int mouse_index = -1;
+
+    SDL_assert(mouseID != 0);
+
+    for (int i = 0; i < SDL_mouse_count; ++i) {
+        if (mouseID == SDL_mice[i]) {
+            mouse_index = i;
+            break;
+        }
+    }
+
+    if (mouse_index >= 0) {
+        /* We already know about this mouse */
+        return;
+    }
+
+    SDL_MouseID *mice = (SDL_MouseID *)SDL_realloc(SDL_mice, (SDL_mouse_count + 1) * sizeof(*mice));
+    if (!mice) {
+        return;
+    }
+    mice[SDL_mouse_count] = mouseID;
+    SDL_mice = mice;
+    ++SDL_mouse_count;
+
+    SDL_Event event;
+    SDL_zero(event);
+    event.type = SDL_EVENT_MOUSE_ADDED;
+    event.mdevice.which = mouseID;
+    SDL_PushEvent(&event);
+}
+
+void SDL_PrivateMouseRemoved(SDL_MouseID mouseID)
+{
+    int mouse_index = -1;
+
+    SDL_assert(mouseID != 0);
+
+    for (int i = 0; i < SDL_mouse_count; ++i) {
+        if (mouseID == SDL_mice[i]) {
+            mouse_index = i;
+            break;
+        }
+    }
+
+    if (mouse_index < 0) {
+        /* We don't know about this mouse */
+        return;
+    }
+
+    if (mouse_index != SDL_mouse_count - 1) {
+        SDL_memcpy(&SDL_mice[mouse_index], &SDL_mice[mouse_index + 1], (SDL_mouse_count - mouse_index - 1) * sizeof(SDL_mice[mouse_index]));
+    }
+    --SDL_mouse_count;
+
+    /* Remove any mouse input sources for this mouseID */
+    SDL_Mouse *mouse = SDL_GetMouse();
+    for (int i = 0; i < mouse->num_sources; ++i) {
+        SDL_MouseInputSource *source = &mouse->sources[i];
+        if (source->mouseID == mouseID) {
+            if (i != mouse->num_sources - 1) {
+                SDL_memcpy(&mouse->sources[i], &mouse->sources[i + 1], (mouse->num_sources - i - 1) * sizeof(mouse->sources[i]));
+            }
+            --mouse->num_sources;
+            break;
+        }
+    }
+
+    SDL_Event event;
+    SDL_zero(event);
+    event.type = SDL_EVENT_MOUSE_REMOVED;
+    event.mdevice.which = mouseID;
+    SDL_PushEvent(&event);
+}
+
+SDL_bool SDL_HasMouse(void)
+{
+    return (SDL_mouse_count > 0);
+}
+
+SDL_MouseID *SDL_GetMice(int *count)
+{
+    int i;
+    SDL_MouseID *mice;
+
+    mice = (SDL_JoystickID *)SDL_malloc((SDL_mouse_count + 1) * sizeof(*mice));
+    if (mice) {
+        if (count) {
+            *count = SDL_mouse_count;
+        }
+
+        for (i = 0; i < SDL_mouse_count; ++i) {
+            mice[i] = SDL_mice[i];
+        }
+        mice[i] = 0;
+    } else {
+        if (count) {
+            *count = 0;
+        }
+    }
+
+    return mice;
+}
+
+
 void SDL_SetDefaultCursor(SDL_Cursor *cursor)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
@@ -687,18 +801,35 @@ static int SDL_PrivateSendMouseMotion(Uint64 timestamp, SDL_Window *window, SDL_
     return posted;
 }
 
-static SDL_MouseInputSource *GetMouseInputSource(SDL_Mouse *mouse, SDL_MouseID mouseID)
+static SDL_MouseInputSource *GetMouseInputSource(SDL_Mouse *mouse, SDL_MouseID mouseID, Uint8 state, Uint8 button)
 {
-    SDL_MouseInputSource *source, *sources;
+    SDL_MouseInputSource *source, *match = NULL, *sources;
     int i;
 
     for (i = 0; i < mouse->num_sources; ++i) {
         source = &mouse->sources[i];
         if (source->mouseID == mouseID) {
-            return source;
+            match = source;
+            break;
         }
     }
 
+    if (!state && (!match || !(match->buttonstate & SDL_BUTTON(button)))) {
+        /* This might be a button release from a transition between mouse messages and raw input.
+         * See if there's another mouse source that already has that button down and use that.
+         */
+        for (i = 0; i < mouse->num_sources; ++i) {
+            source = &mouse->sources[i];
+            if ((source->buttonstate & SDL_BUTTON(button))) {
+                match = source;
+                break;
+            }
+        }
+    }
+    if (match) {
+        return match;
+    }
+
     sources = (SDL_MouseInputSource *)SDL_realloc(mouse->sources, (mouse->num_sources + 1) * sizeof(*mouse->sources));
     if (sources) {
         mouse->sources = sources;
@@ -737,7 +868,7 @@ static int SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_
     Uint32 buttonstate;
     SDL_MouseInputSource *source;
 
-    source = GetMouseInputSource(mouse, mouseID);
+    source = GetMouseInputSource(mouse, mouseID, state, button);
     if (!source) {
         return 0;
     }
@@ -823,7 +954,7 @@ static int SDL_PrivateSendMouseButton(Uint64 timestamp, SDL_Window *window, SDL_
         event.type = type;
         event.common.timestamp = timestamp;
         event.button.windowID = mouse->focus ? mouse->focus->id : 0;
-        event.button.which = mouseID;
+        event.button.which = source->mouseID;
         event.button.state = state;
         event.button.button = button;
         event.button.clicks = (Uint8)SDL_min(clicks, 255);
@@ -957,6 +1088,10 @@ void SDL_QuitMouse(void)
 
     SDL_DelHintCallback(SDL_HINT_MOUSE_RELATIVE_WARP_MOTION,
                         SDL_MouseRelativeWarpMotionChanged, mouse);
+
+    SDL_mouse_count = 0;
+    SDL_free(SDL_mice);
+    SDL_mice = NULL;
 }
 
 Uint32 SDL_GetMouseState(float *x, float *y)

+ 10 - 1
src/events/SDL_mouse_c.h

@@ -128,8 +128,17 @@ extern int SDL_PreInitMouse(void);
 /* Finish initializing the mouse subsystem, called after the main video driver was initialized */
 extern void SDL_PostInitMouse(void);
 
+/* Return whether a device is actually a mouse */
+extern SDL_bool SDL_IsMouse(Uint16 vendor, Uint16 product);
+
+/* A mouse has been added to the system */
+extern void SDL_PrivateMouseAdded(SDL_MouseID mouseID);
+
+/* A mouse has been removed from the system */
+extern void SDL_PrivateMouseRemoved(SDL_MouseID mouseID);
+
 /* Get the mouse state structure */
-SDL_Mouse *SDL_GetMouse(void);
+extern SDL_Mouse *SDL_GetMouse(void);
 
 /* Set the default mouse cursor */
 extern void SDL_SetDefaultCursor(SDL_Cursor *cursor);

+ 20 - 0
src/joystick/SDL_gamepad.c

@@ -2369,6 +2369,26 @@ int SDL_InitGamepads(void)
     return 0;
 }
 
+SDL_bool SDL_HasGamepad(void)
+{
+    int num_joysticks = 0;
+    int num_gamepads = 0;
+    SDL_JoystickID *joysticks = SDL_GetJoysticks(&num_joysticks);
+    if (joysticks) {
+        int i;
+        for (i = num_joysticks - 1; i >= 0 && num_gamepads == 0; --i) {
+            if (SDL_IsGamepad(joysticks[i])) {
+                ++num_gamepads;
+            }
+        }
+        SDL_free(joysticks);
+    }
+    if (num_gamepads > 0) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
 SDL_JoystickID *SDL_GetGamepads(int *count)
 {
     int num_joysticks = 0;

+ 19 - 0
src/joystick/SDL_joystick.c

@@ -682,6 +682,25 @@ SDL_bool SDL_JoystickHandledByAnotherDriver(struct SDL_JoystickDriver *driver, U
     return result;
 }
 
+SDL_bool SDL_HasJoystick(void)
+{
+    int i;
+    int total_joysticks = 0;
+
+    SDL_LockJoysticks();
+    {
+        for (i = 0; i < SDL_arraysize(SDL_joystick_drivers); ++i) {
+            total_joysticks += SDL_joystick_drivers[i]->GetCount();
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    if (total_joysticks > 0) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
 SDL_JoystickID *SDL_GetJoysticks(int *count)
 {
     int i, num_joysticks, device_index;

+ 2 - 2
src/joystick/android/SDL_sysjoystick.c

@@ -205,7 +205,7 @@ int Android_OnPadDown(int device_id, int keycode)
         if (item && item->joystick) {
             SDL_SendJoystickButton(timestamp, item->joystick, button, SDL_PRESSED);
         } else {
-            SDL_SendKeyboardKey(timestamp, SDL_PRESSED, button_to_scancode(button));
+            SDL_SendKeyboardKey(timestamp, 0, SDL_PRESSED, button_to_scancode(button));
         }
         SDL_UnlockJoysticks();
         return 0;
@@ -225,7 +225,7 @@ int Android_OnPadUp(int device_id, int keycode)
         if (item && item->joystick) {
             SDL_SendJoystickButton(timestamp, item->joystick, button, SDL_RELEASED);
         } else {
-            SDL_SendKeyboardKey(timestamp, SDL_RELEASED, button_to_scancode(button));
+            SDL_SendKeyboardKey(timestamp, 0, SDL_RELEASED, button_to_scancode(button));
         }
         SDL_UnlockJoysticks();
         return 0;

+ 21 - 154
src/joystick/windows/SDL_windowsjoystick.c

@@ -35,6 +35,7 @@
 #include "../SDL_sysjoystick.h"
 #include "../../thread/SDL_systhread.h"
 #include "../../core/windows/SDL_windows.h"
+#include "../../core/windows/SDL_hid.h"
 #if !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 #include <dbt.h>
 #endif
@@ -51,162 +52,34 @@
 #define DEVICE_NOTIFY_WINDOW_HANDLE 0x00000000
 #endif
 
-/* CM_Register_Notification definitions */
-
-#define CR_SUCCESS (0x00000000)
-
-/* Set up for C function definitions, even when using C++ */
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-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);
-
 /* local variables */
 static SDL_bool s_bJoystickThread = SDL_FALSE;
-static SDL_bool s_bWindowsDeviceChanged = SDL_FALSE;
 static SDL_Condition *s_condJoystickThread = NULL;
 static SDL_Mutex *s_mutexJoyStickEnum = NULL;
 static SDL_Thread *s_joystickThread = NULL;
 static SDL_bool s_bJoystickThreadQuit = SDL_FALSE;
+static Uint64 s_lastDeviceChange = 0;
 static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2L, 0xF16F, 0x11CF, { 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };
 
 JoyStick_DeviceData *SYS_Joystick; /* array to hold joystick ID values */
 
-#if !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
-static HMODULE cfgmgr32_lib_handle;
-static CM_Register_NotificationFunc CM_Register_Notification;
-static CM_Unregister_NotificationFunc CM_Unregister_Notification;
-static HCMNOTIFICATION s_DeviceNotificationFuncHandle;
 
-void WINDOWS_RAWINPUTEnabledChanged(void)
+static SDL_bool WindowsDeviceChanged(void)
 {
-    s_bWindowsDeviceChanged = SDL_TRUE;
+    return (s_lastDeviceChange != WIN_GetLastDeviceNotification());
 }
 
-static DWORD CALLBACK SDL_DeviceNotificationFunc(HCMNOTIFICATION hNotify, PVOID context, CM_NOTIFY_ACTION action, PCM_NOTIFY_EVENT_DATA eventData, DWORD event_data_size)
+static void SetWindowsDeviceChanged(void)
 {
-    if (action == CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL ||
-        action == CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL) {
-        s_bWindowsDeviceChanged = SDL_TRUE;
-    }
-    return ERROR_SUCCESS;
+    s_lastDeviceChange = 0;
 }
 
-static void SDL_CleanupDeviceNotificationFunc(void)
+void WINDOWS_RAWINPUTEnabledChanged(void)
 {
-    if (cfgmgr32_lib_handle) {
-        if (s_DeviceNotificationFuncHandle != NULL && CM_Unregister_Notification) {
-            CM_Unregister_Notification(s_DeviceNotificationFuncHandle);
-            s_DeviceNotificationFuncHandle = NULL;
-        }
-
-        FreeLibrary(cfgmgr32_lib_handle);
-        cfgmgr32_lib_handle = NULL;
-    }
+    SetWindowsDeviceChanged();
 }
 
-static SDL_bool SDL_CreateDeviceNotificationFunc(void)
-{
-    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 SDL_TRUE;
-            }
-        }
-    }
-
-    SDL_CleanupDeviceNotificationFunc();
-    return SDL_FALSE;
-}
+#if !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
 
 typedef struct
 {
@@ -239,7 +112,7 @@ static LRESULT CALLBACK SDL_PrivateJoystickDetectProc(HWND hwnd, UINT msg, WPARA
         if (wParam == IDT_SDL_DEVICE_CHANGE_TIMER_1 ||
             wParam == IDT_SDL_DEVICE_CHANGE_TIMER_2) {
             KillTimer(hwnd, wParam);
-            s_bWindowsDeviceChanged = SDL_TRUE;
+            SetWindowsDeviceChanged();
             return 0;
         }
         break;
@@ -327,7 +200,7 @@ static SDL_bool SDL_WaitForDeviceNotification(SDL_DeviceNotificationData *data,
     }
 
     SDL_UnlockMutex(mutex);
-    while (lastret > 0 && s_bWindowsDeviceChanged == SDL_FALSE) {
+    while (lastret > 0 && !WindowsDeviceChanged()) {
         lastret = GetMessage(&msg, NULL, 0, 0); /* WM_QUIT causes return value of 0 */
         if (lastret > 0) {
             TranslateMessage(&msg);
@@ -378,7 +251,7 @@ static int SDLCALL SDL_JoystickThread(void *_data)
                     const DWORD result = XINPUTGETCAPABILITIES(userId, XINPUT_FLAG_GAMEPAD, &capabilities);
                     const SDL_bool available = (result == ERROR_SUCCESS);
                     if (bOpenedXInputDevices[userId] != available) {
-                        s_bWindowsDeviceChanged = SDL_TRUE;
+                        SetWindowsDeviceChanged();
                         bOpenedXInputDevices[userId] = available;
                     }
                 }
@@ -476,13 +349,9 @@ static int WINDOWS_JoystickInit(void)
         return -1;
     }
 
-    s_bWindowsDeviceChanged = SDL_TRUE; /* force a scan of the system for joysticks this first time */
-
-    WINDOWS_JoystickDetect();
+    WIN_InitDeviceNotification();
 
 #if !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
-    SDL_CreateDeviceNotificationFunc();
-
     s_bJoystickThread = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_THREAD, SDL_FALSE);
     if (s_bJoystickThread) {
         if (SDL_StartJoystickThread() < 0) {
@@ -502,6 +371,11 @@ static int WINDOWS_JoystickInit(void)
         return -1;
     }
 #endif
+
+    SetWindowsDeviceChanged(); /* force a scan of the system for joysticks this first time */
+
+    WINDOWS_JoystickDetect();
+
     return 0;
 }
 
@@ -524,7 +398,7 @@ void WINDOWS_JoystickDetect(void)
     JoyStick_DeviceData *pCurList = NULL;
 
     /* only enum the devices if the joystick thread told us something changed */
-    if (!s_bWindowsDeviceChanged) {
+    if (!WindowsDeviceChanged()) {
         return; /* thread hasn't signaled, nothing to do right now. */
     }
 
@@ -532,7 +406,7 @@ void WINDOWS_JoystickDetect(void)
         SDL_LockMutex(s_mutexJoyStickEnum);
     }
 
-    s_bWindowsDeviceChanged = SDL_FALSE;
+    s_lastDeviceChange = WIN_GetLastDeviceNotification();
 
     pCurList = SYS_Joystick;
     SYS_Joystick = NULL;
@@ -774,8 +648,6 @@ void WINDOWS_JoystickQuit(void)
     } else {
         SDL_CleanupDeviceNotification(&s_notification_data);
     }
-
-    SDL_CleanupDeviceNotificationFunc();
 #endif
 
 #if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)
@@ -787,7 +659,7 @@ void WINDOWS_JoystickQuit(void)
     SDL_DINPUT_JoystickQuit();
     SDL_XINPUT_JoystickQuit();
 
-    s_bWindowsDeviceChanged = SDL_FALSE;
+    WIN_QuitDeviceNotification();
 }
 
 static SDL_bool WINDOWS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
@@ -819,11 +691,6 @@ SDL_JoystickDriver SDL_WINDOWS_JoystickDriver = {
     WINDOWS_JoystickGetGamepadMapping
 };
 
-/* Ends C function definitions when using C++ */
-#ifdef __cplusplus
-}
-#endif
-
 #else
 
 #ifdef SDL_JOYSTICK_RAWINPUT

+ 18 - 2
src/test/SDL_test_common.c

@@ -1661,6 +1661,14 @@ static void SDLTest_PrintEvent(const SDL_Event *event)
     case SDL_EVENT_WINDOW_DESTROYED:
         SDL_Log("SDL EVENT: Window %" SDL_PRIu32 " destroyed", event->window.windowID);
         break;
+    case SDL_EVENT_KEYBOARD_ADDED:
+        SDL_Log("SDL EVENT: Keyboard %" SDL_PRIu32 " attached",
+                event->kdevice.which);
+        break;
+    case SDL_EVENT_KEYBOARD_REMOVED:
+        SDL_Log("SDL EVENT: Keyboard %" SDL_PRIu32 " removed",
+                event->kdevice.which);
+        break;
     case SDL_EVENT_KEY_DOWN:
     case SDL_EVENT_KEY_UP: {
         char modstr[64];
@@ -1691,6 +1699,14 @@ static void SDLTest_PrintEvent(const SDL_Event *event)
     case SDL_EVENT_KEYMAP_CHANGED:
         SDL_Log("SDL EVENT: Keymap changed");
         break;
+    case SDL_EVENT_MOUSE_ADDED:
+        SDL_Log("SDL EVENT: Mouse %" SDL_PRIu32 " attached",
+                event->mdevice.which);
+        break;
+    case SDL_EVENT_MOUSE_REMOVED:
+        SDL_Log("SDL EVENT: Mouse %" SDL_PRIu32 " removed",
+                event->mdevice.which);
+        break;
     case SDL_EVENT_MOUSE_MOTION:
         SDL_Log("SDL EVENT: Mouse: moved to %g,%g (%g,%g) in window %" SDL_PRIu32,
                 event->motion.x, event->motion.y,
@@ -1712,7 +1728,7 @@ static void SDLTest_PrintEvent(const SDL_Event *event)
                 event->wheel.x, event->wheel.y, event->wheel.direction, event->wheel.windowID);
         break;
     case SDL_EVENT_JOYSTICK_ADDED:
-        SDL_Log("SDL EVENT: Joystick index %" SDL_PRIu32 " attached",
+        SDL_Log("SDL EVENT: Joystick %" SDL_PRIu32 " attached",
                 event->jdevice.which);
         break;
     case SDL_EVENT_JOYSTICK_REMOVED:
@@ -1768,7 +1784,7 @@ static void SDLTest_PrintEvent(const SDL_Event *event)
                 event->jbutton.which, event->jbutton.button);
         break;
     case SDL_EVENT_GAMEPAD_ADDED:
-        SDL_Log("SDL EVENT: Gamepad index %" SDL_PRIu32 " attached",
+        SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " attached",
                 event->gdevice.which);
         break;
     case SDL_EVENT_GAMEPAD_REMOVED:

+ 2 - 2
src/video/android/SDL_androidkeyboard.c

@@ -330,12 +330,12 @@ static SDL_Scancode TranslateKeycode(int keycode)
 
 int Android_OnKeyDown(int keycode)
 {
-    return SDL_SendKeyboardKey(0, SDL_PRESSED, TranslateKeycode(keycode));
+    return SDL_SendKeyboardKey(0, 0, SDL_PRESSED, TranslateKeycode(keycode));
 }
 
 int Android_OnKeyUp(int keycode)
 {
-    return SDL_SendKeyboardKey(0, SDL_RELEASED, TranslateKeycode(keycode));
+    return SDL_SendKeyboardKey(0, 0, SDL_RELEASED, TranslateKeycode(keycode));
 }
 
 SDL_bool Android_HasScreenKeyboardSupport(SDL_VideoDevice *_this)

+ 5 - 5
src/video/cocoa/SDL_cocoakeyboard.m

@@ -231,9 +231,9 @@ static void HandleModifiers(SDL_VideoDevice *_this, SDL_Scancode code, unsigned
     }
 
     if (pressed) {
-        SDL_SendKeyboardKey(0, SDL_PRESSED, code);
+        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, code);
     } else {
-        SDL_SendKeyboardKey(0, SDL_RELEASED, code);
+        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, code);
     }
 }
 
@@ -279,7 +279,7 @@ static void UpdateKeymap(SDL_CocoaVideoData *data, SDL_bool send_event)
                 continue;
             }
 
-            /* 
+            /*
              * Swap the scancode for these two wrongly translated keys
              * UCKeyTranslate() function does not do its job properly for ISO layout keyboards, where the key '@',
              * which is located in the top left corner of the keyboard right under the Escape key, and the additional
@@ -414,7 +414,7 @@ void Cocoa_HandleKeyEvent(SDL_VideoDevice *_this, NSEvent *event)
             UpdateKeymap(data, SDL_TRUE);
         }
 
-        SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_PRESSED, code);
+        SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), 0, SDL_PRESSED, code);
 #ifdef DEBUG_SCANCODES
         if (code == SDL_SCANCODE_UNKNOWN) {
             SDL_Log("The key you just pressed is not recognized by SDL. To help get this fixed, report this to the SDL forums/mailing list <https://discourse.libsdl.org/> or to Christian Walther <cwalther@gmx.ch>. Mac virtual key code is %d.\n", scancode);
@@ -433,7 +433,7 @@ void Cocoa_HandleKeyEvent(SDL_VideoDevice *_this, NSEvent *event)
         }
         break;
     case NSEventTypeKeyUp:
-        SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), SDL_RELEASED, code);
+        SDL_SendKeyboardKey(Cocoa_GetEventTimestamp([event timestamp]), 0, SDL_RELEASED, code);
         break;
     case NSEventTypeFlagsChanged: {
         // see if the new modifierFlags mean any existing keys should be pressed/released...

+ 2 - 2
src/video/cocoa/SDL_cocoawindow.m

@@ -1381,8 +1381,8 @@ static SDL_bool Cocoa_IsZoomed(SDL_Window *window)
     const SDL_bool osenabled = ([theEvent modifierFlags] & NSEventModifierFlagCapsLock) ? SDL_TRUE : SDL_FALSE;
     const SDL_bool sdlenabled = (SDL_GetModState() & SDL_KMOD_CAPS) ? SDL_TRUE : SDL_FALSE;
     if (osenabled ^ sdlenabled) {
-        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
-        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
+        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
+        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
     }
 }
 - (void)keyDown:(NSEvent *)theEvent

+ 1 - 1
src/video/emscripten/SDL_emscriptenevents.c

@@ -811,7 +811,7 @@ static EM_BOOL Emscripten_HandleKey(int eventType, const EmscriptenKeyboardEvent
     }
 
     if (scancode != SDL_SCANCODE_UNKNOWN) {
-        SDL_SendKeyboardKeyAndKeycode(0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? SDL_PRESSED : SDL_RELEASED, scancode, keycode);
+        SDL_SendKeyboardKeyAndKeycode(0, 0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? SDL_PRESSED : SDL_RELEASED, scancode, keycode);
     }
 
     /* if TEXTINPUT events are enabled we can't prevent keydown or we won't get keypress

+ 1 - 1
src/video/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;

+ 2 - 2
src/video/ngage/SDL_ngageevents.cpp

@@ -154,10 +154,10 @@ int HandleWsEvent(SDL_VideoDevice *_this, const TWsEvent &aWsEvent)
 
     switch (aWsEvent.Type()) {
     case EEventKeyDown: /* Key events */
-        SDL_SendKeyboardKey(0, SDL_PRESSED, ConvertScancode(_this, aWsEvent.Key()->iScanCode));
+        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, ConvertScancode(_this, aWsEvent.Key()->iScanCode));
         break;
     case EEventKeyUp: /* Key events */
-        SDL_SendKeyboardKey(0, SDL_RELEASED, ConvertScancode(_this, aWsEvent.Key()->iScanCode));
+        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, ConvertScancode(_this, aWsEvent.Key()->iScanCode));
         break;
     case EEventFocusGained: /* SDL window got focus */
         phdata->NGAGE_IsWindowFocused = ETrue;

+ 2 - 2
src/video/psp/SDL_pspevents.c

@@ -90,7 +90,7 @@ void PSP_PumpEvents(SDL_VideoDevice *_this)
     if (changed) {
         for (i = 0; i < sizeof(keymap_psp) / sizeof(keymap_psp[0]); i++) {
             if (changed & keymap_psp[i].id) {
-                SDL_SendKeyboardKey(0, (keys & keymap_psp[i].id) ? SDL_PRESSED : SDL_RELEASED, SDL_GetScancodeFromKey(keymap_psp[i].sym));
+                SDL_SendKeyboardKey(0, 0, (keys & keymap_psp[i].id) ? SDL_PRESSED : SDL_RELEASED, SDL_GetScancodeFromKey(keymap_psp[i].sym));
             }
         }
     }
@@ -113,7 +113,7 @@ void PSP_PumpEvents(SDL_VideoDevice *_this)
                     sym.sym = keymap[raw];
                     /* not tested */
                     /* SDL_PrivateKeyboard(pressed?SDL_PRESSED:SDL_RELEASED, &sym); */
-                    SDL_SendKeyboardKey(0, (keys & keymap_psp[i].id) ? SDL_PRESSED : SDL_RELEASED, SDL_GetScancodeFromKey(keymap[raw]));
+                    SDL_SendKeyboardKey(0, 0, (keys & keymap_psp[i].id) ? SDL_PRESSED : SDL_RELEASED, SDL_GetScancodeFromKey(keymap[raw]));
                 }
             }
         }

+ 2 - 2
src/video/qnx/SDL_qnxkeyboard.c

@@ -125,8 +125,8 @@ void handleKeyboardEvent(screen_event_t event)
     // FIXME:
     // Need to handle more key states (such as key combinations).
     if (val & KEY_DOWN) {
-        SDL_SendKeyboardKey(0, SDL_PRESSED, scancode);
+        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, scancode);
     } else {
-        SDL_SendKeyboardKey(0, SDL_RELEASED, scancode);
+        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, scancode);
     }
 }

+ 2 - 2
src/video/riscos/SDL_riscosevents.c

@@ -58,7 +58,7 @@ void RISCOS_PollKeyboard(SDL_VideoDevice *_this)
     for (i = 0; i < RISCOS_MAX_KEYS_PRESSED; i++) {
         if (driverdata->key_pressed[i] != 255) {
             if ((_kernel_osbyte(129, driverdata->key_pressed[i] ^ 0xff, 0xff) & 0xff) != 255) {
-                SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_RISCOS_translate_keycode(driverdata->key_pressed[i]));
+                SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_RISCOS_translate_keycode(driverdata->key_pressed[i]));
                 driverdata->key_pressed[i] = 255;
             }
         }
@@ -81,7 +81,7 @@ void RISCOS_PollKeyboard(SDL_VideoDevice *_this)
             break;
 
         default:
-            SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_RISCOS_translate_keycode(key));
+            SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_RISCOS_translate_keycode(key));
 
             /* Record the press so we can detect release later. */
             for (i = 0; i < RISCOS_MAX_KEYS_PRESSED; i++) {

+ 0 - 2
src/video/uikit/SDL_uikitevents.h

@@ -29,11 +29,9 @@ extern Uint64 UIKit_GetEventTimestamp(NSTimeInterval nsTimestamp);
 extern void UIKit_PumpEvents(SDL_VideoDevice *_this);
 
 extern void SDL_InitGCKeyboard(void);
-extern SDL_bool SDL_HasGCKeyboard(void);
 extern void SDL_QuitGCKeyboard(void);
 
 extern void SDL_InitGCMouse(void);
-extern SDL_bool SDL_HasGCMouse(void);
 extern SDL_bool SDL_GCMouseRelativeMode(void);
 extern void SDL_QuitGCMouse(void);
 

+ 16 - 30
src/video/uikit/SDL_uikitevents.m

@@ -171,15 +171,17 @@ void UIKit_PumpEvents(SDL_VideoDevice *_this)
 
 #ifdef ENABLE_GCKEYBOARD
 
-static SDL_bool keyboard_connected = SDL_FALSE;
 static id keyboard_connect_observer = nil;
 static id keyboard_disconnect_observer = nil;
 
 static void OnGCKeyboardConnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
 {
-    keyboard_connected = SDL_TRUE;
+    SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard;
+
+    SDL_PrivateKeyboardAdded(keyboardID);
+
     keyboard.keyboardInput.keyChangedHandler = ^(GCKeyboardInput *kbrd, GCControllerButtonInput *key, GCKeyCode keyCode, BOOL pressed) {
-      SDL_SendKeyboardKey(0, pressed ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)keyCode);
+        SDL_SendKeyboardKey(0, keyboardID, pressed ? SDL_PRESSED : SDL_RELEASED, (SDL_Scancode)keyCode);
     };
 
     dispatch_queue_t queue = dispatch_queue_create("org.libsdl.input.keyboard", DISPATCH_QUEUE_SERIAL);
@@ -189,8 +191,11 @@ static void OnGCKeyboardConnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0
 
 static void OnGCKeyboardDisconnected(GCKeyboard *keyboard) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
 {
+    SDL_KeyboardID keyboardID = (SDL_KeyboardID)(uintptr_t)keyboard;
+
+    SDL_PrivateKeyboardRemoved(keyboardID);
+
     keyboard.keyboardInput.keyChangedHandler = nil;
-    keyboard_connected = SDL_FALSE;
 }
 
 void SDL_InitGCKeyboard(void)
@@ -222,11 +227,6 @@ void SDL_InitGCKeyboard(void)
     }
 }
 
-SDL_bool SDL_HasGCKeyboard(void)
-{
-    return keyboard_connected;
-}
-
 void SDL_QuitGCKeyboard(void)
 {
     @autoreleasepool {
@@ -256,11 +256,6 @@ void SDL_InitGCKeyboard(void)
 {
 }
 
-SDL_bool SDL_HasGCKeyboard(void)
-{
-    return SDL_FALSE;
-}
-
 void SDL_QuitGCKeyboard(void)
 {
 }
@@ -269,7 +264,6 @@ void SDL_QuitGCKeyboard(void)
 
 #ifdef ENABLE_GCMOUSE
 
-static int mice_connected = 0;
 static id mouse_connect_observer = nil;
 static id mouse_disconnect_observer = nil;
 static bool mouse_relative_mode = SDL_FALSE;
@@ -291,7 +285,7 @@ static void UpdateScrollDirection(void)
         /* Couldn't read the preference, assume natural scrolling direction */
         naturalScrollDirection = YES;
     }
-    if (naturalScrollDirection) {    
+    if (naturalScrollDirection) {
         mouse_scroll_direction = SDL_MOUSEWHEEL_FLIPPED;
     } else {
         mouse_scroll_direction = SDL_MOUSEWHEEL_NORMAL;
@@ -323,7 +317,9 @@ static void OnGCMouseButtonChanged(SDL_MouseID mouseID, Uint8 button, BOOL press
 
 static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
 {
-    SDL_MouseID mouseID = mice_connected;
+    SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse;
+
+    SDL_PrivateMouseAdded(mouseID);
 
     mouse.mouseInput.leftButton.pressedChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
       OnGCMouseButtonChanged(mouseID, SDL_BUTTON_LEFT, pressed);
@@ -370,14 +366,12 @@ static void OnGCMouseConnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14
     dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
     mouse.handlerQueue = queue;
 
-    ++mice_connected;
-
     UpdatePointerLock();
 }
 
 static void OnGCMouseDisconnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
 {
-    --mice_connected;
+    SDL_MouseID mouseID = (SDL_MouseID)(uintptr_t)mouse;
 
     mouse.mouseInput.mouseMovedHandler = nil;
 
@@ -390,6 +384,8 @@ static void OnGCMouseDisconnected(GCMouse *mouse) API_AVAILABLE(macos(11.0), ios
     }
 
     UpdatePointerLock();
+
+    SDL_PrivateMouseRemoved(mouseID);
 }
 
 void SDL_InitGCMouse(void)
@@ -432,11 +428,6 @@ void SDL_InitGCMouse(void)
     }
 }
 
-SDL_bool SDL_HasGCMouse(void)
-{
-    return (mice_connected > 0);
-}
-
 SDL_bool SDL_GCMouseRelativeMode(void)
 {
     return mouse_relative_mode;
@@ -473,11 +464,6 @@ void SDL_InitGCMouse(void)
 {
 }
 
-SDL_bool SDL_HasGCMouse(void)
-{
-    return SDL_FALSE;
-}
-
 SDL_bool SDL_GCMouseRelativeMode(void)
 {
     return SDL_FALSE;

+ 8 - 8
src/video/uikit/SDL_uikitview.m

@@ -229,7 +229,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
 #if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
         if (@available(iOS 13.4, *)) {
             if (touch.type == UITouchTypeIndirectPointer) {
-                if (!SDL_HasGCMouse()) {
+                if (!SDL_HasMouse()) {
                     int i;
 
                     for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
@@ -285,7 +285,7 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
 #if !defined(SDL_PLATFORM_TVOS) && defined(__IPHONE_13_4)
         if (@available(iOS 13.4, *)) {
             if (touch.type == UITouchTypeIndirectPointer) {
-                if (!SDL_HasGCMouse()) {
+                if (!SDL_HasMouse()) {
                     int i;
 
                     for (i = 1; i <= MAX_MOUSE_BUTTONS; ++i) {
@@ -411,10 +411,10 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
 
 - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
 {
-    if (!SDL_HasGCKeyboard()) {
+    if (!SDL_HasKeyboard()) {
         for (UIPress *press in presses) {
             SDL_Scancode scancode = [self scancodeFromPress:press];
-            SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_PRESSED, scancode);
+            SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), 0, SDL_PRESSED, scancode);
         }
     }
     if (SDL_TextInputActive()) {
@@ -424,10 +424,10 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
 
 - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
 {
-    if (!SDL_HasGCKeyboard()) {
+    if (!SDL_HasKeyboard()) {
         for (UIPress *press in presses) {
             SDL_Scancode scancode = [self scancodeFromPress:press];
-            SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_RELEASED, scancode);
+            SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), 0, SDL_RELEASED, scancode);
         }
     }
     if (SDL_TextInputActive()) {
@@ -437,10 +437,10 @@ extern int SDL_AppleTVRemoteOpenedAsJoystick;
 
 - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
 {
-    if (!SDL_HasGCKeyboard()) {
+    if (!SDL_HasKeyboard()) {
         for (UIPress *press in presses) {
             SDL_Scancode scancode = [self scancodeFromPress:press];
-            SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), SDL_RELEASED, scancode);
+            SDL_SendKeyboardKey(UIKit_GetEventTimestamp([event timestamp]), 0, SDL_RELEASED, scancode);
         }
     }
     if (SDL_TextInputActive()) {

+ 28 - 28
src/video/vita/SDL_vitakeyboard.c

@@ -62,40 +62,40 @@ void VITA_PollKeyboard(void)
             // The k_report only reports the state of the LED
             if (k_reports[numReports - 1].modifiers[1] & 0x1) {
                 if (!(locks & 0x1)) {
-                    SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_NUMLOCKCLEAR);
+                    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_NUMLOCKCLEAR);
                     locks |= 0x1;
                 }
             } else {
                 if (locks & 0x1) {
-                    SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_NUMLOCKCLEAR);
-                    SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_NUMLOCKCLEAR);
-                    SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_NUMLOCKCLEAR);
+                    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_NUMLOCKCLEAR);
+                    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_NUMLOCKCLEAR);
+                    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_NUMLOCKCLEAR);
                     locks &= ~0x1;
                 }
             }
 
             if (k_reports[numReports - 1].modifiers[1] & 0x2) {
                 if (!(locks & 0x2)) {
-                    SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
+                    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
                     locks |= 0x2;
                 }
             } else {
                 if (locks & 0x2) {
-                    SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
-                    SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
-                    SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
+                    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
+                    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_CAPSLOCK);
+                    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_CAPSLOCK);
                     locks &= ~0x2;
                 }
             }
 
             if (k_reports[numReports - 1].modifiers[1] & 0x4) {
                 if (!(locks & 0x4)) {
-                    SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_SCROLLLOCK);
+                    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_SCROLLLOCK);
                     locks |= 0x4;
                 }
             } else {
                 if (locks & 0x4) {
-                    SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_SCROLLLOCK);
+                    SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_SCROLLLOCK);
                     locks &= ~0x4;
                 }
             }
@@ -105,58 +105,58 @@ void VITA_PollKeyboard(void)
 
                 if (changed_modifiers & 0x01) {
                     if (prev_modifiers & 0x01) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LCTRL);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_LCTRL);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LCTRL);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_LCTRL);
                     }
                 }
                 if (changed_modifiers & 0x02) {
                     if (prev_modifiers & 0x02) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LSHIFT);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_LSHIFT);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LSHIFT);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_LSHIFT);
                     }
                 }
                 if (changed_modifiers & 0x04) {
                     if (prev_modifiers & 0x04) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LALT);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_LALT);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LALT);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_LALT);
                     }
                 }
                 if (changed_modifiers & 0x08) {
                     if (prev_modifiers & 0x08) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LGUI);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_LGUI);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_LGUI);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_LGUI);
                     }
                 }
                 if (changed_modifiers & 0x10) {
                     if (prev_modifiers & 0x10) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RCTRL);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_RCTRL);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RCTRL);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_RCTRL);
                     }
                 }
                 if (changed_modifiers & 0x20) {
                     if (prev_modifiers & 0x20) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RSHIFT);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_RSHIFT);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RSHIFT);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_RSHIFT);
                     }
                 }
                 if (changed_modifiers & 0x40) {
                     if (prev_modifiers & 0x40) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RALT);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_RALT);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RALT);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_RALT);
                     }
                 }
                 if (changed_modifiers & 0x80) {
                     if (prev_modifiers & 0x80) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RGUI);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_RGUI);
                     } else {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, SDL_SCANCODE_RGUI);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, SDL_SCANCODE_RGUI);
                     }
                 }
             }
@@ -170,10 +170,10 @@ void VITA_PollKeyboard(void)
                 if (keyCode != prev_keys[i]) {
 
                     if (prev_keys[i]) {
-                        SDL_SendKeyboardKey(0, SDL_RELEASED, prev_keys[i]);
+                        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, prev_keys[i]);
                     }
                     if (keyCode) {
-                        SDL_SendKeyboardKey(0, SDL_PRESSED, keyCode);
+                        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, keyCode);
                     }
                     prev_keys[i] = keyCode;
                 }

+ 3 - 3
src/video/wayland/SDL_waylandevents.c

@@ -264,7 +264,7 @@ static SDL_bool keyboard_repeat_handle(SDL_WaylandKeyboardRepeat *repeat_info, U
     while (elapsed >= repeat_info->next_repeat_ns) {
         if (repeat_info->scancode != SDL_SCANCODE_UNKNOWN) {
             const Uint64 timestamp = repeat_info->wl_press_time_ns + repeat_info->next_repeat_ns;
-            SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetEventTimestamp(timestamp), SDL_PRESSED, repeat_info->scancode);
+            SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetEventTimestamp(timestamp), 0, SDL_PRESSED, repeat_info->scancode);
         }
         if (repeat_info->text[0]) {
             SDL_SendKeyboardText(repeat_info->text);
@@ -1483,7 +1483,7 @@ static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard,
         case SDLK_RGUI:
         case SDLK_MODE:
             Wayland_HandleModifierKeys(input, scancode, SDL_TRUE);
-            SDL_SendKeyboardKeyIgnoreModifiers(0, SDL_PRESSED, scancode);
+            SDL_SendKeyboardKeyIgnoreModifiers(0, 0, SDL_PRESSED, scancode);
             break;
         default:
             break;
@@ -1618,7 +1618,7 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard,
     if (!handled_by_ime) {
         scancode = Wayland_get_scancode_from_key(input, key + 8);
         Wayland_HandleModifierKeys(input, scancode, state == WL_KEYBOARD_KEY_STATE_PRESSED);
-        SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetKeyboardTimestamp(input, time), state == WL_KEYBOARD_KEY_STATE_PRESSED ? SDL_PRESSED : SDL_RELEASED, scancode);
+        SDL_SendKeyboardKeyIgnoreModifiers(Wayland_GetKeyboardTimestamp(input, time), 0, state == WL_KEYBOARD_KEY_STATE_PRESSED ? SDL_PRESSED : SDL_RELEASED, scancode);
     }
 
     if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {

+ 138 - 16
src/video/windows/SDL_windowsevents.c

@@ -27,6 +27,7 @@
 #include "../../events/SDL_touch_c.h"
 #include "../../events/scancodes_windows.h"
 #include "../../main/SDL_main_callbacks.h"
+#include "../../core/windows/SDL_hid.h"
 
 /* Dropfile support */
 #include <shellapi.h>
@@ -503,9 +504,9 @@ WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
     }
 
     if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
-        SDL_SendKeyboardKey(0, SDL_PRESSED, scanCode);
+        SDL_SendKeyboardKey(0, 0, SDL_PRESSED, scanCode);
     } else {
-        SDL_SendKeyboardKey(0, SDL_RELEASED, scanCode);
+        SDL_SendKeyboardKey(0, 0, 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
@@ -529,11 +530,7 @@ static void WIN_HandleRawMouseInput(Uint64 timestamp, SDL_WindowData *data, HAND
         return;
     }
 
-    /* We do all of our mouse state checking against mouse ID 0
-     * We would only use the actual hDevice if we were tracking
-     * all mouse motion independently, and never using mouse ID 0.
-     */
-    mouseID = 0; /* (SDL_MouseID)(uintptr_t)inp.header.hDevice; */
+    mouseID = (SDL_MouseID)(uintptr_t)hDevice;
 
     if ((rawmouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
         if (rawmouse->lLastX || rawmouse->lLastY) {
@@ -698,8 +695,130 @@ void WIN_PollRawMouseInput(void)
 
 #endif /*!defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)*/
 
-LRESULT CALLBACK
-WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+static void AddDeviceID(Uint32 deviceID, Uint32 **list, int *count)
+{
+    int new_count = (*count + 1);
+    Uint32 *new_list = (Uint32 *)SDL_realloc(*list, new_count * sizeof(*new_list));
+    if (!new_list) {
+        /* Oh well, we'll drop this one */
+        return;
+    }
+    new_list[new_count - 1] = deviceID;
+
+    *count = new_count;
+    *list = new_list;
+}
+
+static SDL_bool HasDeviceID(Uint32 deviceID, Uint32 *list, int count)
+{
+    for (int i = 0; i < count; ++i) {
+        if (deviceID == list[i]) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
+void WIN_CheckKeyboardAndMouseHotplug(SDL_bool initial_check)
+{
+    PRAWINPUTDEVICELIST raw_devices = NULL;
+    UINT raw_device_count = 0;
+    int old_keyboard_count = 0;
+    SDL_KeyboardID *old_keyboards = NULL;
+    int new_keyboard_count = 0;
+    SDL_KeyboardID *new_keyboards = NULL;
+    int old_mouse_count = 0;
+    SDL_MouseID *old_mice = NULL;
+    int new_mouse_count = 0;
+    SDL_MouseID *new_mice = NULL;
+
+    /* Check to see if anything has changed */
+    static Uint64 s_last_device_change;
+    Uint64 last_device_change = WIN_GetLastDeviceNotification();
+    if (!initial_check && last_device_change == s_last_device_change) {
+        return;
+    }
+    s_last_device_change = last_device_change;
+
+    if ((GetRawInputDeviceList(NULL, &raw_device_count, sizeof(RAWINPUTDEVICELIST)) == -1) || (!raw_device_count)) {
+        return; /* oh well. */
+    }
+
+    raw_devices = (PRAWINPUTDEVICELIST)SDL_malloc(sizeof(RAWINPUTDEVICELIST) * raw_device_count);
+    if (!raw_devices) {
+        return; /* oh well. */
+    }
+
+    raw_device_count = GetRawInputDeviceList(raw_devices, &raw_device_count, sizeof(RAWINPUTDEVICELIST));
+    if (raw_device_count == (UINT)-1) {
+        SDL_free(raw_devices);
+        raw_devices = NULL;
+        return; /* oh well. */
+    }
+
+    for (UINT i = 0; i < raw_device_count; i++) {
+        RID_DEVICE_INFO rdi;
+        char devName[MAX_PATH] = { 0 };
+        UINT rdiSize = sizeof(rdi);
+        UINT nameSize = SDL_arraysize(devName);
+        int vendor = 0, product = 0;
+
+        rdi.cbSize = sizeof(rdi);
+
+        if (GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) == ((UINT)-1) ||
+            GetRawInputDeviceInfoA(raw_devices[i].hDevice, RIDI_DEVICENAME, devName, &nameSize) == ((UINT)-1)) {
+            continue;
+        }
+
+        SDL_sscanf(devName, "\\\\?\\HID#VID_%X&PID_%X&", &vendor, &product);
+
+        switch (raw_devices[i].dwType) {
+        case RIM_TYPEKEYBOARD:
+            if (SDL_IsKeyboard((Uint16)vendor, (Uint16)product, rdi.keyboard.dwNumberOfKeysTotal)) {
+                AddDeviceID((Uint32)(uintptr_t)raw_devices[i].hDevice, &new_keyboards, &new_keyboard_count);
+            }
+            break;
+        case RIM_TYPEMOUSE:
+            if (SDL_IsMouse((Uint16)vendor, (Uint16)product)) {
+                AddDeviceID((Uint32)(uintptr_t)raw_devices[i].hDevice, &new_mice, &new_mouse_count);
+            }
+            break;
+        default:
+            break;
+        }
+    }
+    SDL_free(raw_devices);
+
+    old_keyboards = SDL_GetKeyboards(&old_keyboard_count);
+    for (int i = old_keyboard_count; i--;) {
+        if (!HasDeviceID(old_keyboards[i], new_keyboards, new_keyboard_count)) {
+            SDL_PrivateKeyboardRemoved(old_keyboards[i]);
+        }
+    }
+    for (int i = 0; i < new_keyboard_count; ++i) {
+        if (!HasDeviceID(new_keyboards[i], old_keyboards, old_keyboard_count)) {
+            SDL_PrivateKeyboardAdded(new_keyboards[i]);
+        }
+    }
+    SDL_free(new_keyboards);
+    SDL_free(old_keyboards);
+
+    old_mice = SDL_GetMice(&old_mouse_count);
+    for (int i = old_mouse_count; i--;) {
+        if (!HasDeviceID(old_mice[i], new_mice, new_mouse_count)) {
+            SDL_PrivateMouseRemoved(old_mice[i]);
+        }
+    }
+    for (int i = 0; i < new_mouse_count; ++i) {
+        if (!HasDeviceID(new_mice[i], old_mice, old_mouse_count)) {
+            SDL_PrivateMouseAdded(new_mice[i]);
+        }
+    }
+    SDL_free(new_mice);
+    SDL_free(old_mice);
+}
+
+LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 {
     SDL_WindowData *data;
     LRESULT returnCode = -1;
@@ -923,7 +1042,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         }
 
         if (code != SDL_SCANCODE_UNKNOWN) {
-            SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_PRESSED, code);
+            SDL_SendKeyboardKey(WIN_GetEventTimestamp(), 0, SDL_PRESSED, code);
         }
     }
 
@@ -939,9 +1058,9 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
         if (code != SDL_SCANCODE_UNKNOWN) {
             if (code == SDL_SCANCODE_PRINTSCREEN &&
                 keyboardState[code] == SDL_RELEASED) {
-                SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_PRESSED, code);
+                SDL_SendKeyboardKey(WIN_GetEventTimestamp(), 0, SDL_PRESSED, code);
             }
-            SDL_SendKeyboardKey(WIN_GetEventTimestamp(), SDL_RELEASED, code);
+            SDL_SendKeyboardKey(WIN_GetEventTimestamp(), 0, SDL_RELEASED, code);
         }
     }
         returnCode = 0;
@@ -1808,10 +1927,10 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
        and if we think a key is pressed when Windows doesn't, unstick it in SDL's state. */
     keystate = SDL_GetKeyboardState(NULL);
     if ((keystate[SDL_SCANCODE_LSHIFT] == SDL_PRESSED) && !(GetKeyState(VK_LSHIFT) & 0x8000)) {
-        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LSHIFT);
+        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_LSHIFT);
     }
     if ((keystate[SDL_SCANCODE_RSHIFT] == SDL_PRESSED) && !(GetKeyState(VK_RSHIFT) & 0x8000)) {
-        SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RSHIFT);
+        SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_RSHIFT);
     }
 
     /* The Windows key state gets lost when using Windows+Space or Windows+G shortcuts and
@@ -1820,10 +1939,10 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
     focusWindow = SDL_GetKeyboardFocus();
     if (!focusWindow || !(focusWindow->flags & SDL_WINDOW_KEYBOARD_GRABBED)) {
         if ((keystate[SDL_SCANCODE_LGUI] == SDL_PRESSED) && !(GetKeyState(VK_LWIN) & 0x8000)) {
-            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_LGUI);
+            SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_LGUI);
         }
         if ((keystate[SDL_SCANCODE_RGUI] == SDL_PRESSED) && !(GetKeyState(VK_RWIN) & 0x8000)) {
-            SDL_SendKeyboardKey(0, SDL_RELEASED, SDL_SCANCODE_RGUI);
+            SDL_SendKeyboardKey(0, 0, SDL_RELEASED, SDL_SCANCODE_RGUI);
         }
     }
 
@@ -1832,6 +1951,9 @@ void WIN_PumpEvents(SDL_VideoDevice *_this)
 
     /* Update mouse capture */
     WIN_UpdateMouseCapture();
+
+    WIN_CheckKeyboardAndMouseHotplug(SDL_FALSE);
+
 #endif /*!defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)*/
 
 #ifdef SDL_PLATFORM_GDK

+ 1 - 0
src/video/windows/SDL_windowsevents.h

@@ -31,6 +31,7 @@ extern LRESULT CALLBACK WIN_KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lP
 extern LRESULT CALLBACK WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam,
                                        LPARAM lParam);
 extern void WIN_PollRawMouseInput(void);
+extern void WIN_CheckKeyboardAndMouseHotplug(SDL_bool initial_check);
 extern void WIN_PumpEvents(SDL_VideoDevice *_this);
 extern void WIN_SendWakeupEvent(SDL_VideoDevice *_this, SDL_Window *window);
 extern int WIN_WaitEventTimeout(SDL_VideoDevice *_this, Sint64 timeoutNS);

+ 4 - 0
src/video/windows/SDL_windowsvideo.c

@@ -28,6 +28,7 @@
 #include "../SDL_sysvideo.h"
 #include "../SDL_pixels_c.h"
 #include "../../SDL_hints_c.h"
+#include "../../core/windows/SDL_hid.h"
 
 #include "SDL_windowsvideo.h"
 #include "SDL_windowsframebuffer.h"
@@ -455,6 +456,8 @@ int WIN_VideoInit(SDL_VideoDevice *_this)
 
     WIN_InitKeyboard(_this);
     WIN_InitMouse(_this);
+    WIN_InitDeviceNotification();
+    WIN_CheckKeyboardAndMouseHotplug(SDL_TRUE);
 #endif
 
     SDL_AddHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL);
@@ -472,6 +475,7 @@ void WIN_VideoQuit(SDL_VideoDevice *_this)
 {
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     WIN_QuitModes(_this);
+    WIN_QuitDeviceNotification();
     WIN_QuitKeyboard(_this);
     WIN_QuitMouse(_this);
 #endif

+ 1 - 1
src/video/winrt/SDL_winrtkeyboard.cpp

@@ -77,7 +77,7 @@ void WINRT_ProcessAcceleratorKeyActivated(Windows::UI::Core::AcceleratorKeyEvent
     }
 
     code = WINRT_TranslateKeycode(args->VirtualKey, args->KeyStatus);
-    SDL_SendKeyboardKey(0, state, code);
+    SDL_SendKeyboardKey(0, 0, state, code);
 }
 
 void WINRT_ProcessCharacterReceivedEvent(SDL_Window *window, Windows::UI::Core::CharacterReceivedEventArgs ^ args)

+ 6 - 6
src/video/x11/SDL_x11events.c

@@ -437,13 +437,13 @@ void X11_ReconcileKeyboardState(SDL_VideoDevice *_this)
             case SDLK_LGUI:
             case SDLK_RGUI:
             case SDLK_MODE:
-                SDL_SendKeyboardKey(0, SDL_PRESSED, scancode);
+                SDL_SendKeyboardKey(0, 0, SDL_PRESSED, scancode);
                 break;
             default:
                 break;
             }
         } else if (!x11KeyPressed && sdlKeyPressed) {
-            SDL_SendKeyboardKey(0, SDL_RELEASED, scancode);
+            SDL_SendKeyboardKey(0, 0, SDL_RELEASED, scancode);
         }
     }
 }
@@ -957,9 +957,9 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
             videodata->filter_time = xevent->xkey.time;
 
             if (orig_event_type == KeyPress) {
-                SDL_SendKeyboardKey(0, SDL_PRESSED, scancode);
+                SDL_SendKeyboardKey(0, 0, SDL_PRESSED, scancode);
             } else {
-                SDL_SendKeyboardKey(0, SDL_RELEASED, scancode);
+                SDL_SendKeyboardKey(0, 0, SDL_RELEASED, scancode);
             }
 #endif
         }
@@ -1262,7 +1262,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
             if (xevent->type == KeyPress) {
                 /* Don't send the key if it looks like a duplicate of a filtered key sent by an IME */
                 if (xevent->xkey.keycode != videodata->filter_code || xevent->xkey.time != videodata->filter_time) {
-                    SDL_SendKeyboardKey(0, SDL_PRESSED, videodata->key_layout[keycode]);
+                    SDL_SendKeyboardKey(0, 0, SDL_PRESSED, videodata->key_layout[keycode]);
                 }
                 if (*text) {
                     SDL_SendKeyboardText(text);
@@ -1272,7 +1272,7 @@ static void X11_DispatchEvent(SDL_VideoDevice *_this, XEvent *xevent)
                     /* We're about to get a repeated key down, ignore the key up */
                     break;
                 }
-                SDL_SendKeyboardKey(0, SDL_RELEASED, videodata->key_layout[keycode]);
+                SDL_SendKeyboardKey(0, 0, SDL_RELEASED, videodata->key_layout[keycode]);
             }
         }
 

+ 22 - 2
test/testhotplug.c

@@ -20,6 +20,8 @@
 
 int main(int argc, char *argv[])
 {
+    int num_keyboards = 0;
+    int num_mice = 0;
     int num_joysticks = 0;
     SDL_Joystick *joystick = NULL;
     SDL_Haptic *haptic = NULL;
@@ -78,12 +80,18 @@ int main(int argc, char *argv[])
     //SDL_CreateWindow("Dummy", 128, 128, 0);
     */
 
+    SDL_free(SDL_GetKeyboards(&num_keyboards));
+    SDL_Log("There are %d keyboards at startup\n", num_keyboards);
+
+    SDL_free(SDL_GetMice(&num_mice));
+    SDL_Log("There are %d mice at startup\n", num_mice);
+
     SDL_free(SDL_GetJoysticks(&num_joysticks));
     SDL_Log("There are %d joysticks at startup\n", num_joysticks);
+
     if (enable_haptic) {
         int num_haptics;
-        SDL_HapticID *haptics = SDL_GetHaptics(&num_haptics);
-        SDL_free(haptics);
+        SDL_free(SDL_GetHaptics(&num_haptics));
         SDL_Log("There are %d haptic devices at startup\n", num_haptics);
     }
 
@@ -94,6 +102,18 @@ int main(int argc, char *argv[])
             case SDL_EVENT_QUIT:
                 keepGoing = SDL_FALSE;
                 break;
+            case SDL_EVENT_KEYBOARD_ADDED:
+                SDL_Log("Keyboard added  : %" SDL_PRIu32 "\n", event.kdevice.which);
+                break;
+            case SDL_EVENT_KEYBOARD_REMOVED:
+                SDL_Log("Keyboard removed: %" SDL_PRIu32 "\n", event.kdevice.which);
+                break;
+            case SDL_EVENT_MOUSE_ADDED:
+                SDL_Log("Mouse added  : %" SDL_PRIu32 "\n", event.mdevice.which);
+                break;
+            case SDL_EVENT_MOUSE_REMOVED:
+                SDL_Log("Mouse removed: %" SDL_PRIu32 "\n", event.mdevice.which);
+                break;
             case SDL_EVENT_JOYSTICK_ADDED:
                 if (joystick) {
                     SDL_Log("Only one joystick supported by this test\n");