| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
- 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"
- #if defined(SDL_VIDEO_DRIVER_WINDOWS)
- #include "SDL_windowsvideo.h"
- #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
- #include "SDL_windowsevents.h"
- #include "../../joystick/usb_ids.h"
- #include "../../events/SDL_events_c.h"
- #include "../../thread/SDL_systhread.h"
- #define ENABLE_RAW_MOUSE_INPUT 0x01
- #define ENABLE_RAW_KEYBOARD_INPUT 0x02
- #define RAW_KEYBOARD_FLAG_NOHOTKEYS 0x04
- #define RAW_KEYBOARD_FLAG_INPUTSINK 0x08
- typedef struct
- {
- bool done;
- SDL_AtomicU32 flags; // Thread sets this to the actually-set flags if updating state failed
- HANDLE ready_event;
- HANDLE signal_event;
- HANDLE thread;
- } RawInputThreadData;
- static RawInputThreadData thread_data = { 0 };
- typedef enum
- {
- RINP_QUIT,
- RINP_UPDATE,
- RINP_CONTINUE
- } RawInputIterateResult;
- static RawInputIterateResult IterateRawInputThread(void)
- {
- // The high-order word of GetQueueStatus() will let us know if there's immediate raw input to be processed.
- // A (necessary!) side effect is that it also marks the message queue bits as stale,
- // so MsgWaitForMultipleObjects will block.
- // Any pending flag update won't be processed until the queue drains, but this is
- // at most one poll cycle since GetQueueStatus clears the wake bits.
- if (HIWORD(GetQueueStatus(QS_RAWINPUT)) & QS_RAWINPUT) {
- return thread_data.done ? RINP_QUIT : RINP_CONTINUE;
- }
- static const DWORD WAIT_SIGNAL = WAIT_OBJECT_0;
- static const DWORD WAIT_INPUT = WAIT_OBJECT_0 + 1;
- // There wasn't anything, so we'll wait until new data (or signal_event) wakes us up.
- DWORD wait_status = MsgWaitForMultipleObjects(1, &thread_data.signal_event, FALSE, INFINITE, QS_RAWINPUT);
- if (wait_status == WAIT_SIGNAL) {
- // signal_event can only be set if we need to update or quit.
- return thread_data.done ? RINP_QUIT : RINP_UPDATE;
- } else if (wait_status == WAIT_INPUT) {
- return RINP_CONTINUE;
- } else {
- SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Raw input thread exiting, unexpected wait result: %lu", wait_status);
- return RINP_QUIT;
- }
- }
- static bool UpdateRawInputDeviceFlags(HWND window, Uint32 last_flags, Uint32 new_flags)
- {
- // We had nothing enabled, and we're trying to stop everything. Nothing to do.
- if (last_flags == new_flags) {
- return true;
- }
- RAWINPUTDEVICE devices[2] = { 0 };
- UINT count = 0;
- if ((new_flags & ENABLE_RAW_MOUSE_INPUT) != (last_flags & ENABLE_RAW_MOUSE_INPUT)) {
- devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
- devices[count].usUsage = USB_USAGE_GENERIC_MOUSE;
- if (new_flags & ENABLE_RAW_MOUSE_INPUT) {
- devices[count].dwFlags = 0;
- devices[count].hwndTarget = window;
- } else {
- devices[count].dwFlags = RIDEV_REMOVE;
- devices[count].hwndTarget = NULL;
- }
- ++count;
- }
- const Uint32 old_kb_flags = last_flags & (ENABLE_RAW_KEYBOARD_INPUT | RAW_KEYBOARD_FLAG_NOHOTKEYS | RAW_KEYBOARD_FLAG_INPUTSINK);
- const Uint32 new_kb_flags = new_flags & (ENABLE_RAW_KEYBOARD_INPUT | RAW_KEYBOARD_FLAG_NOHOTKEYS | RAW_KEYBOARD_FLAG_INPUTSINK);
- if (old_kb_flags != new_kb_flags) {
- devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
- devices[count].usUsage = USB_USAGE_GENERIC_KEYBOARD;
- if (new_kb_flags & ENABLE_RAW_KEYBOARD_INPUT) {
- devices[count].dwFlags = 0;
- devices[count].hwndTarget = window;
- if (new_kb_flags & RAW_KEYBOARD_FLAG_NOHOTKEYS) {
- devices[count].dwFlags |= RIDEV_NOHOTKEYS;
- }
- if (new_kb_flags & RAW_KEYBOARD_FLAG_INPUTSINK) {
- devices[count].dwFlags |= RIDEV_INPUTSINK;
- }
- } else {
- devices[count].dwFlags = RIDEV_REMOVE;
- devices[count].hwndTarget = NULL;
- }
- ++count;
- }
- if (RegisterRawInputDevices(devices, count, sizeof(devices[0]))) {
- return true;
- }
- return false;
- }
- static DWORD WINAPI WIN_RawInputThread(LPVOID param)
- {
- SDL_VideoDevice *_this = SDL_GetVideoDevice();
- Uint32 last_flags = 0;
- HWND window;
- SDL_SYS_SetupThread("SDLRawInput");
- window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
- if (!window) {
- return 0;
- }
- // This doesn't *really* need to be atomic, because the parent is waiting for us
- last_flags = SDL_GetAtomicU32(&thread_data.flags);
- if (!UpdateRawInputDeviceFlags(window, 0, last_flags)) {
- DestroyWindow(window);
- return 0;
- }
- // Make sure we get events as soon as possible
- SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
- // Tell the parent we're ready to go!
- SetEvent(thread_data.ready_event);
- RawInputIterateResult iter_result;
- Uint64 idle_begin = SDL_GetTicksNS();
- while ((iter_result = IterateRawInputThread()) != RINP_QUIT) {
- switch (iter_result) {
- case RINP_QUIT: // Unreachable
- break;
- case RINP_UPDATE:
- {
- const Uint32 new_flags = SDL_GetAtomicU32(&thread_data.flags);
- if (!UpdateRawInputDeviceFlags(window, last_flags, new_flags)) {
- // Revert the shared flags so the main thread can detect the failure
- SDL_SetAtomicU32(&thread_data.flags, last_flags);
- } else {
- last_flags = new_flags;
- }
- } break;
- case RINP_CONTINUE:
- {
- Uint64 idle_end = SDL_GetTicksNS();
- Uint64 idle_time = idle_end - idle_begin;
- Uint64 usb_8khz_interval = SDL_US_TO_NS(125);
- Uint64 poll_start = idle_time < usb_8khz_interval ? _this->internal->last_rawinput_poll : idle_end;
- WIN_PollRawInput(_this, poll_start);
- // Reset idle_begin for the next go-around
- idle_begin = SDL_GetTicksNS();
- } break;
- }
- }
- if (_this->internal->raw_input_fake_pen_id) {
- SDL_RemovePenDevice(0, SDL_GetKeyboardFocus(), _this->internal->raw_input_fake_pen_id);
- _this->internal->raw_input_fake_pen_id = 0;
- }
- // Reset this here, since if we're exiting due to failure, WIN_UpdateRawInputEnabled would see a stale value.
- SDL_SetAtomicU32(&thread_data.flags, 0);
- UpdateRawInputDeviceFlags(NULL, last_flags, 0);
- DestroyWindow(window);
- return 0;
- }
- static void CleanupRawInputThreadData(void)
- {
- if (thread_data.thread) {
- thread_data.done = true;
- SetEvent(thread_data.signal_event);
- WaitForSingleObject(thread_data.thread, 3000);
- CloseHandle(thread_data.thread);
- }
- if (thread_data.ready_event) {
- CloseHandle(thread_data.ready_event);
- }
- if (thread_data.signal_event) {
- CloseHandle(thread_data.signal_event);
- }
- thread_data = (RawInputThreadData){ 0 };
- }
- // Computes the desired raw input flags from SDL_VideoData and ensures the
- // raw input thread's device registrations match.
- // Creates the thread on first use, only WIN_QuitRawInput actually shuts it down.
- static bool WIN_UpdateRawInputEnabled(SDL_VideoDevice *_this)
- {
- bool result = false;
- SDL_VideoData *data = _this->internal;
- Uint32 desired_flags = 0;
- if (data->raw_mouse_enabled) {
- desired_flags |= ENABLE_RAW_MOUSE_INPUT;
- }
- if (data->raw_keyboard_enabled) {
- desired_flags |= ENABLE_RAW_KEYBOARD_INPUT;
- }
- if (data->raw_keyboard_flag_nohotkeys) {
- desired_flags |= RAW_KEYBOARD_FLAG_NOHOTKEYS;
- }
- if (data->raw_keyboard_flag_inputsink) {
- desired_flags |= RAW_KEYBOARD_FLAG_INPUTSINK;
- }
- if (desired_flags == SDL_GetAtomicU32(&thread_data.flags)) {
- result = true;
- goto done;
- }
- // If the thread exited unexpectedly (e.g. MsgWaitForMultipleObjects failed),
- // the handle is stale. Clean it up so the creation path below can recover.
- if (thread_data.thread && WaitForSingleObject(thread_data.thread, 0) != WAIT_TIMEOUT) {
- CleanupRawInputThreadData();
- }
- // The thread will read from this to update its flags
- SDL_SetAtomicU32(&thread_data.flags, desired_flags);
- if (thread_data.thread) {
- // Thread is already running. Fire (the event) and forget, it'll read the atomic flags on wakeup.
- // If RegisterRawInputDevices fails, the thread reverts the atomic and the next call
- // to this function will see the mismatch and retry.
- SDL_assert(thread_data.signal_event);
- SetEvent(thread_data.signal_event);
- result = true;
- } else if (desired_flags) {
- HANDLE wait_handles[2];
- // Thread isn't running, spin it up
- thread_data.ready_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!thread_data.ready_event) {
- WIN_SetError("CreateEvent");
- goto done;
- }
- thread_data.done = false;
- thread_data.signal_event = CreateEvent(NULL, FALSE, FALSE, NULL);
- if (!thread_data.signal_event) {
- WIN_SetError("CreateEvent");
- goto done;
- }
- thread_data.thread = CreateThread(NULL, 0, WIN_RawInputThread, NULL, 0, NULL);
- if (!thread_data.thread) {
- WIN_SetError("CreateThread");
- goto done;
- }
- // Wait for the thread to complete initial setup or exit
- wait_handles[0] = thread_data.ready_event;
- wait_handles[1] = thread_data.thread;
- if (WaitForMultipleObjects(2, wait_handles, FALSE, INFINITE) != WAIT_OBJECT_0) {
- SDL_SetError("Couldn't set up raw input handling");
- goto done;
- }
- result = true;
- } else {
- // Thread isn't running and we tried to disable raw input, nothing to do
- result = true;
- }
- done:
- if (!result) {
- CleanupRawInputThreadData();
- }
- return result;
- }
- bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled)
- {
- SDL_VideoData *data = _this->internal;
- data->raw_mouse_enabled = enabled;
- if (data->gameinput_context) {
- if (!WIN_UpdateGameInputEnabled(_this)) {
- data->raw_mouse_enabled = !enabled;
- return false;
- }
- } else {
- if (!WIN_UpdateRawInputEnabled(_this)) {
- data->raw_mouse_enabled = !enabled;
- return false;
- }
- }
- return true;
- }
- bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled)
- {
- SDL_VideoData *data = _this->internal;
- data->raw_keyboard_enabled = enabled;
- if (data->gameinput_context) {
- if (!WIN_UpdateGameInputEnabled(_this)) {
- data->raw_keyboard_enabled = !enabled;
- return false;
- }
- } else {
- if (!WIN_UpdateRawInputEnabled(_this)) {
- data->raw_keyboard_enabled = !enabled;
- return false;
- }
- }
- return true;
- }
- typedef enum WIN_RawKeyboardFlag {
- NOHOTKEYS,
- INPUTSINK,
- } WIN_RawKeyboardFlag;
- static bool WIN_SetRawKeyboardFlag(SDL_VideoDevice *_this, WIN_RawKeyboardFlag flag, bool enabled)
- {
- SDL_VideoData *data = _this->internal;
- switch(flag) {
- case NOHOTKEYS:
- data->raw_keyboard_flag_nohotkeys = enabled;
- break;
- case INPUTSINK:
- data->raw_keyboard_flag_inputsink = enabled;
- break;
- default:
- return false;
- }
- if (data->gameinput_context) {
- return true;
- }
- return WIN_UpdateRawInputEnabled(_this);
- }
- bool WIN_SetRawKeyboardFlag_NoHotkeys(SDL_VideoDevice *_this, bool enabled)
- {
- return WIN_SetRawKeyboardFlag(_this, NOHOTKEYS, enabled);
- }
- bool WIN_SetRawKeyboardFlag_Inputsink(SDL_VideoDevice *_this, bool enabled)
- {
- return WIN_SetRawKeyboardFlag(_this, INPUTSINK, enabled);
- }
- void WIN_QuitRawInput(SDL_VideoDevice *_this)
- {
- CleanupRawInputThreadData();
- }
- #else
- bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled)
- {
- return SDL_Unsupported();
- }
- bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled)
- {
- return SDL_Unsupported();
- }
- bool WIN_SetRawKeyboardFlag_NoHotkeys(SDL_VideoDevice *_this, bool enabled)
- {
- return SDL_Unsupported();
- }
- bool WIN_SetRawKeyboardFlag_Inputsink(SDL_VideoDevice *_this, bool enabled)
- {
- return SDL_Unsupported();
- }
- void WIN_QuitRawInput(SDL_VideoDevice *_this)
- {
- }
- #endif // !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES
- #endif // SDL_VIDEO_DRIVER_WINDOWS
|