From 001dbc5da8a6cbce92b8722d91c2a9400ae7297f Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 7 Aug 2024 06:48:36 -0700 Subject: [PATCH] Added support for raw mouse and keyboard using GameInput on Windows Fixes https://github.com/libsdl-org/SDL/issues/10442 --- VisualC-GDK/SDL/SDL.vcxproj | 2 + VisualC-GDK/SDL/SDL.vcxproj.filters | 2 + VisualC/SDL/SDL.vcxproj | 2 + VisualC/SDL/SDL.vcxproj.filters | 6 + include/SDL3/SDL_hints.h | 14 + src/video/windows/SDL_windowsevents.c | 8 +- src/video/windows/SDL_windowsgameinput.c | 625 +++++++++++++++++++++++ src/video/windows/SDL_windowsgameinput.h | 29 ++ src/video/windows/SDL_windowsrawinput.c | 26 +- src/video/windows/SDL_windowsvideo.c | 24 +- src/video/windows/SDL_windowsvideo.h | 3 + 11 files changed, 729 insertions(+), 12 deletions(-) create mode 100644 src/video/windows/SDL_windowsgameinput.c create mode 100644 src/video/windows/SDL_windowsgameinput.h diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 77fc0785a8..8d038f6d31 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -573,6 +573,7 @@ + @@ -850,6 +851,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index f3cd8ca92b..fbd091f590 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -217,6 +217,7 @@ + @@ -447,6 +448,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 4cb8de774c..f8111cf344 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -479,6 +479,7 @@ + @@ -708,6 +709,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index d58f179c07..57b5a527a7 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -684,6 +684,9 @@ video\windows + + video\windows + video\windows @@ -1340,6 +1343,9 @@ video\windows + + video\windows + video\windows diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index 7f4391c4cc..d8bbfb4728 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -3823,6 +3823,20 @@ extern "C" { */ #define SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP "SDL_WINDOWS_ENABLE_MESSAGELOOP" +/** + * A variable controlling whether GameInput is used for raw keyboard and mouse on Windows. + * + * The variable can be set to the following values: + * + * - "0": GameInput is not used for raw keyboard and mouse events. + * - "1": GameInput is used for raw keyboard and mouse events, if available. (default) + * + * This hint should be set before SDL is initialized. + * + * \since This hint is available since SDL 3.0.0. + */ +#define SDL_HINT_WINDOWS_GAMEINPUT "SDL_WINDOWS_GAMEINPUT" + /** * A variable controlling whether raw keyboard events are used on Windows. * diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c index c5d3603972..367f8f0273 100644 --- a/src/video/windows/SDL_windowsevents.c +++ b/src/video/windows/SDL_windowsevents.c @@ -2233,6 +2233,10 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) SDL_Window *focusWindow; #endif + if (_this->internal->gameinput_context) { + WIN_UpdateGameInput(_this); + } + if (g_WindowsEnableMessageLoop) { SDL_processing_messages = SDL_TRUE; @@ -2310,7 +2314,9 @@ void WIN_PumpEvents(SDL_VideoDevice *_this) /* Update mouse capture */ WIN_UpdateMouseCapture(); - WIN_CheckKeyboardAndMouseHotplug(_this, SDL_FALSE); + if (!_this->internal->gameinput_context) { + WIN_CheckKeyboardAndMouseHotplug(_this, SDL_FALSE); + } WIN_UpdateIMECandidates(_this); diff --git a/src/video/windows/SDL_windowsgameinput.c b/src/video/windows/SDL_windowsgameinput.c new file mode 100644 index 0000000000..d92eb27cdd --- /dev/null +++ b/src/video/windows/SDL_windowsgameinput.c @@ -0,0 +1,625 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +#include "SDL_windowsvideo.h" + +#if defined(__has_include) && __has_include() +#define HAVE_GAMEINPUT_H +#endif + +#ifdef HAVE_GAMEINPUT_H + +#include +#define COBJMACROS +#include + +#include "../../events/SDL_mouse_c.h" +#include "../../events/SDL_keyboard_c.h" +#include "../../events/scancodes_windows.h" + + +#define MAX_GAMEINPUT_BUTTONS 7 // GameInputMouseWheelTiltRight is the highest button + +static const Uint8 GAMEINPUT_button_map[MAX_GAMEINPUT_BUTTONS] = { + SDL_BUTTON_LEFT, + SDL_BUTTON_RIGHT, + SDL_BUTTON_MIDDLE, + SDL_BUTTON_X1, + SDL_BUTTON_X2, + 6, + 7 +}; + +typedef struct GAMEINPUT_Device +{ + IGameInputDevice *pDevice; + const GameInputDeviceInfo *info; + char *name; + Uint32 instance_id; /* generated by SDL */ + SDL_bool registered; + SDL_bool delete_requested; + IGameInputReading *last_mouse_reading; + IGameInputReading *last_keyboard_reading; +} GAMEINPUT_Device; + +struct WIN_GameInputData +{ + void *hGameInputDLL; + IGameInput *pGameInput; + GameInputCallbackToken gameinput_callback_token; + int num_devices; + GAMEINPUT_Device **devices; + GameInputKind enabled_input; + SDL_Mutex *lock; + uint64_t timestamp_offset; +}; + +static int GAMEINPUT_InternalAddOrFind(WIN_GameInputData *data, IGameInputDevice *pDevice) +{ + GAMEINPUT_Device **devicelist = NULL; + GAMEINPUT_Device *device = NULL; + const GameInputDeviceInfo *info; + int retval = -1; + + info = IGameInputDevice_GetDeviceInfo(pDevice); + + SDL_LockMutex(data->lock); + { + for (int i = 0; i < data->num_devices; ++i) { + device = data->devices[i]; + if (device && device->pDevice == pDevice) { + /* we're already added */ + device->delete_requested = SDL_FALSE; + retval = 0; + goto done; + } + } + + device = (GAMEINPUT_Device *)SDL_calloc(1, sizeof(*device)); + if (!device) { + goto done; + } + + devicelist = (GAMEINPUT_Device **)SDL_realloc(data->devices, (data->num_devices + 1) * sizeof(*devicelist)); + if (!devicelist) { + SDL_free(device); + goto done; + } + + if (info->deviceStrings) { + /* In theory we could get the manufacturer and product strings here, but they're NULL for all the devices I've tested */ + } + + if (info->displayName) { + /* This could give us a product string, but it's NULL for all the devices I've tested */ + } + + IGameInputDevice_AddRef(pDevice); + device->pDevice = pDevice; + device->instance_id = SDL_GetNextObjectID(); + device->info = info; + + data->devices = devicelist; + data->devices[data->num_devices++] = device; + + retval = 0; + } +done: + SDL_UnlockMutex(data->lock); + + return retval; +} + +static int GAMEINPUT_InternalRemoveByIndex(WIN_GameInputData *data, int idx) +{ + GAMEINPUT_Device **devicelist = NULL; + GAMEINPUT_Device *device; + int retval = -1; + + SDL_LockMutex(data->lock); + { + if (idx < 0 || idx >= data->num_devices) { + retval = SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx); + goto done; + } + + device = data->devices[idx]; + if (device) { + if (device->registered) { + if (device->info->supportedInput & GameInputKindMouse) { + SDL_RemoveMouse(device->instance_id, SDL_TRUE); + } + if (device->info->supportedInput & GameInputKindKeyboard) { + SDL_RemoveKeyboard(device->instance_id, SDL_TRUE); + } + if (device->last_mouse_reading) { + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = NULL; + } + if (device->last_keyboard_reading) { + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } + IGameInputDevice_Release(device->pDevice); + SDL_free(device->name); + SDL_free(device); + } + data->devices[idx] = NULL; + + if (data->num_devices == 1) { + /* last element in the list, free the entire list then */ + SDL_free(data->devices); + data->devices = NULL; + } else { + if (idx != data->num_devices - 1) { + size_t bytes = sizeof(*devicelist) * (data->num_devices - idx); + SDL_memmove(&data->devices[idx], &data->devices[idx + 1], bytes); + } + } + + /* decrement the count and return */ + retval = data->num_devices--; + } +done: + SDL_UnlockMutex(data->lock); + + return retval; +} + +static void CALLBACK GAMEINPUT_InternalDeviceCallback( + _In_ GameInputCallbackToken callbackToken, + _In_ void* context, + _In_ IGameInputDevice *pDevice, + _In_ uint64_t timestamp, + _In_ GameInputDeviceStatus currentStatus, + _In_ GameInputDeviceStatus previousStatus) +{ + WIN_GameInputData *data = (WIN_GameInputData *)context; + int idx = 0; + GAMEINPUT_Device *device = NULL; + + if (!pDevice) { + /* This should never happen, but ignore it if it does */ + return; + } + + if (currentStatus & GameInputDeviceConnected) { + GAMEINPUT_InternalAddOrFind(data, pDevice); + } else { + for (idx = 0; idx < data->num_devices; ++idx) { + device = data->devices[idx]; + if (device && device->pDevice == pDevice) { + /* will be deleted on the next Detect call */ + device->delete_requested = SDL_TRUE; + break; + } + } + } +} + +int WIN_InitGameInput(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data; + HRESULT hr; + int retval = -1; + + if (_this->internal->gameinput_context) { + return 0; + } + + data = (WIN_GameInputData *)SDL_calloc(1, sizeof(*data)); + if (!data) { + goto done; + } + _this->internal->gameinput_context = data; + + data->lock = SDL_CreateMutex(); + if (!data->lock) { + goto done; + } + + data->hGameInputDLL = SDL_LoadObject("gameinput.dll"); + if (!data->hGameInputDLL) { + goto done; + } + + typedef HRESULT (WINAPI *GameInputCreate_t)(IGameInput * *gameInput); + GameInputCreate_t GameInputCreateFunc = (GameInputCreate_t)SDL_LoadFunction(data->hGameInputDLL, "GameInputCreate"); + if (!GameInputCreateFunc) { + goto done; + } + + hr = GameInputCreateFunc(&data->pGameInput); + if (FAILED(hr)) { + SDL_SetError("GameInputCreate failure with HRESULT of %08X", hr); + goto done; + } + + hr = IGameInput_RegisterDeviceCallback(data->pGameInput, + NULL, + (GameInputKindMouse | GameInputKindKeyboard), + GameInputDeviceConnected, + GameInputBlockingEnumeration, + data, + GAMEINPUT_InternalDeviceCallback, + &data->gameinput_callback_token); + if (FAILED(hr)) { + SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08X", hr); + goto done; + } + + // Calculate the relative offset between SDL timestamps and GameInput timestamps + Uint64 now = SDL_GetTicksNS(); + uint64_t timestampUS = IGameInput_GetCurrentTimestamp(data->pGameInput); + data->timestamp_offset = (SDL_NS_TO_US(now) - timestampUS); + + retval = 0; + +done: + if (retval < 0) { + WIN_QuitGameInput(_this); + } + return retval; +} + +static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading) +{ + GameInputMouseState state; + if (SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) { + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_MouseID mouseID = device->instance_id; + + for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) { + const GameInputMouseButtons mask = (1 << i); + SDL_SendMouseButton(timestamp, window, mouseID, (state.buttons & mask) ? SDL_PRESSED : SDL_RELEASED, GAMEINPUT_button_map[i]); + } + } +} + +static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading) +{ + GameInputMouseState last; + GameInputMouseState state; + if (SUCCEEDED(IGameInputReading_GetMouseState(last_reading, &last)) && + SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) { + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_MouseID mouseID = device->instance_id; + + GameInputMouseState delta; + delta.buttons = (state.buttons ^ last.buttons); + delta.positionX = (state.positionX - last.positionX); + delta.positionY = (state.positionY - last.positionY); + delta.wheelX = (state.wheelX - last.wheelX); + delta.wheelY = (state.wheelY - last.wheelY); + + if (delta.positionX || delta.positionY) { + SDL_SendMouseMotion(timestamp, window, mouseID, SDL_TRUE, (float)delta.positionX, (float)delta.positionY); + } + if (delta.buttons) { + for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) { + const GameInputMouseButtons mask = (1 << i); + if (delta.buttons & mask) { + SDL_SendMouseButton(timestamp, window, mouseID, (state.buttons & mask) ? SDL_PRESSED : SDL_RELEASED, GAMEINPUT_button_map[i]); + } + } + } + if (delta.wheelX || delta.wheelY) { + float fAmountX = (float)delta.wheelX / WHEEL_DELTA; + float fAmountY = (float)delta.wheelY / WHEEL_DELTA; + SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), device->instance_id, fAmountX, fAmountY, SDL_MOUSEWHEEL_NORMAL); + } + } +} + +static SDL_Scancode GetScancodeFromKeyState(const GameInputKeyState *state) +{ + Uint8 index = (Uint8)(state->scanCode & 0xFF); + if ((state->scanCode & 0xFF00) == 0xE000) { + index |= 0x80; + } + return windows_scancode_table[index]; +} + +static SDL_bool KeysHaveScancode(const GameInputKeyState *keys, uint32_t count, SDL_Scancode scancode) +{ + for (uint32_t i = 0; i < count; ++i) { + if (GetScancodeFromKeyState(&keys[i]) == scancode) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static void GAMEINPUT_InitialKeyboardReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading) +{ + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_KeyboardID keyboardID = device->instance_id; + + uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys; + GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys); + if (!keys) { + return; + } + + uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys); + if (!num_keys) { + // FIXME: We probably need to track key state by keyboardID + SDL_ResetKeyboard(); + return; + } + + // Go through and send key up events for any key that's not held down + int num_scancodes; + const Uint8 *keyboard_state = SDL_GetKeyboardState(&num_scancodes); + for (int i = 0; i < num_scancodes; ++i) { + if (keyboard_state[i] && !KeysHaveScancode(keys, num_keys, (SDL_Scancode)i)) { + SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, (SDL_Scancode)i, SDL_RELEASED); + } + } + + // Go through and send key down events for any key that's held down + for (uint32_t i = 0; i < num_keys; ++i) { + SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, GetScancodeFromKeyState(&keys[i]), SDL_PRESSED); + } +} + +#ifdef DEBUG_KEYS +static void DumpKeys(const char *prefix, GameInputKeyState *keys, uint32_t count) +{ + SDL_Log("%s", prefix); + for (uint32_t i = 0; i < count; ++i) { + char str[5]; + *SDL_UCS4ToUTF8(keys[i].codePoint, str) = '\0'; + SDL_Log(" Key 0x%.2x (%s)\n", keys[i].scanCode, str); + } +} +#endif // DEBUG_KEYS + +static void GAMEINPUT_HandleKeyboardDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading) +{ + Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset); + SDL_KeyboardID keyboardID = device->instance_id; + + uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys; + GameInputKeyState *last = SDL_stack_alloc(GameInputKeyState, max_keys); + GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys); + if (!last || !keys) { + return; + } + + uint32_t index_last = 0; + uint32_t index_keys = 0; + uint32_t num_last = IGameInputReading_GetKeyState(last_reading, max_keys, last); + uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys); +#ifdef DEBUG_KEYS + SDL_Log("Timestamp: %llu\n", timestamp); + DumpKeys("Last keys:", last, num_last); + DumpKeys("New keys:", keys, num_keys); +#endif + while (index_last < num_last || index_keys < num_keys) { + if (index_last < num_last && index_keys < num_keys) { + if (last[index_last].scanCode == keys[index_keys].scanCode) { + // No change + ++index_last; + ++index_keys; + } else { + // This key was released + SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), SDL_RELEASED); + ++index_last; + } + } else if (index_last < num_last) { + // This key was released + SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), SDL_RELEASED); + ++index_last; + } else { + // This key was pressed + SDL_SendKeyboardKey(timestamp, keyboardID, keys[index_keys].scanCode, GetScancodeFromKeyState(&keys[index_keys]), SDL_PRESSED); + ++index_keys; + } + } +} + +void WIN_UpdateGameInput(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data = _this->internal->gameinput_context; + + SDL_LockMutex(data->lock); + { + // Key events and relative mouse motion both go to the window with keyboard focus + SDL_Window *window = SDL_GetKeyboardFocus(); + + for (int i = 0; i < data->num_devices; ++i) { + GAMEINPUT_Device *device = data->devices[i]; + IGameInputReading *reading; + + if (!device->registered) { + if (device->info->supportedInput & GameInputKindMouse) { + SDL_AddMouse(device->instance_id, device->name, SDL_TRUE); + } + if (device->info->supportedInput & GameInputKindKeyboard) { + SDL_AddKeyboard(device->instance_id, device->name, SDL_TRUE); + } + device->registered = SDL_TRUE; + } + + if (device->delete_requested) { + GAMEINPUT_InternalRemoveByIndex(data, i--); + continue; + } + + if (!(device->info->supportedInput & data->enabled_input)) { + continue; + } + + if (!window) { + continue; + } + + if (data->enabled_input & GameInputKindMouse) { + if (device->last_mouse_reading) { + HRESULT hr; + while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_mouse_reading, GameInputKindMouse, device->pDevice, &reading))) { + GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading); + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = reading; + } + if (hr != GAMEINPUT_E_READING_NOT_FOUND) { + // The last reading is too old, resynchronize + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = NULL; + } + } + if (!device->last_mouse_reading) { + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) { + GAMEINPUT_InitialMouseReading(data, window, device, reading); + device->last_mouse_reading = reading; + } + } + } + + if (data->enabled_input & GameInputKindKeyboard) { + if (window->text_input_active) { + // Reset raw input while text input is active + if (device->last_keyboard_reading) { + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } else { + if (device->last_keyboard_reading) { + HRESULT hr; + while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_keyboard_reading, GameInputKindKeyboard, device->pDevice, &reading))) { + GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading); + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = reading; + } + if (hr != GAMEINPUT_E_READING_NOT_FOUND) { + // The last reading is too old, resynchronize + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } + if (!device->last_keyboard_reading) { + if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) { + GAMEINPUT_InitialKeyboardReading(data, window, device, reading); + device->last_keyboard_reading = reading; + } + } + } + } + } + } + SDL_UnlockMutex(data->lock); +} + +int WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data = _this->internal->gameinput_context; + SDL_bool raw_mouse_enabled = _this->internal->raw_mouse_enabled; + SDL_bool raw_keyboard_enabled = _this->internal->raw_keyboard_enabled; + + SDL_LockMutex(data->lock); + { + data->enabled_input = (raw_mouse_enabled ? GameInputKindMouse : 0) | + (raw_keyboard_enabled ? GameInputKindKeyboard : 0); + + // Reset input if not enabled + for (int i = 0; i < data->num_devices; ++i) { + GAMEINPUT_Device *device = data->devices[i]; + + if (device->last_mouse_reading && !raw_mouse_enabled) { + IGameInputReading_Release(device->last_mouse_reading); + device->last_mouse_reading = NULL; + } + + if (device->last_keyboard_reading && !raw_keyboard_enabled) { + IGameInputReading_Release(device->last_keyboard_reading); + device->last_keyboard_reading = NULL; + } + } + } + SDL_UnlockMutex(data->lock); + + return 0; +} + +void WIN_QuitGameInput(SDL_VideoDevice *_this) +{ + WIN_GameInputData *data = _this->internal->gameinput_context; + + if (!data) { + return; + } + + if (data->pGameInput) { + /* free the callback */ + if (data->gameinput_callback_token != GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE) { + IGameInput_UnregisterCallback(data->pGameInput, data->gameinput_callback_token, /*timeoutInUs:*/ 10000); + data->gameinput_callback_token = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE; + } + + /* free the list */ + while (data->num_devices > 0) { + GAMEINPUT_InternalRemoveByIndex(data, 0); + } + + IGameInput_Release(data->pGameInput); + data->pGameInput = NULL; + } + + if (data->hGameInputDLL) { + SDL_UnloadObject(data->hGameInputDLL); + data->hGameInputDLL = NULL; + } + + if (data->lock) { + SDL_DestroyMutex(data->lock); + data->lock = NULL; + } + + SDL_free(data); + _this->internal->gameinput_context = NULL; +} + +#else /* !HAVE_GAMEINPUT_H */ + +int WIN_InitGameInput(SDL_VideoDevice* _this) +{ + return SDL_Unsupported(); +} + +int WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this) +{ + return SDL_Unsupported(); +} + +void WIN_UpdateGameInput(SDL_VideoDevice* _this) +{ + return; +} + +void WIN_QuitGameInput(SDL_VideoDevice* _this) +{ + return; +} + +#endif /* HAVE_GAMEINPUT_H */ diff --git a/src/video/windows/SDL_windowsgameinput.h b/src/video/windows/SDL_windowsgameinput.h new file mode 100644 index 0000000000..58ef19a367 --- /dev/null +++ b/src/video/windows/SDL_windowsgameinput.h @@ -0,0 +1,29 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2024 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_internal.h" + +typedef struct WIN_GameInputData WIN_GameInputData; + +extern int WIN_InitGameInput(SDL_VideoDevice *_this); +extern int WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this); +extern void WIN_UpdateGameInput(SDL_VideoDevice *_this); +extern void WIN_QuitGameInput(SDL_VideoDevice *_this); + diff --git a/src/video/windows/SDL_windowsrawinput.c b/src/video/windows/SDL_windowsrawinput.c index b9ed5419c3..68fee93e32 100644 --- a/src/video/windows/SDL_windowsrawinput.c +++ b/src/video/windows/SDL_windowsrawinput.c @@ -203,14 +203,36 @@ int WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, SDL_bool enabled) { SDL_VideoData *data = _this->internal; data->raw_mouse_enabled = enabled; - return WIN_UpdateRawInputEnabled(_this); + if (data->gameinput_context) { + if (WIN_UpdateGameInputEnabled(_this) < 0) { + data->raw_mouse_enabled = !enabled; + return -1; + } + } else { + if (WIN_UpdateRawInputEnabled(_this) < 0) { + data->raw_mouse_enabled = !enabled; + return -1; + } + } + return 0; } int WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, SDL_bool enabled) { SDL_VideoData *data = _this->internal; data->raw_keyboard_enabled = enabled; - return WIN_UpdateRawInputEnabled(_this); + if (data->gameinput_context) { + if (WIN_UpdateGameInputEnabled(_this) < 0) { + data->raw_keyboard_enabled = !enabled; + return -1; + } + } else { + if (WIN_UpdateRawInputEnabled(_this) < 0) { + data->raw_keyboard_enabled = !enabled; + return -1; + } + } + return 0; } #else diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index ee768c1e16..440e21db2d 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -491,6 +491,10 @@ int WIN_VideoInit(SDL_VideoDevice *_this) SDL_Log("DPI awareness: %s", WIN_GetDPIAwareness(_this)); #endif + if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_GAMEINPUT, SDL_TRUE)) { + WIN_InitGameInput(_this); + } + #if defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES) /* For Xbox, we just need to create the single display */ { @@ -511,7 +515,9 @@ int WIN_VideoInit(SDL_VideoDevice *_this) WIN_InitKeyboard(_this); WIN_InitMouse(_this); WIN_InitDeviceNotification(); - WIN_CheckKeyboardAndMouseHotplug(_this, SDL_TRUE); + if (!_this->internal->gameinput_context) { + WIN_CheckKeyboardAndMouseHotplug(_this, SDL_TRUE); + } #endif SDL_AddHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this); @@ -530,13 +536,6 @@ void WIN_VideoQuit(SDL_VideoDevice *_this) { SDL_VideoData *data = _this->internal; -#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) - WIN_QuitModes(_this); - WIN_QuitDeviceNotification(); - WIN_QuitKeyboard(_this); - WIN_QuitMouse(_this); -#endif - SDL_DelHintCallback(SDL_HINT_WINDOWS_RAW_KEYBOARD, UpdateWindowsRawKeyboard, _this); SDL_DelHintCallback(SDL_HINT_WINDOWS_ENABLE_MESSAGELOOP, UpdateWindowsEnableMessageLoop, NULL); SDL_DelHintCallback(SDL_HINT_WINDOWS_ENABLE_MENU_MNEMONICS, UpdateWindowsEnableMenuMnemonics, NULL); @@ -544,13 +543,20 @@ void WIN_VideoQuit(SDL_VideoDevice *_this) WIN_SetRawMouseEnabled(_this, SDL_FALSE); WIN_SetRawKeyboardEnabled(_this, SDL_FALSE); + WIN_QuitGameInput(_this); + +#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) + WIN_QuitModes(_this); + WIN_QuitDeviceNotification(); + WIN_QuitKeyboard(_this); + WIN_QuitMouse(_this); -#if !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) if (data->oleinitialized) { OleUninitialize(); data->oleinitialized = SDL_FALSE; } #endif /* !(defined(SDL_PLATFORM_XBOXONE) || defined(SDL_PLATFORM_XBOXSERIES)) */ + if (data->coinitialized) { WIN_CoUninitialize(); data->coinitialized = SDL_FALSE; diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 2265ac0192..9ad6a65f58 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -47,6 +47,7 @@ #include "SDL_windowsclipboard.h" #include "SDL_windowsevents.h" +#include "SDL_windowsgameinput.h" #include "SDL_windowsopengl.h" #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) @@ -437,6 +438,8 @@ struct SDL_VideoData SDL_bool pending_E1_key_sequence; Uint32 raw_input_enabled; + WIN_GameInputData *gameinput_context; + #ifndef SDL_DISABLE_WINDOWS_IME SDL_bool ime_initialized; SDL_bool ime_enabled;