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