src/joystick/iphoneos/SDL_sysjoystick.m
author Sam Lantinga <slouken@libsdl.org>
Fri, 07 Oct 2016 23:40:44 -0700
changeset 10499 363c1c7e7a41
parent 10351 12f90eb6b52b
child 10617 346c02ff71b6
permissions -rw-r--r--
Implemented SDL_GetHintBoolean() to make it easier to check boolean hints
     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         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 
   145 static void
   146 SDL_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 
   199 static SDL_JoystickDeviceItem *
   200 SDL_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
   254 static void
   255 SDL_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  */
   273 int
   274 SDL_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 
   329 int SDL_SYS_NumJoysticks()
   330 {
   331     return numjoysticks;
   332 }
   333 
   334 void SDL_SYS_JoystickDetect()
   335 {
   336 }
   337 
   338 /* Function to get the device-dependent name of a joystick */
   339 const char *
   340 SDL_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 */
   347 SDL_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  */
   358 int
   359 SDL_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 */
   403 SDL_bool
   404 SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
   405 {
   406     return joystick->hwdata != NULL;
   407 }
   408 
   409 static void
   410 SDL_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
   454 static Uint8
   455 SDL_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 
   479 static void
   480 SDL_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  */
   621 void
   622 SDL_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 */
   638 void
   639 SDL_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 */
   665 void
   666 SDL_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 
   700 SDL_JoystickGUID
   701 SDL_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 
   713 SDL_JoystickGUID
   714 SDL_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: */