summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
committer3gg <3gg@shellblade.net>2025-12-27 12:03:39 -0800
commit5a079a2d114f96d4847d1ee305d5b7c16eeec50e (patch)
tree8926ab44f168acf787d8e19608857b3af0f82758 /contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c
Initial commit
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c')
-rw-r--r--contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c
new file mode 100644
index 0000000..183733a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowsgameinput.c
@@ -0,0 +1,612 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_windowsvideo.h"
24
25// GameInput currently has a bug with keys stuck on focus change, and crashes on initialization on some systems, so we'll disable it until these issues are fixed.
26#undef HAVE_GAMEINPUT_H
27
28#ifdef HAVE_GAMEINPUT_H
29
30#include "../../core/windows/SDL_gameinput.h"
31#include "../../events/SDL_mouse_c.h"
32#include "../../events/SDL_keyboard_c.h"
33#include "../../events/scancodes_windows.h"
34
35
36#define MAX_GAMEINPUT_BUTTONS 7 // GameInputMouseWheelTiltRight is the highest button
37
38static const Uint8 GAMEINPUT_button_map[MAX_GAMEINPUT_BUTTONS] = {
39 SDL_BUTTON_LEFT,
40 SDL_BUTTON_RIGHT,
41 SDL_BUTTON_MIDDLE,
42 SDL_BUTTON_X1,
43 SDL_BUTTON_X2,
44 6,
45 7
46};
47
48typedef struct GAMEINPUT_Device
49{
50 IGameInputDevice *pDevice;
51 const GameInputDeviceInfo *info;
52 char *name;
53 Uint32 instance_id; // generated by SDL
54 bool registered;
55 bool delete_requested;
56 IGameInputReading *last_mouse_reading;
57 IGameInputReading *last_keyboard_reading;
58} GAMEINPUT_Device;
59
60struct WIN_GameInputData
61{
62 IGameInput *pGameInput;
63 GameInputCallbackToken gameinput_callback_token;
64 int num_devices;
65 GAMEINPUT_Device **devices;
66 GameInputKind enabled_input;
67 SDL_Mutex *lock;
68 uint64_t timestamp_offset;
69};
70
71static bool GAMEINPUT_InternalAddOrFind(WIN_GameInputData *data, IGameInputDevice *pDevice)
72{
73 GAMEINPUT_Device **devicelist = NULL;
74 GAMEINPUT_Device *device = NULL;
75 const GameInputDeviceInfo *info;
76 bool result = false;
77
78 info = IGameInputDevice_GetDeviceInfo(pDevice);
79
80 SDL_LockMutex(data->lock);
81 {
82 for (int i = 0; i < data->num_devices; ++i) {
83 device = data->devices[i];
84 if (device && device->pDevice == pDevice) {
85 // we're already added
86 device->delete_requested = false;
87 result = true;
88 goto done;
89 }
90 }
91
92 device = (GAMEINPUT_Device *)SDL_calloc(1, sizeof(*device));
93 if (!device) {
94 goto done;
95 }
96
97 devicelist = (GAMEINPUT_Device **)SDL_realloc(data->devices, (data->num_devices + 1) * sizeof(*devicelist));
98 if (!devicelist) {
99 SDL_free(device);
100 goto done;
101 }
102
103 if (info->deviceStrings) {
104 // In theory we could get the manufacturer and product strings here, but they're NULL for all the devices I've tested
105 }
106
107 if (info->displayName) {
108 // This could give us a product string, but it's NULL for all the devices I've tested
109 }
110
111 IGameInputDevice_AddRef(pDevice);
112 device->pDevice = pDevice;
113 device->instance_id = SDL_GetNextObjectID();
114 device->info = info;
115
116 data->devices = devicelist;
117 data->devices[data->num_devices++] = device;
118
119 result = true;
120 }
121done:
122 SDL_UnlockMutex(data->lock);
123
124 return result;
125}
126
127static bool GAMEINPUT_InternalRemoveByIndex(WIN_GameInputData *data, int idx)
128{
129 GAMEINPUT_Device **devicelist = NULL;
130 GAMEINPUT_Device *device;
131 bool result = false;
132
133 SDL_LockMutex(data->lock);
134 {
135 if (idx < 0 || idx >= data->num_devices) {
136 result = SDL_SetError("GAMEINPUT_InternalRemoveByIndex argument idx %d is out of range", idx);
137 goto done;
138 }
139
140 device = data->devices[idx];
141 if (device) {
142 if (device->registered) {
143 if (device->info->supportedInput & GameInputKindMouse) {
144 SDL_RemoveMouse(device->instance_id, true);
145 }
146 if (device->info->supportedInput & GameInputKindKeyboard) {
147 SDL_RemoveKeyboard(device->instance_id, true);
148 }
149 if (device->last_mouse_reading) {
150 IGameInputReading_Release(device->last_mouse_reading);
151 device->last_mouse_reading = NULL;
152 }
153 if (device->last_keyboard_reading) {
154 IGameInputReading_Release(device->last_keyboard_reading);
155 device->last_keyboard_reading = NULL;
156 }
157 }
158 IGameInputDevice_Release(device->pDevice);
159 SDL_free(device->name);
160 SDL_free(device);
161 }
162 data->devices[idx] = NULL;
163
164 if (data->num_devices == 1) {
165 // last element in the list, free the entire list then
166 SDL_free(data->devices);
167 data->devices = NULL;
168 } else {
169 if (idx != data->num_devices - 1) {
170 size_t bytes = sizeof(*devicelist) * (data->num_devices - idx - 1);
171 SDL_memmove(&data->devices[idx], &data->devices[idx + 1], bytes);
172 }
173 }
174
175 // decrement the count and return
176 --data->num_devices;
177 result = true;
178 }
179done:
180 SDL_UnlockMutex(data->lock);
181
182 return result;
183}
184
185static void CALLBACK GAMEINPUT_InternalDeviceCallback(
186 _In_ GameInputCallbackToken callbackToken,
187 _In_ void* context,
188 _In_ IGameInputDevice *pDevice,
189 _In_ uint64_t timestamp,
190 _In_ GameInputDeviceStatus currentStatus,
191 _In_ GameInputDeviceStatus previousStatus)
192{
193 WIN_GameInputData *data = (WIN_GameInputData *)context;
194 int idx = 0;
195 GAMEINPUT_Device *device = NULL;
196
197 if (!pDevice) {
198 // This should never happen, but ignore it if it does
199 return;
200 }
201
202 if (currentStatus & GameInputDeviceConnected) {
203 GAMEINPUT_InternalAddOrFind(data, pDevice);
204 } else {
205 for (idx = 0; idx < data->num_devices; ++idx) {
206 device = data->devices[idx];
207 if (device && device->pDevice == pDevice) {
208 // will be deleted on the next Detect call
209 device->delete_requested = true;
210 break;
211 }
212 }
213 }
214}
215
216bool WIN_InitGameInput(SDL_VideoDevice *_this)
217{
218 WIN_GameInputData *data;
219 HRESULT hr;
220 bool result = false;
221
222 if (_this->internal->gameinput_context) {
223 return true;
224 }
225
226 data = (WIN_GameInputData *)SDL_calloc(1, sizeof(*data));
227 if (!data) {
228 goto done;
229 }
230 _this->internal->gameinput_context = data;
231
232 data->lock = SDL_CreateMutex();
233 if (!data->lock) {
234 goto done;
235 }
236
237 if (!SDL_InitGameInput(&data->pGameInput)) {
238 goto done;
239 }
240
241 hr = IGameInput_RegisterDeviceCallback(data->pGameInput,
242 NULL,
243 (GameInputKindMouse | GameInputKindKeyboard),
244 GameInputDeviceConnected,
245 GameInputBlockingEnumeration,
246 data,
247 GAMEINPUT_InternalDeviceCallback,
248 &data->gameinput_callback_token);
249 if (FAILED(hr)) {
250 SDL_SetError("IGameInput::RegisterDeviceCallback failure with HRESULT of %08X", hr);
251 goto done;
252 }
253
254 // Calculate the relative offset between SDL timestamps and GameInput timestamps
255 Uint64 now = SDL_GetTicksNS();
256 uint64_t timestampUS = IGameInput_GetCurrentTimestamp(data->pGameInput);
257 data->timestamp_offset = (SDL_NS_TO_US(now) - timestampUS);
258
259 result = true;
260
261done:
262 if (!result) {
263 WIN_QuitGameInput(_this);
264 }
265 return result;
266}
267
268static void GAMEINPUT_InitialMouseReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
269{
270 GameInputMouseState state;
271 if (SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) {
272 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
273 SDL_MouseID mouseID = device->instance_id;
274
275 for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
276 const GameInputMouseButtons mask = (1 << i);
277 bool down = ((state.buttons & mask) != 0);
278 SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down);
279 }
280 }
281}
282
283static void GAMEINPUT_HandleMouseDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
284{
285 GameInputMouseState last;
286 GameInputMouseState state;
287 if (SUCCEEDED(IGameInputReading_GetMouseState(last_reading, &last)) &&
288 SUCCEEDED(IGameInputReading_GetMouseState(reading, &state))) {
289 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
290 SDL_MouseID mouseID = device->instance_id;
291
292 GameInputMouseState delta;
293 delta.buttons = (state.buttons ^ last.buttons);
294 delta.positionX = (state.positionX - last.positionX);
295 delta.positionY = (state.positionY - last.positionY);
296 delta.wheelX = (state.wheelX - last.wheelX);
297 delta.wheelY = (state.wheelY - last.wheelY);
298
299 if (delta.positionX || delta.positionY) {
300 SDL_SendMouseMotion(timestamp, window, mouseID, true, (float)delta.positionX, (float)delta.positionY);
301 }
302 if (delta.buttons) {
303 for (int i = 0; i < MAX_GAMEINPUT_BUTTONS; ++i) {
304 const GameInputMouseButtons mask = (1 << i);
305 if (delta.buttons & mask) {
306 bool down = ((state.buttons & mask) != 0);
307 SDL_SendMouseButton(timestamp, window, mouseID, GAMEINPUT_button_map[i], down);
308 }
309 }
310 }
311 if (delta.wheelX || delta.wheelY) {
312 float fAmountX = (float)delta.wheelX / WHEEL_DELTA;
313 float fAmountY = (float)delta.wheelY / WHEEL_DELTA;
314 SDL_SendMouseWheel(timestamp, SDL_GetMouseFocus(), device->instance_id, fAmountX, fAmountY, SDL_MOUSEWHEEL_NORMAL);
315 }
316 }
317}
318
319static SDL_Scancode GetScancodeFromKeyState(const GameInputKeyState *state)
320{
321 Uint8 index = (Uint8)(state->scanCode & 0xFF);
322 if ((state->scanCode & 0xFF00) == 0xE000) {
323 index |= 0x80;
324 }
325 return windows_scancode_table[index];
326}
327
328static bool KeysHaveScancode(const GameInputKeyState *keys, uint32_t count, SDL_Scancode scancode)
329{
330 for (uint32_t i = 0; i < count; ++i) {
331 if (GetScancodeFromKeyState(&keys[i]) == scancode) {
332 return true;
333 }
334 }
335 return false;
336}
337
338static void GAMEINPUT_InitialKeyboardReading(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *reading)
339{
340 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
341 SDL_KeyboardID keyboardID = device->instance_id;
342
343 uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys;
344 GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys);
345 if (!keys) {
346 return;
347 }
348
349 uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys);
350 if (!num_keys) {
351 // FIXME: We probably need to track key state by keyboardID
352 SDL_ResetKeyboard();
353 return;
354 }
355
356 // Go through and send key up events for any key that's not held down
357 int num_scancodes;
358 const bool *keyboard_state = SDL_GetKeyboardState(&num_scancodes);
359 for (int i = 0; i < num_scancodes; ++i) {
360 if (keyboard_state[i] && !KeysHaveScancode(keys, num_keys, (SDL_Scancode)i)) {
361 SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, (SDL_Scancode)i, false);
362 }
363 }
364
365 // Go through and send key down events for any key that's held down
366 for (uint32_t i = 0; i < num_keys; ++i) {
367 SDL_SendKeyboardKey(timestamp, keyboardID, keys[i].scanCode, GetScancodeFromKeyState(&keys[i]), true);
368 }
369}
370
371#ifdef DEBUG_KEYS
372static void DumpKeys(const char *prefix, GameInputKeyState *keys, uint32_t count)
373{
374 SDL_Log("%s", prefix);
375 for (uint32_t i = 0; i < count; ++i) {
376 char str[5];
377 *SDL_UCS4ToUTF8(keys[i].codePoint, str) = '\0';
378 SDL_Log(" Key 0x%.2x (%s)", keys[i].scanCode, str);
379 }
380}
381#endif // DEBUG_KEYS
382
383static void GAMEINPUT_HandleKeyboardDelta(WIN_GameInputData *data, SDL_Window *window, GAMEINPUT_Device *device, IGameInputReading *last_reading, IGameInputReading *reading)
384{
385 Uint64 timestamp = SDL_US_TO_NS(IGameInputReading_GetTimestamp(reading) + data->timestamp_offset);
386 SDL_KeyboardID keyboardID = device->instance_id;
387
388 uint32_t max_keys = device->info->keyboardInfo->maxSimultaneousKeys;
389 GameInputKeyState *last = SDL_stack_alloc(GameInputKeyState, max_keys);
390 GameInputKeyState *keys = SDL_stack_alloc(GameInputKeyState, max_keys);
391 if (!last || !keys) {
392 return;
393 }
394
395 uint32_t index_last = 0;
396 uint32_t index_keys = 0;
397 uint32_t num_last = IGameInputReading_GetKeyState(last_reading, max_keys, last);
398 uint32_t num_keys = IGameInputReading_GetKeyState(reading, max_keys, keys);
399#ifdef DEBUG_KEYS
400 SDL_Log("Timestamp: %llu", timestamp);
401 DumpKeys("Last keys:", last, num_last);
402 DumpKeys("New keys:", keys, num_keys);
403#endif
404 while (index_last < num_last || index_keys < num_keys) {
405 if (index_last < num_last && index_keys < num_keys) {
406 if (last[index_last].scanCode == keys[index_keys].scanCode) {
407 // No change
408 ++index_last;
409 ++index_keys;
410 } else {
411 // This key was released
412 SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false);
413 ++index_last;
414 }
415 } else if (index_last < num_last) {
416 // This key was released
417 SDL_SendKeyboardKey(timestamp, keyboardID, last[index_last].scanCode, GetScancodeFromKeyState(&last[index_last]), false);
418 ++index_last;
419 } else {
420 // This key was pressed
421 SDL_SendKeyboardKey(timestamp, keyboardID, keys[index_keys].scanCode, GetScancodeFromKeyState(&keys[index_keys]), true);
422 ++index_keys;
423 }
424 }
425}
426
427void WIN_UpdateGameInput(SDL_VideoDevice *_this)
428{
429 WIN_GameInputData *data = _this->internal->gameinput_context;
430
431 SDL_LockMutex(data->lock);
432 {
433 // Key events and relative mouse motion both go to the window with keyboard focus
434 SDL_Window *window = SDL_GetKeyboardFocus();
435
436 for (int i = 0; i < data->num_devices; ++i) {
437 GAMEINPUT_Device *device = data->devices[i];
438 IGameInputReading *reading;
439
440 if (!device->registered) {
441 if (device->info->supportedInput & GameInputKindMouse) {
442 SDL_AddMouse(device->instance_id, device->name, true);
443 }
444 if (device->info->supportedInput & GameInputKindKeyboard) {
445 SDL_AddKeyboard(device->instance_id, device->name, true);
446 }
447 device->registered = true;
448 }
449
450 if (device->delete_requested) {
451 GAMEINPUT_InternalRemoveByIndex(data, i--);
452 continue;
453 }
454
455 if (!(device->info->supportedInput & data->enabled_input)) {
456 continue;
457 }
458
459 if (!window) {
460 continue;
461 }
462
463 if (data->enabled_input & GameInputKindMouse) {
464 if (device->last_mouse_reading) {
465 HRESULT hr;
466 while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_mouse_reading, GameInputKindMouse, device->pDevice, &reading))) {
467 GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
468 IGameInputReading_Release(device->last_mouse_reading);
469 device->last_mouse_reading = reading;
470 }
471 if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
472 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) {
473 GAMEINPUT_HandleMouseDelta(data, window, device, device->last_mouse_reading, reading);
474 IGameInputReading_Release(device->last_mouse_reading);
475 device->last_mouse_reading = reading;
476 }
477 }
478 } else {
479 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindMouse, device->pDevice, &reading))) {
480 GAMEINPUT_InitialMouseReading(data, window, device, reading);
481 device->last_mouse_reading = reading;
482 }
483 }
484 }
485
486 if (data->enabled_input & GameInputKindKeyboard) {
487 if (window->text_input_active) {
488 // Reset raw input while text input is active
489 if (device->last_keyboard_reading) {
490 IGameInputReading_Release(device->last_keyboard_reading);
491 device->last_keyboard_reading = NULL;
492 }
493 } else {
494 if (device->last_keyboard_reading) {
495 HRESULT hr;
496 while (SUCCEEDED(hr = IGameInput_GetNextReading(data->pGameInput, device->last_keyboard_reading, GameInputKindKeyboard, device->pDevice, &reading))) {
497 GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
498 IGameInputReading_Release(device->last_keyboard_reading);
499 device->last_keyboard_reading = reading;
500 }
501 if (hr != GAMEINPUT_E_READING_NOT_FOUND) {
502 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) {
503 GAMEINPUT_HandleKeyboardDelta(data, window, device, device->last_keyboard_reading, reading);
504 IGameInputReading_Release(device->last_keyboard_reading);
505 device->last_keyboard_reading = reading;
506 }
507 }
508 } else {
509 if (SUCCEEDED(IGameInput_GetCurrentReading(data->pGameInput, GameInputKindKeyboard, device->pDevice, &reading))) {
510 GAMEINPUT_InitialKeyboardReading(data, window, device, reading);
511 device->last_keyboard_reading = reading;
512 }
513 }
514 }
515 }
516 }
517 }
518 SDL_UnlockMutex(data->lock);
519}
520
521bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
522{
523 WIN_GameInputData *data = _this->internal->gameinput_context;
524 bool raw_mouse_enabled = _this->internal->raw_mouse_enabled;
525 bool raw_keyboard_enabled = _this->internal->raw_keyboard_enabled;
526
527 SDL_LockMutex(data->lock);
528 {
529 data->enabled_input = (raw_mouse_enabled ? GameInputKindMouse : GameInputKindUnknown) |
530 (raw_keyboard_enabled ? GameInputKindKeyboard : GameInputKindUnknown);
531
532 // Reset input if not enabled
533 for (int i = 0; i < data->num_devices; ++i) {
534 GAMEINPUT_Device *device = data->devices[i];
535
536 if (device->last_mouse_reading && !raw_mouse_enabled) {
537 IGameInputReading_Release(device->last_mouse_reading);
538 device->last_mouse_reading = NULL;
539 }
540
541 if (device->last_keyboard_reading && !raw_keyboard_enabled) {
542 IGameInputReading_Release(device->last_keyboard_reading);
543 device->last_keyboard_reading = NULL;
544 }
545 }
546 }
547 SDL_UnlockMutex(data->lock);
548
549 return true;
550}
551
552void WIN_QuitGameInput(SDL_VideoDevice *_this)
553{
554 WIN_GameInputData *data = _this->internal->gameinput_context;
555
556 if (!data) {
557 return;
558 }
559
560 if (data->pGameInput) {
561 // free the callback
562 if (data->gameinput_callback_token != GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE) {
563 IGameInput_UnregisterCallback(data->pGameInput, data->gameinput_callback_token, /*timeoutInUs:*/ 10000);
564 data->gameinput_callback_token = GAMEINPUT_INVALID_CALLBACK_TOKEN_VALUE;
565 }
566
567 // free the list
568 while (data->num_devices > 0) {
569 GAMEINPUT_InternalRemoveByIndex(data, 0);
570 }
571
572 IGameInput_Release(data->pGameInput);
573 data->pGameInput = NULL;
574 }
575
576 if (data->pGameInput) {
577 SDL_QuitGameInput();
578 data->pGameInput = NULL;
579 }
580
581 if (data->lock) {
582 SDL_DestroyMutex(data->lock);
583 data->lock = NULL;
584 }
585
586 SDL_free(data);
587 _this->internal->gameinput_context = NULL;
588}
589
590#else // !HAVE_GAMEINPUT_H
591
592bool WIN_InitGameInput(SDL_VideoDevice* _this)
593{
594 return SDL_Unsupported();
595}
596
597bool WIN_UpdateGameInputEnabled(SDL_VideoDevice *_this)
598{
599 return SDL_Unsupported();
600}
601
602void WIN_UpdateGameInput(SDL_VideoDevice* _this)
603{
604 return;
605}
606
607void WIN_QuitGameInput(SDL_VideoDevice* _this)
608{
609 return;
610}
611
612#endif // HAVE_GAMEINPUT_H