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