Atlas - SDL_sysjoystick.c
Home / ext / SDL / src / joystick / emscripten Lines: 1 | Size: 20945 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 22#include "SDL_internal.h" 23 24#ifdef SDL_JOYSTICK_EMSCRIPTEN 25 26#include <stdio.h> // For the definition of NULL 27 28#include "SDL_sysjoystick_c.h" 29#include "../SDL_joystick_c.h" 30#include "../usb_ids.h" 31 32static SDL_joylist_item *JoystickByIndex(int index); 33 34static SDL_joylist_item *SDL_joylist = NULL; 35static SDL_joylist_item *SDL_joylist_tail = NULL; 36static int numjoysticks = 0; 37 38EM_JS(int, SDL_GetEmscriptenJoystickVendor, (int device_index), { 39 // Let's assume that if we're calling these function then the gamepad object definitely exists 40 let gamepad = navigator['getGamepads']()[device_index]; 41 42 // Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc) 43 let vendor_str = 'Vendor: '; 44 if (gamepad['id']['indexOf'](vendor_str) > 0) { 45 let vendor_str_index = gamepad['id']['indexOf'](vendor_str) + vendor_str['length']; 46 return parseInt(gamepad['id']['substr'](vendor_str_index, 4), 16); 47 } 48 49 // Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action) 50 let id_split = gamepad['id']['split']('-'); 51 if (id_split['length'] > 1 && !isNaN(parseInt(id_split[0], 16))) { 52 return parseInt(id_split[0], 16); 53 } 54 55 return 0; 56}); 57 58EM_JS(int, SDL_GetEmscriptenJoystickProduct, (int device_index), { 59 let gamepad = navigator['getGamepads']()[device_index]; 60 61 // Chrome, Edge, Opera: Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 09cc) 62 let product_str = 'Product: '; 63 if (gamepad['id']['indexOf'](product_str) > 0) { 64 let product_str_index = gamepad['id']['indexOf'](product_str) + product_str['length']; 65 return parseInt(gamepad['id']['substr'](product_str_index, 4), 16); 66 } 67 68 // Firefox, Safari: 046d-c216-Logitech Dual Action (or 46d-c216-Logicool Dual Action) 69 let id_split = gamepad['id']['split']('-'); 70 if (id_split['length'] > 1 && !isNaN(parseInt(id_split[1], 16))) { 71 return parseInt(id_split[1], 16); 72 } 73 74 return 0; 75}); 76 77EM_JS(int, SDL_IsEmscriptenJoystickXInput, (int device_index), { 78 let gamepad = navigator['getGamepads']()[device_index]; 79 80 // Chrome, Edge, Opera: Xbox 360 Controller (XInput STANDARD GAMEPAD) 81 // Firefox: xinput 82 // TODO: Safari 83 return gamepad['id']['toLowerCase']()['indexOf']('xinput') >= 0; 84}); 85 86static EM_BOOL Emscripten_JoyStickConnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) 87{ 88 SDL_joylist_item *item; 89 int i; 90 Uint16 vendor, product; 91 bool is_xinput; 92 93 SDL_LockJoysticks(); 94 95 if (JoystickByIndex(gamepadEvent->index) != NULL) { 96 goto done; 97 } 98 99 item = (SDL_joylist_item *)SDL_malloc(sizeof(SDL_joylist_item)); 100 if (!item) { 101 goto done; 102 } 103 104 SDL_zerop(item); 105 item->index = gamepadEvent->index; 106 107 vendor = SDL_GetEmscriptenJoystickVendor(gamepadEvent->index); 108 product = SDL_GetEmscriptenJoystickProduct(gamepadEvent->index); 109 is_xinput = SDL_IsEmscriptenJoystickXInput(gamepadEvent->index); 110 111 // Use a generic VID/PID representing an XInput controller 112 if (!vendor && !product && is_xinput) { 113 vendor = USB_VENDOR_MICROSOFT; 114 product = USB_PRODUCT_XBOX360_XUSB_CONTROLLER; 115 } 116 117 item->name = SDL_CreateJoystickName(vendor, product, NULL, gamepadEvent->id); 118 if (!item->name) { 119 SDL_free(item); 120 goto done; 121 } 122 123 if (vendor && product) { 124 item->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_UNKNOWN, vendor, product, 0, NULL, item->name, 0, 0); 125 } else { 126 item->guid = SDL_CreateJoystickGUIDForName(item->name); 127 } 128 129 if (is_xinput) { 130 item->guid.data[14] = 'x'; // See SDL_IsJoystickXInput 131 } 132 133 item->mapping = SDL_strdup(gamepadEvent->mapping); 134 if (!item->mapping) { 135 SDL_free(item->name); 136 SDL_free(item); 137 goto done; 138 } 139 140 const int real_button_count = gamepadEvent->numButtons; 141 const int real_axis_count = gamepadEvent->numAxes; 142 int first_trigger_button = -1; 143 int first_hat_button = -1; 144 int num_buttons = gamepadEvent->numButtons; 145 int num_axes = gamepadEvent->numAxes; 146 bool triggers_are_buttons = false; 147 if ((SDL_strcmp(gamepadEvent->mapping, "standard") == 0) && (num_buttons >= 16)) { // maps to a game console gamepad layout, turn the d-pad into a hat, treat triggers as analog. 148 num_buttons -= 4; // 4 dpad buttons become a hat. 149 first_hat_button = 12; 150 151 if (num_axes == 4) { // Chrome gives the triggers analog button values, Firefox exposes them as extra axes. Both have the digital buttons. 152 num_axes += 2; // the two trigger "buttons" 153 triggers_are_buttons = true; 154 } 155 156 // dump the digital trigger buttons in any case. 157 first_trigger_button = 6; 158 num_buttons -= 2; 159 } 160 161 item->first_hat_button = first_hat_button; 162 item->first_trigger_button = first_trigger_button; 163 item->triggers_are_buttons = triggers_are_buttons; 164 item->nhats = (first_hat_button >= 0) ? 1 : 0; 165 item->naxes = num_axes; 166 item->nbuttons = num_buttons; 167 item->device_instance = SDL_GetNextObjectID(); 168 169 item->timestamp = gamepadEvent->timestamp; 170 171 int buttonidx = 0; 172 for (i = 0; i < real_button_count; i++, buttonidx++) { 173 if (buttonidx == first_hat_button) { 174 buttonidx += 4; // skip these buttons, we're treating them as hat input. 175 } else if (buttonidx == first_trigger_button) { 176 buttonidx += 2; // skip these buttons, we're treating them as axes. 177 } 178 item->analogButton[i] = gamepadEvent->analogButton[buttonidx]; 179 item->digitalButton[i] = gamepadEvent->digitalButton[buttonidx]; 180 } 181 182 for (i = 0; i < real_axis_count; i++) { 183 item->axis[i] = gamepadEvent->axis[i]; 184 } 185 186 if (item->triggers_are_buttons) { 187 item->axis[real_axis_count] = (gamepadEvent->analogButton[first_trigger_button] * 2.0f) - 1.0f; 188 item->axis[real_axis_count+1] = (gamepadEvent->analogButton[first_trigger_button+1] * 2.0f) - 1.0f; 189 } 190 191 SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. 192 if (first_hat_button != -1) { 193 Uint8 value = SDL_HAT_CENTERED; 194 // this currently expects the first button to be up, then down, then left, then right. 195 if (gamepadEvent->digitalButton[first_hat_button + 0]) { 196 value |= SDL_HAT_UP; 197 } 198 if (gamepadEvent->digitalButton[first_hat_button + 1]) { 199 value |= SDL_HAT_DOWN; 200 } 201 if (gamepadEvent->digitalButton[first_hat_button + 2]) { 202 value |= SDL_HAT_LEFT; 203 } 204 if (gamepadEvent->digitalButton[first_hat_button + 3]) { 205 value |= SDL_HAT_RIGHT; 206 } 207 item->hat = value; 208 } 209 210 if (!SDL_joylist_tail) { 211 SDL_joylist = SDL_joylist_tail = item; 212 } else { 213 SDL_joylist_tail->next = item; 214 SDL_joylist_tail = item; 215 } 216 217 ++numjoysticks; 218 219 SDL_PrivateJoystickAdded(item->device_instance); 220 221#ifdef DEBUG_JOYSTICK 222 SDL_Log("Number of joysticks is %d", numjoysticks); 223#endif 224#ifdef DEBUG_JOYSTICK 225 SDL_Log("Added joystick with index %d", item->index); 226#endif 227 228done: 229 SDL_UnlockJoysticks(); 230 231 return 1; 232} 233 234static EM_BOOL Emscripten_JoyStickDisconnected(int eventType, const EmscriptenGamepadEvent *gamepadEvent, void *userData) 235{ 236 SDL_joylist_item *item = SDL_joylist; 237 SDL_joylist_item *prev = NULL; 238 239 SDL_LockJoysticks(); 240 241 while (item) { 242 if (item->index == gamepadEvent->index) { 243 break; 244 } 245 prev = item; 246 item = item->next; 247 } 248 249 if (!item) { 250 goto done; 251 } 252 253 if (item->joystick) { 254 item->joystick->hwdata = NULL; 255 } 256 257 if (prev) { 258 prev->next = item->next; 259 } else { 260 SDL_assert(SDL_joylist == item); 261 SDL_joylist = item->next; 262 } 263 if (item == SDL_joylist_tail) { 264 SDL_joylist_tail = prev; 265 } 266 267 // Need to decrement the joystick count before we post the event 268 --numjoysticks; 269 270 SDL_PrivateJoystickRemoved(item->device_instance); 271 272#ifdef DEBUG_JOYSTICK 273 SDL_Log("Removed joystick with id %d", item->device_instance); 274#endif 275 SDL_free(item->name); 276 SDL_free(item->mapping); 277 SDL_free(item); 278 279done: 280 SDL_UnlockJoysticks(); 281 282 return 1; 283} 284 285// Function to perform any system-specific joystick related cleanup 286static void EMSCRIPTEN_JoystickQuit(void) 287{ 288 SDL_joylist_item *item = NULL; 289 SDL_joylist_item *next = NULL; 290 291 for (item = SDL_joylist; item; item = next) { 292 next = item->next; 293 SDL_free(item->mapping); 294 SDL_free(item->name); 295 SDL_free(item); 296 } 297 298 SDL_joylist = SDL_joylist_tail = NULL; 299 300 numjoysticks = 0; 301 302 emscripten_set_gamepadconnected_callback(NULL, 0, NULL); 303 emscripten_set_gamepaddisconnected_callback(NULL, 0, NULL); 304} 305 306// Function to scan the system for joysticks. 307static bool EMSCRIPTEN_JoystickInit(void) 308{ 309 int rc, i, numjs; 310 EmscriptenGamepadEvent gamepadState; 311 312 numjoysticks = 0; 313 314 rc = emscripten_sample_gamepad_data(); 315 316 // Check if gamepad is supported by browser 317 if (rc == EMSCRIPTEN_RESULT_NOT_SUPPORTED) { 318 return SDL_SetError("Gamepads not supported"); 319 } 320 321 numjs = emscripten_get_num_gamepads(); 322 323 // handle already connected gamepads 324 if (numjs > 0) { 325 for (i = 0; i < numjs; i++) { 326 rc = emscripten_get_gamepad_status(i, &gamepadState); 327 if (rc == EMSCRIPTEN_RESULT_SUCCESS) { 328 Emscripten_JoyStickConnected(EMSCRIPTEN_EVENT_GAMEPADCONNECTED, 329 &gamepadState, 330 NULL); 331 } 332 } 333 } 334 335 rc = emscripten_set_gamepadconnected_callback(NULL, 336 0, 337 Emscripten_JoyStickConnected); 338 339 if (rc != EMSCRIPTEN_RESULT_SUCCESS) { 340 EMSCRIPTEN_JoystickQuit(); 341 return SDL_SetError("Could not set gamepad connect callback"); 342 } 343 344 rc = emscripten_set_gamepaddisconnected_callback(NULL, 345 0, 346 Emscripten_JoyStickDisconnected); 347 if (rc != EMSCRIPTEN_RESULT_SUCCESS) { 348 EMSCRIPTEN_JoystickQuit(); 349 return SDL_SetError("Could not set gamepad disconnect callback"); 350 } 351 352 return true; 353} 354 355// Returns item matching given SDL device index. 356static SDL_joylist_item *JoystickByDeviceIndex(int device_index) 357{ 358 SDL_joylist_item *item = SDL_joylist; 359 360 while (0 < device_index) { 361 --device_index; 362 item = item->next; 363 } 364 365 return item; 366} 367 368// Returns item matching given HTML gamepad index. 369static SDL_joylist_item *JoystickByIndex(int index) 370{ 371 SDL_joylist_item *item = SDL_joylist; 372 373 if (index < 0) { 374 return NULL; 375 } 376 377 while (item) { 378 if (item->index == index) { 379 break; 380 } 381 item = item->next; 382 } 383 384 return item; 385} 386 387static int EMSCRIPTEN_JoystickGetCount(void) 388{ 389 return numjoysticks; 390} 391 392static void EMSCRIPTEN_JoystickDetect(void) 393{ 394} 395 396static bool EMSCRIPTEN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name) 397{ 398 // We don't override any other drivers 399 return false; 400} 401 402static const char *EMSCRIPTEN_JoystickGetDeviceName(int device_index) 403{ 404 return JoystickByDeviceIndex(device_index)->name; 405} 406 407static const char *EMSCRIPTEN_JoystickGetDevicePath(int device_index) 408{ 409 return NULL; 410} 411 412static int EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index) 413{ 414 return -1; 415} 416 417static int EMSCRIPTEN_JoystickGetDevicePlayerIndex(int device_index) 418{ 419 return -1; 420} 421 422static void EMSCRIPTEN_JoystickSetDevicePlayerIndex(int device_index, int player_index) 423{ 424} 425 426static SDL_JoystickID EMSCRIPTEN_JoystickGetDeviceInstanceID(int device_index) 427{ 428 return JoystickByDeviceIndex(device_index)->device_instance; 429} 430 431static bool EMSCRIPTEN_JoystickOpen(SDL_Joystick *joystick, int device_index) 432{ 433 SDL_joylist_item *item = JoystickByDeviceIndex(device_index); 434 bool rumble_available = false; 435 436 if (!item) { 437 return SDL_SetError("No such device"); 438 } 439 440 if (item->joystick) { 441 return SDL_SetError("Joystick already opened"); 442 } 443 444 joystick->hwdata = (struct joystick_hwdata *)item; 445 item->joystick = joystick; 446 447 // HTML5 Gamepad API doesn't offer hats, but we can fake it from the d-pad buttons on the "standard" mapping. 448 joystick->nhats = item->nhats; 449 joystick->nbuttons = item->nbuttons; 450 joystick->naxes = item->naxes; 451 452 rumble_available = EM_ASM_INT({ 453 let gamepads = navigator['getGamepads'](); 454 if (!gamepads) { 455 return 0; 456 } 457 let gamepad = gamepads[$0]; 458 if (!gamepad || !gamepad['vibrationActuator']) { 459 return 0; 460 } 461 return 1; 462 }, item->index); 463 464 if (rumble_available) { 465 SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true); 466 } 467 468 return true; 469} 470 471/* Function to update the state of a joystick - called as a device poll. 472 * This function shouldn't update the joystick structure directly, 473 * but instead should call SDL_PrivateJoystick*() to deliver events 474 * and update joystick device state. 475 */ 476static void EMSCRIPTEN_JoystickUpdate(SDL_Joystick *joystick) 477{ 478 EmscriptenGamepadEvent gamepadState; 479 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; 480 int i, result; 481 Uint64 timestamp = SDL_GetTicksNS(); 482 483 emscripten_sample_gamepad_data(); 484 485 if (item) { 486 result = emscripten_get_gamepad_status(item->index, &gamepadState); 487 if (result == EMSCRIPTEN_RESULT_SUCCESS) { 488 if (gamepadState.timestamp == 0 || gamepadState.timestamp != item->timestamp) { 489 const int first_hat_button = item->first_hat_button; 490 const int first_trigger_button = item->first_trigger_button; 491 const int real_button_count = gamepadState.numButtons; 492 const int real_axis_count = gamepadState.numAxes; 493 494 int buttonidx = 0; 495 for (i = 0; i < real_button_count; i++, buttonidx++) { 496 if (buttonidx == first_hat_button) { 497 buttonidx += 4; // skip these buttons, we're treating them as hat input. 498 } else if (buttonidx == first_trigger_button) { 499 buttonidx += 2; // skip these buttons, we're treating them as axes. 500 } 501 if (item->digitalButton[i] != gamepadState.digitalButton[buttonidx]) { 502 const bool down = (gamepadState.digitalButton[buttonidx] != 0); 503 SDL_SendJoystickButton(timestamp, item->joystick, i, down); 504 } 505 506 // store values to compare them in the next update 507 item->analogButton[i] = gamepadState.analogButton[buttonidx]; 508 item->digitalButton[i] = gamepadState.digitalButton[buttonidx]; 509 } 510 511 for (i = 0; i < real_axis_count; i++) { 512 if (item->axis[i] != gamepadState.axis[i]) { 513 SDL_SendJoystickAxis(timestamp, item->joystick, i, (Sint16)(32767.0f * gamepadState.axis[i])); 514 item->axis[i] = gamepadState.axis[i]; 515 } 516 } 517 518 if (item->triggers_are_buttons) { 519 for (i = 0; i < 2; i++) { 520 if (item->axis[real_axis_count+i] != gamepadState.analogButton[first_trigger_button+i]) { 521 SDL_SendJoystickAxis(timestamp, item->joystick, real_axis_count+i, (Sint16)(32767.0f * ((gamepadState.analogButton[first_trigger_button+i] * 2.0f) - 1.0f))); 522 item->axis[real_axis_count+i] = gamepadState.analogButton[first_trigger_button+i]; 523 } 524 } 525 } 526 527 SDL_assert(item->nhats <= 1); // there is (currently) only ever one of these, faked from the d-pad buttons. 528 if (item->nhats) { 529 Uint8 value = SDL_HAT_CENTERED; 530 // this currently expects the first button to be up, then down, then left, then right. 531 if (gamepadState.digitalButton[first_hat_button + 0]) { 532 value |= SDL_HAT_UP; 533 } else if (gamepadState.digitalButton[first_hat_button + 1]) { 534 value |= SDL_HAT_DOWN; 535 } 536 if (gamepadState.digitalButton[first_hat_button + 2]) { 537 value |= SDL_HAT_LEFT; 538 } else if (gamepadState.digitalButton[first_hat_button + 3]) { 539 value |= SDL_HAT_RIGHT; 540 } 541 if (item->hat != value) { 542 item->hat = value; 543 SDL_SendJoystickHat(timestamp, item->joystick, 0, value); 544 } 545 } 546 547 548 item->timestamp = gamepadState.timestamp; 549 } 550 } 551 } 552} 553 554// Function to close a joystick after use 555static void EMSCRIPTEN_JoystickClose(SDL_Joystick *joystick) 556{ 557 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; 558 if (item) { 559 item->joystick = NULL; 560 } 561} 562 563static SDL_GUID EMSCRIPTEN_JoystickGetDeviceGUID(int device_index) 564{ 565 return JoystickByDeviceIndex(device_index)->guid; 566} 567 568static bool EMSCRIPTEN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 569{ 570 SDL_joylist_item *item = (SDL_joylist_item *)joystick->hwdata; 571 572 // clang-format off 573 bool result = EM_ASM_INT({ 574 let gamepads = navigator['getGamepads'](); 575 if (!gamepads) { 576 return 0; 577 } 578 let gamepad = gamepads[$0]; 579 if (!gamepad || !gamepad['vibrationActuator']) { 580 return 0; 581 } 582 583 gamepad['vibrationActuator']['playEffect']('dual-rumble', { 584 'startDelay': 0, 585 'duration': 3000, 586 'weakMagnitude': $2 / 0xFFFF, 587 'strongMagnitude': $1 / 0xFFFF, 588 }); 589 return 1; 590 }, item->index, low_frequency_rumble, high_frequency_rumble); 591 592 return result; 593} 594 595static bool EMSCRIPTEN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 596{ 597 return SDL_Unsupported(); 598} 599 600static bool EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) 601{ 602 return false; 603} 604 605static bool EMSCRIPTEN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 606{ 607 return SDL_Unsupported(); 608} 609 610static bool EMSCRIPTEN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) 611{ 612 return SDL_Unsupported(); 613} 614 615static bool EMSCRIPTEN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) 616{ 617 return SDL_Unsupported(); 618} 619 620SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver = { 621 EMSCRIPTEN_JoystickInit, 622 EMSCRIPTEN_JoystickGetCount, 623 EMSCRIPTEN_JoystickDetect, 624 EMSCRIPTEN_JoystickIsDevicePresent, 625 EMSCRIPTEN_JoystickGetDeviceName, 626 EMSCRIPTEN_JoystickGetDevicePath, 627 EMSCRIPTEN_JoystickGetDeviceSteamVirtualGamepadSlot, 628 EMSCRIPTEN_JoystickGetDevicePlayerIndex, 629 EMSCRIPTEN_JoystickSetDevicePlayerIndex, 630 EMSCRIPTEN_JoystickGetDeviceGUID, 631 EMSCRIPTEN_JoystickGetDeviceInstanceID, 632 EMSCRIPTEN_JoystickOpen, 633 EMSCRIPTEN_JoystickRumble, 634 EMSCRIPTEN_JoystickRumbleTriggers, 635 EMSCRIPTEN_JoystickSetLED, 636 EMSCRIPTEN_JoystickSendEffect, 637 EMSCRIPTEN_JoystickSetSensorsEnabled, 638 EMSCRIPTEN_JoystickUpdate, 639 EMSCRIPTEN_JoystickClose, 640 EMSCRIPTEN_JoystickQuit, 641 EMSCRIPTEN_JoystickGetGamepadMapping 642}; 643 644#endif // SDL_JOYSTICK_EMSCRIPTEN 645[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.