SDL_windowsrawinput.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "SDL_internal.h"
  19. #if defined(SDL_VIDEO_DRIVER_WINDOWS)
  20. #include "SDL_windowsvideo.h"
  21. #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
  22. #include "SDL_windowsevents.h"
  23. #include "../../joystick/usb_ids.h"
  24. #include "../../events/SDL_events_c.h"
  25. #include "../../thread/SDL_systhread.h"
  26. #define ENABLE_RAW_MOUSE_INPUT 0x01
  27. #define ENABLE_RAW_KEYBOARD_INPUT 0x02
  28. #define RAW_KEYBOARD_FLAG_NOHOTKEYS 0x04
  29. #define RAW_KEYBOARD_FLAG_INPUTSINK 0x08
  30. typedef struct
  31. {
  32. bool done;
  33. SDL_AtomicU32 flags; // Thread sets this to the actually-set flags if updating state failed
  34. HANDLE ready_event;
  35. HANDLE signal_event;
  36. HANDLE thread;
  37. } RawInputThreadData;
  38. static RawInputThreadData thread_data = { 0 };
  39. typedef enum
  40. {
  41. RINP_QUIT,
  42. RINP_UPDATE,
  43. RINP_CONTINUE
  44. } RawInputIterateResult;
  45. static RawInputIterateResult IterateRawInputThread(void)
  46. {
  47. // The high-order word of GetQueueStatus() will let us know if there's immediate raw input to be processed.
  48. // A (necessary!) side effect is that it also marks the message queue bits as stale,
  49. // so MsgWaitForMultipleObjects will block.
  50. // Any pending flag update won't be processed until the queue drains, but this is
  51. // at most one poll cycle since GetQueueStatus clears the wake bits.
  52. if (HIWORD(GetQueueStatus(QS_RAWINPUT)) & QS_RAWINPUT) {
  53. return thread_data.done ? RINP_QUIT : RINP_CONTINUE;
  54. }
  55. static const DWORD WAIT_SIGNAL = WAIT_OBJECT_0;
  56. static const DWORD WAIT_INPUT = WAIT_OBJECT_0 + 1;
  57. // There wasn't anything, so we'll wait until new data (or signal_event) wakes us up.
  58. DWORD wait_status = MsgWaitForMultipleObjects(1, &thread_data.signal_event, FALSE, INFINITE, QS_RAWINPUT);
  59. if (wait_status == WAIT_SIGNAL) {
  60. // signal_event can only be set if we need to update or quit.
  61. return thread_data.done ? RINP_QUIT : RINP_UPDATE;
  62. } else if (wait_status == WAIT_INPUT) {
  63. return RINP_CONTINUE;
  64. } else {
  65. SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Raw input thread exiting, unexpected wait result: %lu", wait_status);
  66. return RINP_QUIT;
  67. }
  68. }
  69. static bool UpdateRawInputDeviceFlags(HWND window, Uint32 last_flags, Uint32 new_flags)
  70. {
  71. // We had nothing enabled, and we're trying to stop everything. Nothing to do.
  72. if (last_flags == new_flags) {
  73. return true;
  74. }
  75. RAWINPUTDEVICE devices[2] = { 0 };
  76. UINT count = 0;
  77. if ((new_flags & ENABLE_RAW_MOUSE_INPUT) != (last_flags & ENABLE_RAW_MOUSE_INPUT)) {
  78. devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
  79. devices[count].usUsage = USB_USAGE_GENERIC_MOUSE;
  80. if (new_flags & ENABLE_RAW_MOUSE_INPUT) {
  81. devices[count].dwFlags = 0;
  82. devices[count].hwndTarget = window;
  83. } else {
  84. devices[count].dwFlags = RIDEV_REMOVE;
  85. devices[count].hwndTarget = NULL;
  86. }
  87. ++count;
  88. }
  89. const Uint32 old_kb_flags = last_flags & (ENABLE_RAW_KEYBOARD_INPUT | RAW_KEYBOARD_FLAG_NOHOTKEYS | RAW_KEYBOARD_FLAG_INPUTSINK);
  90. const Uint32 new_kb_flags = new_flags & (ENABLE_RAW_KEYBOARD_INPUT | RAW_KEYBOARD_FLAG_NOHOTKEYS | RAW_KEYBOARD_FLAG_INPUTSINK);
  91. if (old_kb_flags != new_kb_flags) {
  92. devices[count].usUsagePage = USB_USAGEPAGE_GENERIC_DESKTOP;
  93. devices[count].usUsage = USB_USAGE_GENERIC_KEYBOARD;
  94. if (new_kb_flags & ENABLE_RAW_KEYBOARD_INPUT) {
  95. devices[count].dwFlags = 0;
  96. devices[count].hwndTarget = window;
  97. if (new_kb_flags & RAW_KEYBOARD_FLAG_NOHOTKEYS) {
  98. devices[count].dwFlags |= RIDEV_NOHOTKEYS;
  99. }
  100. if (new_kb_flags & RAW_KEYBOARD_FLAG_INPUTSINK) {
  101. devices[count].dwFlags |= RIDEV_INPUTSINK;
  102. }
  103. } else {
  104. devices[count].dwFlags = RIDEV_REMOVE;
  105. devices[count].hwndTarget = NULL;
  106. }
  107. ++count;
  108. }
  109. if (RegisterRawInputDevices(devices, count, sizeof(devices[0]))) {
  110. return true;
  111. }
  112. return false;
  113. }
  114. static DWORD WINAPI WIN_RawInputThread(LPVOID param)
  115. {
  116. SDL_VideoDevice *_this = SDL_GetVideoDevice();
  117. Uint32 last_flags = 0;
  118. HWND window;
  119. SDL_SYS_SetupThread("SDLRawInput");
  120. window = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
  121. if (!window) {
  122. return 0;
  123. }
  124. // This doesn't *really* need to be atomic, because the parent is waiting for us
  125. last_flags = SDL_GetAtomicU32(&thread_data.flags);
  126. if (!UpdateRawInputDeviceFlags(window, 0, last_flags)) {
  127. DestroyWindow(window);
  128. return 0;
  129. }
  130. // Make sure we get events as soon as possible
  131. SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
  132. // Tell the parent we're ready to go!
  133. SetEvent(thread_data.ready_event);
  134. RawInputIterateResult iter_result;
  135. Uint64 idle_begin = SDL_GetTicksNS();
  136. while ((iter_result = IterateRawInputThread()) != RINP_QUIT) {
  137. switch (iter_result) {
  138. case RINP_QUIT: // Unreachable
  139. break;
  140. case RINP_UPDATE:
  141. {
  142. const Uint32 new_flags = SDL_GetAtomicU32(&thread_data.flags);
  143. if (!UpdateRawInputDeviceFlags(window, last_flags, new_flags)) {
  144. // Revert the shared flags so the main thread can detect the failure
  145. SDL_SetAtomicU32(&thread_data.flags, last_flags);
  146. } else {
  147. last_flags = new_flags;
  148. }
  149. } break;
  150. case RINP_CONTINUE:
  151. {
  152. Uint64 idle_end = SDL_GetTicksNS();
  153. Uint64 idle_time = idle_end - idle_begin;
  154. Uint64 usb_8khz_interval = SDL_US_TO_NS(125);
  155. Uint64 poll_start = idle_time < usb_8khz_interval ? _this->internal->last_rawinput_poll : idle_end;
  156. WIN_PollRawInput(_this, poll_start);
  157. // Reset idle_begin for the next go-around
  158. idle_begin = SDL_GetTicksNS();
  159. } break;
  160. }
  161. }
  162. if (_this->internal->raw_input_fake_pen_id) {
  163. SDL_RemovePenDevice(0, SDL_GetKeyboardFocus(), _this->internal->raw_input_fake_pen_id);
  164. _this->internal->raw_input_fake_pen_id = 0;
  165. }
  166. // Reset this here, since if we're exiting due to failure, WIN_UpdateRawInputEnabled would see a stale value.
  167. SDL_SetAtomicU32(&thread_data.flags, 0);
  168. UpdateRawInputDeviceFlags(NULL, last_flags, 0);
  169. DestroyWindow(window);
  170. return 0;
  171. }
  172. static void CleanupRawInputThreadData(void)
  173. {
  174. if (thread_data.thread) {
  175. thread_data.done = true;
  176. SetEvent(thread_data.signal_event);
  177. WaitForSingleObject(thread_data.thread, 3000);
  178. CloseHandle(thread_data.thread);
  179. }
  180. if (thread_data.ready_event) {
  181. CloseHandle(thread_data.ready_event);
  182. }
  183. if (thread_data.signal_event) {
  184. CloseHandle(thread_data.signal_event);
  185. }
  186. thread_data = (RawInputThreadData){ 0 };
  187. }
  188. // Computes the desired raw input flags from SDL_VideoData and ensures the
  189. // raw input thread's device registrations match.
  190. // Creates the thread on first use, only WIN_QuitRawInput actually shuts it down.
  191. static bool WIN_UpdateRawInputEnabled(SDL_VideoDevice *_this)
  192. {
  193. bool result = false;
  194. SDL_VideoData *data = _this->internal;
  195. Uint32 desired_flags = 0;
  196. if (data->raw_mouse_enabled) {
  197. desired_flags |= ENABLE_RAW_MOUSE_INPUT;
  198. }
  199. if (data->raw_keyboard_enabled) {
  200. desired_flags |= ENABLE_RAW_KEYBOARD_INPUT;
  201. }
  202. if (data->raw_keyboard_flag_nohotkeys) {
  203. desired_flags |= RAW_KEYBOARD_FLAG_NOHOTKEYS;
  204. }
  205. if (data->raw_keyboard_flag_inputsink) {
  206. desired_flags |= RAW_KEYBOARD_FLAG_INPUTSINK;
  207. }
  208. if (desired_flags == SDL_GetAtomicU32(&thread_data.flags)) {
  209. result = true;
  210. goto done;
  211. }
  212. // If the thread exited unexpectedly (e.g. MsgWaitForMultipleObjects failed),
  213. // the handle is stale. Clean it up so the creation path below can recover.
  214. if (thread_data.thread && WaitForSingleObject(thread_data.thread, 0) != WAIT_TIMEOUT) {
  215. CleanupRawInputThreadData();
  216. }
  217. // The thread will read from this to update its flags
  218. SDL_SetAtomicU32(&thread_data.flags, desired_flags);
  219. if (thread_data.thread) {
  220. // Thread is already running. Fire (the event) and forget, it'll read the atomic flags on wakeup.
  221. // If RegisterRawInputDevices fails, the thread reverts the atomic and the next call
  222. // to this function will see the mismatch and retry.
  223. SDL_assert(thread_data.signal_event);
  224. SetEvent(thread_data.signal_event);
  225. result = true;
  226. } else if (desired_flags) {
  227. HANDLE wait_handles[2];
  228. // Thread isn't running, spin it up
  229. thread_data.ready_event = CreateEvent(NULL, FALSE, FALSE, NULL);
  230. if (!thread_data.ready_event) {
  231. WIN_SetError("CreateEvent");
  232. goto done;
  233. }
  234. thread_data.done = false;
  235. thread_data.signal_event = CreateEvent(NULL, FALSE, FALSE, NULL);
  236. if (!thread_data.signal_event) {
  237. WIN_SetError("CreateEvent");
  238. goto done;
  239. }
  240. thread_data.thread = CreateThread(NULL, 0, WIN_RawInputThread, NULL, 0, NULL);
  241. if (!thread_data.thread) {
  242. WIN_SetError("CreateThread");
  243. goto done;
  244. }
  245. // Wait for the thread to complete initial setup or exit
  246. wait_handles[0] = thread_data.ready_event;
  247. wait_handles[1] = thread_data.thread;
  248. if (WaitForMultipleObjects(2, wait_handles, FALSE, INFINITE) != WAIT_OBJECT_0) {
  249. SDL_SetError("Couldn't set up raw input handling");
  250. goto done;
  251. }
  252. result = true;
  253. } else {
  254. // Thread isn't running and we tried to disable raw input, nothing to do
  255. result = true;
  256. }
  257. done:
  258. if (!result) {
  259. CleanupRawInputThreadData();
  260. }
  261. return result;
  262. }
  263. bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled)
  264. {
  265. SDL_VideoData *data = _this->internal;
  266. data->raw_mouse_enabled = enabled;
  267. if (data->gameinput_context) {
  268. if (!WIN_UpdateGameInputEnabled(_this)) {
  269. data->raw_mouse_enabled = !enabled;
  270. return false;
  271. }
  272. } else {
  273. if (!WIN_UpdateRawInputEnabled(_this)) {
  274. data->raw_mouse_enabled = !enabled;
  275. return false;
  276. }
  277. }
  278. return true;
  279. }
  280. bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled)
  281. {
  282. SDL_VideoData *data = _this->internal;
  283. data->raw_keyboard_enabled = enabled;
  284. if (data->gameinput_context) {
  285. if (!WIN_UpdateGameInputEnabled(_this)) {
  286. data->raw_keyboard_enabled = !enabled;
  287. return false;
  288. }
  289. } else {
  290. if (!WIN_UpdateRawInputEnabled(_this)) {
  291. data->raw_keyboard_enabled = !enabled;
  292. return false;
  293. }
  294. }
  295. return true;
  296. }
  297. typedef enum WIN_RawKeyboardFlag {
  298. NOHOTKEYS,
  299. INPUTSINK,
  300. } WIN_RawKeyboardFlag;
  301. static bool WIN_SetRawKeyboardFlag(SDL_VideoDevice *_this, WIN_RawKeyboardFlag flag, bool enabled)
  302. {
  303. SDL_VideoData *data = _this->internal;
  304. switch(flag) {
  305. case NOHOTKEYS:
  306. data->raw_keyboard_flag_nohotkeys = enabled;
  307. break;
  308. case INPUTSINK:
  309. data->raw_keyboard_flag_inputsink = enabled;
  310. break;
  311. default:
  312. return false;
  313. }
  314. if (data->gameinput_context) {
  315. return true;
  316. }
  317. return WIN_UpdateRawInputEnabled(_this);
  318. }
  319. bool WIN_SetRawKeyboardFlag_NoHotkeys(SDL_VideoDevice *_this, bool enabled)
  320. {
  321. return WIN_SetRawKeyboardFlag(_this, NOHOTKEYS, enabled);
  322. }
  323. bool WIN_SetRawKeyboardFlag_Inputsink(SDL_VideoDevice *_this, bool enabled)
  324. {
  325. return WIN_SetRawKeyboardFlag(_this, INPUTSINK, enabled);
  326. }
  327. void WIN_QuitRawInput(SDL_VideoDevice *_this)
  328. {
  329. CleanupRawInputThreadData();
  330. }
  331. #else
  332. bool WIN_SetRawMouseEnabled(SDL_VideoDevice *_this, bool enabled)
  333. {
  334. return SDL_Unsupported();
  335. }
  336. bool WIN_SetRawKeyboardEnabled(SDL_VideoDevice *_this, bool enabled)
  337. {
  338. return SDL_Unsupported();
  339. }
  340. bool WIN_SetRawKeyboardFlag_NoHotkeys(SDL_VideoDevice *_this, bool enabled)
  341. {
  342. return SDL_Unsupported();
  343. }
  344. bool WIN_SetRawKeyboardFlag_Inputsink(SDL_VideoDevice *_this, bool enabled)
  345. {
  346. return SDL_Unsupported();
  347. }
  348. void WIN_QuitRawInput(SDL_VideoDevice *_this)
  349. {
  350. }
  351. #endif // !SDL_PLATFORM_XBOXONE && !SDL_PLATFORM_XBOXSERIES
  352. #endif // SDL_VIDEO_DRIVER_WINDOWS