src/joystick/iphoneos/SDL_sysjoystick.m
author Alex Szpakowski <slime73@gmail.com>
Tue, 13 Sep 2016 22:18:06 -0300
changeset 10340 5724f5087acd
parent 10228 9a277db2806d
child 10341 75ac5b0ed013
permissions -rw-r--r--
Initial Apple TV / tvOS support.

The Apple TV remote is currently exposed as a joystick with its touch surface treated as two axes. Key presses are also generated when its buttons and touch surface are used.

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