1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2016 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/* This is the iOS implementation of the SDL joystick API */ 24#include "SDL_sysjoystick_c.h" 25 26/* needed for SDL_IPHONE_MAX_GFORCE macro */ 27#include "SDL_config_iphoneos.h" 28 29#include "SDL_events.h" 30#include "SDL_joystick.h" 31#include "SDL_hints.h" 32#include "SDL_stdinc.h" 33#include "../SDL_sysjoystick.h" 34#include "../SDL_joystick_c.h" 35 36#if !SDL_EVENTS_DISABLED 37#include "../../events/SDL_events_c.h" 38#endif 39 40#if !TARGET_OS_TV 41#import <CoreMotion/CoreMotion.h> 42#endif 43 44#ifdef SDL_JOYSTICK_MFI 45#import <GameController/GameController.h> 46 47static id connectObserver = nil; 48static id disconnectObserver = nil; 49#endif /* SDL_JOYSTICK_MFI */ 50 51#if !TARGET_OS_TV 52static const char *accelerometerName = "iOS Accelerometer"; 53static CMMotionManager *motionManager = nil; 54#endif /* !TARGET_OS_TV */ 55 56static SDL_JoystickDeviceItem *deviceList = NULL; 57 58static int numjoysticks = 0; 59static SDL_JoystickID instancecounter = 0; 60 61static SDL_JoystickDeviceItem * 62GetDeviceForIndex(int device_index) 63{ 64 SDL_JoystickDeviceItem *device = deviceList; 65 int i = 0; 66 67 while (i < device_index) { 68 if (device == NULL) { 69 return NULL; 70 } 71 device = device->next; 72 i++; 73 } 74 75 return device; 76} 77 78static void 79SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller) 80{ 81#ifdef SDL_JOYSTICK_MFI 82 const char *name = NULL; 83 /* Explicitly retain the controller because SDL_JoystickDeviceItem is a 84 * struct, and ARC doesn't work with structs. */ 85 device->controller = (__bridge GCController *) CFBridgingRetain(controller); 86 87 if (controller.vendorName) { 88 name = controller.vendorName.UTF8String; 89 } 90 91 if (!name) { 92 name = "MFi Gamepad"; 93 } 94 95 device->name = SDL_strdup(name); 96 97 device->guid.data[0] = 'M'; 98 device->guid.data[1] = 'F'; 99 device->guid.data[2] = 'i'; 100 device->guid.data[3] = 'G'; 101 device->guid.data[4] = 'a'; 102 device->guid.data[5] = 'm'; 103 device->guid.data[6] = 'e'; 104 device->guid.data[7] = 'p'; 105 device->guid.data[8] = 'a'; 106 device->guid.data[9] = 'd'; 107 108 if (controller.extendedGamepad) { 109 device->guid.data[10] = 1; 110 } else if (controller.gamepad) { 111 device->guid.data[10] = 2; 112 } 113#if TARGET_OS_TV 114 else if (controller.microGamepad) { 115 device->guid.data[10] = 3; 116 } 117#endif /* TARGET_OS_TV */ 118 119 if (controller.extendedGamepad) { 120 device->naxes = 6; /* 2 thumbsticks and 2 triggers */ 121 device->nhats = 1; /* d-pad */ 122 device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */ 123 } else if (controller.gamepad) { 124 device->naxes = 0; /* no traditional analog inputs */ 125 device->nhats = 1; /* d-pad */ 126 device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */ 127 } 128#if TARGET_OS_TV 129 else if (controller.microGamepad) { 130 device->naxes = 2; /* treat the touch surface as two axes */ 131 device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */ 132 device->nbuttons = 3; /* AX, pause button */ 133 134 controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE); 135 } 136#endif /* TARGET_OS_TV */ 137 138 /* This will be set when the first button press of the controller is 139 * detected. */ 140 controller.playerIndex = -1; 141 142#endif /* SDL_JOYSTICK_MFI */ 143} 144 145static void 146SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer) 147{ 148 SDL_JoystickDeviceItem *device = deviceList; 149 150 while (device != NULL) { 151 if (device->controller == controller) { 152 return; 153 } 154 device = device->next; 155 } 156 157 device = (SDL_JoystickDeviceItem *) SDL_malloc(sizeof(SDL_JoystickDeviceItem)); 158 if (device == NULL) { 159 return; 160 } 161 162 SDL_zerop(device); 163 164 device->accelerometer = accelerometer; 165 device->instance_id = instancecounter++; 166 167 if (accelerometer) { 168#if TARGET_OS_TV 169 SDL_free(device); 170 return; 171#else 172 device->name = SDL_strdup(accelerometerName); 173 device->naxes = 3; /* Device acceleration in the x, y, and z axes. */ 174 device->nhats = 0; 175 device->nbuttons = 0; 176 177 /* Use the accelerometer name as a GUID. */ 178 SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name))); 179#endif /* TARGET_OS_TV */ 180 } else if (controller) { 181 SDL_SYS_AddMFIJoystickDevice(device, controller); 182 } 183 184 if (deviceList == NULL) { 185 deviceList = device; 186 } else { 187 SDL_JoystickDeviceItem *lastdevice = deviceList; 188 while (lastdevice->next != NULL) { 189 lastdevice = lastdevice->next; 190 } 191 lastdevice->next = device; 192 } 193 194 ++numjoysticks; 195 196 SDL_PrivateJoystickAdded(numjoysticks - 1); 197} 198 199static SDL_JoystickDeviceItem * 200SDL_SYS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device) 201{ 202 SDL_JoystickDeviceItem *prev = NULL; 203 SDL_JoystickDeviceItem *next = NULL; 204 SDL_JoystickDeviceItem *item = deviceList; 205 206 if (device == NULL) { 207 return NULL; 208 } 209 210 next = device->next; 211 212 while (item != NULL) { 213 if (item == device) { 214 break; 215 } 216 prev = item; 217 item = item->next; 218 } 219 220 /* Unlink the device item from the device list. */ 221 if (prev) { 222 prev->next = device->next; 223 } else if (device == deviceList) { 224 deviceList = device->next; 225 } 226 227 if (device->joystick) { 228 device->joystick->hwdata = NULL; 229 } 230 231#ifdef SDL_JOYSTICK_MFI 232 @autoreleasepool { 233 if (device->controller) { 234 /* The controller was explicitly retained in the struct, so it 235 * should be explicitly released before freeing the struct. */ 236 GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller)); 237 controller.controllerPausedHandler = nil; 238 device->controller = nil; 239 } 240 } 241#endif /* SDL_JOYSTICK_MFI */ 242 243 --numjoysticks; 244 245 SDL_PrivateJoystickRemoved(device->instance_id); 246 247 SDL_free(device->name); 248 SDL_free(device); 249 250 return next; 251} 252 253#if TARGET_OS_TV 254static void 255SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue) 256{ 257 BOOL allowRotation = newValue != NULL && *newValue != '0'; 258 259 @autoreleasepool { 260 for (GCController *controller in [GCController controllers]) { 261 if (controller.microGamepad) { 262 controller.microGamepad.allowsRotation = allowRotation; 263 } 264 } 265 } 266} 267#endif /* TARGET_OS_TV */ 268 269/* Function to scan the system for joysticks. 270 * Joystick 0 should be the system default joystick. 271 * It should return 0, or -1 on an unrecoverable fatal error. 272 */ 273int 274SDL_SYS_JoystickInit(void) 275{ 276 @autoreleasepool { 277 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 278 279#if !TARGET_OS_TV 280 if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) { 281 /* Default behavior, accelerometer as joystick */ 282 SDL_SYS_AddJoystickDevice(nil, SDL_TRUE); 283 } 284#endif /* !TARGET_OS_TV */ 285 286#ifdef SDL_JOYSTICK_MFI 287 /* GameController.framework was added in iOS 7. */ 288 if (![GCController class]) { 289 return numjoysticks; 290 } 291 292 for (GCController *controller in [GCController controllers]) { 293 SDL_SYS_AddJoystickDevice(controller, SDL_FALSE); 294 } 295 296#if TARGET_OS_TV 297 SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 298 SDL_AppleTVRemoteRotationHintChanged, NULL); 299#endif /* TARGET_OS_TV */ 300 301 connectObserver = [center addObserverForName:GCControllerDidConnectNotification 302 object:nil 303 queue:nil 304 usingBlock:^(NSNotification *note) { 305 GCController *controller = note.object; 306 SDL_SYS_AddJoystickDevice(controller, SDL_FALSE); 307 }]; 308 309 disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification 310 object:nil 311 queue:nil 312 usingBlock:^(NSNotification *note) { 313 GCController *controller = note.object; 314 SDL_JoystickDeviceItem *device = deviceList; 315 while (device != NULL) { 316 if (device->controller == controller) { 317 SDL_SYS_RemoveJoystickDevice(device); 318 break; 319 } 320 device = device->next; 321 } 322 }]; 323#endif /* SDL_JOYSTICK_MFI */ 324 } 325 326 return numjoysticks; 327} 328 329int SDL_SYS_NumJoysticks() 330{ 331 return numjoysticks; 332} 333 334void SDL_SYS_JoystickDetect() 335{ 336} 337 338/* Function to get the device-dependent name of a joystick */ 339const char * 340SDL_SYS_JoystickNameForDeviceIndex(int device_index) 341{ 342 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 343 return device ? device->name : "Unknown"; 344} 345 346/* Function to perform the mapping from device index to the instance id for this index */ 347SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index) 348{ 349 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 350 return device ? device->instance_id : 0; 351} 352 353/* Function to open a joystick for use. 354 The joystick to open is specified by the device index. 355 This should fill the nbuttons and naxes fields of the joystick structure. 356 It returns 0, or -1 if there is an error. 357 */ 358int 359SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index) 360{ 361 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 362 if (device == NULL) { 363 return SDL_SetError("Could not open Joystick: no hardware device for the specified index"); 364 } 365 366 joystick->hwdata = device; 367 joystick->instance_id = device->instance_id; 368 369 joystick->naxes = device->naxes; 370 joystick->nhats = device->nhats; 371 joystick->nbuttons = device->nbuttons; 372 joystick->nballs = 0; 373 374 device->joystick = joystick; 375 376 @autoreleasepool { 377 if (device->accelerometer) { 378#if !TARGET_OS_TV 379 if (motionManager == nil) { 380 motionManager = [[CMMotionManager alloc] init]; 381 } 382 383 /* Shorter times between updates can significantly increase CPU usage. */ 384 motionManager.accelerometerUpdateInterval = 0.1; 385 [motionManager startAccelerometerUpdates]; 386#endif /* !TARGET_OS_TV */ 387 } else { 388#ifdef SDL_JOYSTICK_MFI 389 GCController *controller = device->controller; 390 controller.controllerPausedHandler = ^(GCController *c) { 391 if (joystick->hwdata) { 392 ++joystick->hwdata->num_pause_presses; 393 } 394 }; 395#endif /* SDL_JOYSTICK_MFI */ 396 } 397 } 398 399 return 0; 400} 401 402/* Function to determine if this joystick is attached to the system right now */ 403SDL_bool 404SDL_SYS_JoystickAttached(SDL_Joystick *joystick) 405{ 406 return joystick->hwdata != NULL; 407} 408 409static void 410SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick) 411{ 412#if !TARGET_OS_TV 413 const float maxgforce = SDL_IPHONE_MAX_GFORCE; 414 const SInt16 maxsint16 = 0x7FFF; 415 CMAcceleration accel; 416 417 @autoreleasepool { 418 if (!motionManager.isAccelerometerActive) { 419 return; 420 } 421 422 accel = motionManager.accelerometerData.acceleration; 423 } 424 425 /* 426 Convert accelerometer data from floating point to Sint16, which is what 427 the joystick system expects. 428 429 To do the conversion, the data is first clamped onto the interval 430 [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied 431 by MAX_SINT16 so that it is mapped to the full range of an Sint16. 432 433 You can customize the clamped range of this function by modifying the 434 SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h. 435 436 Once converted to Sint16, the accelerometer data no longer has coherent 437 units. You can convert the data back to units of g-force by multiplying 438 it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF. 439 */ 440 441 /* clamp the data */ 442 accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce); 443 accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce); 444 accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce); 445 446 /* pass in data mapped to range of SInt16 */ 447 SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16); 448 SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16); 449 SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16); 450#endif /* !TARGET_OS_TV */ 451} 452 453#ifdef SDL_JOYSTICK_MFI 454static Uint8 455SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) 456{ 457 Uint8 hat = 0; 458 459 if (dpad.up.isPressed) { 460 hat |= SDL_HAT_UP; 461 } else if (dpad.down.isPressed) { 462 hat |= SDL_HAT_DOWN; 463 } 464 465 if (dpad.left.isPressed) { 466 hat |= SDL_HAT_LEFT; 467 } else if (dpad.right.isPressed) { 468 hat |= SDL_HAT_RIGHT; 469 } 470 471 if (hat == 0) { 472 return SDL_HAT_CENTERED; 473 } 474 475 return hat; 476} 477#endif 478 479static void 480SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick) 481{ 482#if SDL_JOYSTICK_MFI 483 @autoreleasepool { 484 GCController *controller = joystick->hwdata->controller; 485 Uint8 hatstate = SDL_HAT_CENTERED; 486 int i; 487 int updateplayerindex = 0; 488 489 if (controller.extendedGamepad) { 490 GCExtendedGamepad *gamepad = controller.extendedGamepad; 491 492 /* Axis order matches the XInput Windows mappings. */ 493 Sint16 axes[] = { 494 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767), 495 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767), 496 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768), 497 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767), 498 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767), 499 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768), 500 }; 501 502 /* Button order matches the XInput Windows mappings. */ 503 Uint8 buttons[] = { 504 gamepad.buttonA.isPressed, gamepad.buttonB.isPressed, 505 gamepad.buttonX.isPressed, gamepad.buttonY.isPressed, 506 gamepad.leftShoulder.isPressed, 507 gamepad.rightShoulder.isPressed, 508 }; 509 510 hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad); 511 512 for (i = 0; i < SDL_arraysize(axes); i++) { 513 /* The triggers (axes 2 and 5) are resting at -32768 but SDL 514 * initializes its values to 0. We only want to make sure the 515 * player index is up to date if the user actually moves an axis. */ 516 if ((i != 2 && i != 5) || axes[i] != -32768) { 517 updateplayerindex |= (joystick->axes[i] != axes[i]); 518 } 519 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 520 } 521 522 for (i = 0; i < SDL_arraysize(buttons); i++) { 523 updateplayerindex |= (joystick->buttons[i] != buttons[i]); 524 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 525 } 526 } else if (controller.gamepad) { 527 GCGamepad *gamepad = controller.gamepad; 528 529 /* Button order matches the XInput Windows mappings. */ 530 Uint8 buttons[] = { 531 gamepad.buttonA.isPressed, gamepad.buttonB.isPressed, 532 gamepad.buttonX.isPressed, gamepad.buttonY.isPressed, 533 gamepad.leftShoulder.isPressed, 534 gamepad.rightShoulder.isPressed, 535 }; 536 537 hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad); 538 539 for (i = 0; i < SDL_arraysize(buttons); i++) { 540 updateplayerindex |= (joystick->buttons[i] != buttons[i]); 541 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 542 } 543 } 544#if TARGET_OS_TV 545 else if (controller.microGamepad) { 546 GCMicroGamepad *gamepad = controller.microGamepad; 547 548 Sint16 axes[] = { 549 (Sint16) (gamepad.dpad.xAxis.value * 32767), 550 (Sint16) (gamepad.dpad.yAxis.value * -32767), 551 }; 552 553 for (i = 0; i < SDL_arraysize(axes); i++) { 554 updateplayerindex |= (joystick->axes[i] != axes[i]); 555 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 556 } 557 558 /* Apparently the dpad values are not accurate enough to be useful. */ 559 /* hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad); */ 560 561 Uint8 buttons[] = { 562 gamepad.buttonA.isPressed, 563 gamepad.buttonX.isPressed, 564 }; 565 566 for (i = 0; i < SDL_arraysize(buttons); i++) { 567 updateplayerindex |= (joystick->buttons[i] != buttons[i]); 568 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 569 } 570 571 /* TODO: Figure out what to do with reportsAbsoluteDpadValues */ 572 } 573#endif /* TARGET_OS_TV */ 574 575 if (joystick->nhats > 0) { 576 updateplayerindex |= (joystick->hats[0] != hatstate); 577 SDL_PrivateJoystickHat(joystick, 0, hatstate); 578 } 579 580 for (i = 0; i < joystick->hwdata->num_pause_presses; i++) { 581 /* The pause button is always last. */ 582 Uint8 pausebutton = joystick->nbuttons - 1; 583 584 SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED); 585 SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED); 586 587 updateplayerindex = YES; 588 } 589 590 joystick->hwdata->num_pause_presses = 0; 591 592 if (updateplayerindex && controller.playerIndex == -1) { 593 BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO}; 594 595 /* Find the player index of all other connected controllers. */ 596 for (GCController *c in [GCController controllers]) { 597 if (c != controller && c.playerIndex >= 0) { 598 usedPlayerIndexSlots[c.playerIndex] = YES; 599 } 600 } 601 602 /* Set this controller's player index to the first unused index. 603 * FIXME: This logic isn't great... but SDL doesn't expose this 604 * concept in its external API, so we don't have much to go on. */ 605 for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) { 606 if (!usedPlayerIndexSlots[i]) { 607 controller.playerIndex = i; 608 break; 609 } 610 } 611 } 612 } 613#endif /* SDL_JOYSTICK_MFI */ 614} 615 616/* Function to update the state of a joystick - called as a device poll. 617 * This function shouldn't update the joystick structure directly, 618 * but instead should call SDL_PrivateJoystick*() to deliver events 619 * and update joystick device state. 620 */ 621void 622SDL_SYS_JoystickUpdate(SDL_Joystick * joystick) 623{ 624 SDL_JoystickDeviceItem *device = joystick->hwdata; 625 626 if (device == NULL) { 627 return; 628 } 629 630 if (device->accelerometer) { 631 SDL_SYS_AccelerometerUpdate(joystick); 632 } else if (device->controller) { 633 SDL_SYS_MFIJoystickUpdate(joystick); 634 } 635} 636 637/* Function to close a joystick after use */ 638void 639SDL_SYS_JoystickClose(SDL_Joystick * joystick) 640{ 641 SDL_JoystickDeviceItem *device = joystick->hwdata; 642 643 if (device == NULL) { 644 return; 645 } 646 647 device->joystick = NULL; 648 649 @autoreleasepool { 650 if (device->accelerometer) { 651#if !TARGET_OS_TV 652 [motionManager stopAccelerometerUpdates]; 653#endif /* !TARGET_OS_TV */ 654 } else if (device->controller) { 655#ifdef SDL_JOYSTICK_MFI 656 GCController *controller = device->controller; 657 controller.controllerPausedHandler = nil; 658 controller.playerIndex = -1; 659#endif 660 } 661 } 662} 663 664/* Function to perform any system-specific joystick related cleanup */ 665void 666SDL_SYS_JoystickQuit(void) 667{ 668 @autoreleasepool { 669#ifdef SDL_JOYSTICK_MFI 670 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 671 672 if (connectObserver) { 673 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil]; 674 connectObserver = nil; 675 } 676 677 if (disconnectObserver) { 678 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil]; 679 disconnectObserver = nil; 680 } 681 682#if TARGET_OS_TV 683 SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 684 SDL_AppleTVRemoteRotationHintChanged, NULL); 685#endif /* TARGET_OS_TV */ 686#endif /* SDL_JOYSTICK_MFI */ 687 688 while (deviceList != NULL) { 689 SDL_SYS_RemoveJoystickDevice(deviceList); 690 } 691 692#if !TARGET_OS_TV 693 motionManager = nil; 694#endif /* !TARGET_OS_TV */ 695 } 696 697 numjoysticks = 0; 698} 699 700SDL_JoystickGUID 701SDL_SYS_JoystickGetDeviceGUID( int device_index ) 702{ 703 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 704 SDL_JoystickGUID guid; 705 if (device) { 706 guid = device->guid; 707 } else { 708 SDL_zero(guid); 709 } 710 return guid; 711} 712 713SDL_JoystickGUID 714SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick) 715{ 716 SDL_JoystickGUID guid; 717 if (joystick->hwdata) { 718 guid = joystick->hwdata->guid; 719 } else { 720 SDL_zero(guid); 721 } 722 return guid; 723} 724 725/* vi: set ts=4 sw=4 expandtab: */ 726