src/joystick/iphoneos/SDL_sysjoystick.m
author Sam Lantinga <slouken@libsdl.org>
Thu, 02 Feb 2017 16:56:02 -0800
changeset 10873 8754e496e7b2
parent 10737 3406a0f8b041
child 11284 3db78361e751
permissions -rw-r--r--
Fixed build on Apple TV
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 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
   330 SDL_SYS_NumJoysticks(void)
   331 {
   332     return numjoysticks;
   333 }
   334 
   335 void
   336 SDL_SYS_JoystickDetect(void)
   337 {
   338 }
   339 
   340 /* Function to get the device-dependent name of a joystick */
   341 const char *
   342 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
   343 {
   344     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   345     return device ? device->name : "Unknown";
   346 }
   347 
   348 /* Function to perform the mapping from device index to the instance id for this index */
   349 SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
   350 {
   351     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   352     return device ? device->instance_id : 0;
   353 }
   354 
   355 /* Function to open a joystick for use.
   356    The joystick to open is specified by the device index.
   357    This should fill the nbuttons and naxes fields of the joystick structure.
   358    It returns 0, or -1 if there is an error.
   359  */
   360 int
   361 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   362 {
   363     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   364     if (device == NULL) {
   365         return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
   366     }
   367 
   368     joystick->hwdata = device;
   369     joystick->instance_id = device->instance_id;
   370 
   371     joystick->naxes = device->naxes;
   372     joystick->nhats = device->nhats;
   373     joystick->nbuttons = device->nbuttons;
   374     joystick->nballs = 0;
   375 
   376     device->joystick = joystick;
   377 
   378     @autoreleasepool {
   379         if (device->accelerometer) {
   380 #if !TARGET_OS_TV
   381             if (motionManager == nil) {
   382                 motionManager = [[CMMotionManager alloc] init];
   383             }
   384 
   385             /* Shorter times between updates can significantly increase CPU usage. */
   386             motionManager.accelerometerUpdateInterval = 0.1;
   387             [motionManager startAccelerometerUpdates];
   388 #endif /* !TARGET_OS_TV */
   389         } else {
   390 #ifdef SDL_JOYSTICK_MFI
   391             GCController *controller = device->controller;
   392             controller.controllerPausedHandler = ^(GCController *c) {
   393                 if (joystick->hwdata) {
   394                     ++joystick->hwdata->num_pause_presses;
   395                 }
   396             };
   397 #endif /* SDL_JOYSTICK_MFI */
   398         }
   399     }
   400 
   401     return 0;
   402 }
   403 
   404 /* Function to determine if this joystick is attached to the system right now */
   405 SDL_bool
   406 SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
   407 {
   408     return joystick->hwdata != NULL;
   409 }
   410 
   411 static void
   412 SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
   413 {
   414 #if !TARGET_OS_TV
   415     const float maxgforce = SDL_IPHONE_MAX_GFORCE;
   416     const SInt16 maxsint16 = 0x7FFF;
   417     CMAcceleration accel;
   418 
   419     @autoreleasepool {
   420         if (!motionManager.isAccelerometerActive) {
   421             return;
   422         }
   423 
   424         accel = motionManager.accelerometerData.acceleration;
   425     }
   426 
   427     /*
   428      Convert accelerometer data from floating point to Sint16, which is what
   429      the joystick system expects.
   430 
   431      To do the conversion, the data is first clamped onto the interval
   432      [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
   433      by MAX_SINT16 so that it is mapped to the full range of an Sint16.
   434 
   435      You can customize the clamped range of this function by modifying the
   436      SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
   437 
   438      Once converted to Sint16, the accelerometer data no longer has coherent
   439      units. You can convert the data back to units of g-force by multiplying
   440      it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
   441      */
   442 
   443     /* clamp the data */
   444     accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
   445     accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
   446     accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
   447 
   448     /* pass in data mapped to range of SInt16 */
   449     SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
   450     SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
   451     SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
   452 #endif /* !TARGET_OS_TV */
   453 }
   454 
   455 #ifdef SDL_JOYSTICK_MFI
   456 static Uint8
   457 SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
   458 {
   459     Uint8 hat = 0;
   460 
   461     if (dpad.up.isPressed) {
   462         hat |= SDL_HAT_UP;
   463     } else if (dpad.down.isPressed) {
   464         hat |= SDL_HAT_DOWN;
   465     }
   466 
   467     if (dpad.left.isPressed) {
   468         hat |= SDL_HAT_LEFT;
   469     } else if (dpad.right.isPressed) {
   470         hat |= SDL_HAT_RIGHT;
   471     }
   472 
   473     if (hat == 0) {
   474         return SDL_HAT_CENTERED;
   475     }
   476 
   477     return hat;
   478 }
   479 #endif
   480 
   481 static void
   482 SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
   483 {
   484 #if SDL_JOYSTICK_MFI
   485     @autoreleasepool {
   486         GCController *controller = joystick->hwdata->controller;
   487         Uint8 hatstate = SDL_HAT_CENTERED;
   488         int i;
   489         int updateplayerindex = 0;
   490 
   491         if (controller.extendedGamepad) {
   492             GCExtendedGamepad *gamepad = controller.extendedGamepad;
   493 
   494             /* Axis order matches the XInput Windows mappings. */
   495             Sint16 axes[] = {
   496                 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
   497                 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
   498                 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
   499                 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
   500                 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
   501                 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
   502             };
   503 
   504             /* Button order matches the XInput Windows mappings. */
   505             Uint8 buttons[] = {
   506                 gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
   507                 gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
   508                 gamepad.leftShoulder.isPressed,
   509                 gamepad.rightShoulder.isPressed,
   510             };
   511 
   512             hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
   513 
   514             for (i = 0; i < SDL_arraysize(axes); i++) {
   515                 /* The triggers (axes 2 and 5) are resting at -32768 but SDL
   516                  * initializes its values to 0. We only want to make sure the
   517                  * player index is up to date if the user actually moves an axis. */
   518                 if ((i != 2 && i != 5) || axes[i] != -32768) {
   519                     updateplayerindex |= (joystick->axes[i].value != axes[i]);
   520                 }
   521                 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
   522             }
   523 
   524             for (i = 0; i < SDL_arraysize(buttons); i++) {
   525                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   526                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   527             }
   528         } else if (controller.gamepad) {
   529             GCGamepad *gamepad = controller.gamepad;
   530 
   531             /* Button order matches the XInput Windows mappings. */
   532             Uint8 buttons[] = {
   533                 gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
   534                 gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
   535                 gamepad.leftShoulder.isPressed,
   536                 gamepad.rightShoulder.isPressed,
   537             };
   538 
   539             hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
   540 
   541             for (i = 0; i < SDL_arraysize(buttons); i++) {
   542                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   543                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   544             }
   545         }
   546 #if TARGET_OS_TV
   547         else if (controller.microGamepad) {
   548             GCMicroGamepad *gamepad = controller.microGamepad;
   549 
   550             Sint16 axes[] = {
   551                 (Sint16) (gamepad.dpad.xAxis.value * 32767),
   552                 (Sint16) (gamepad.dpad.yAxis.value * -32767),
   553             };
   554 
   555             for (i = 0; i < SDL_arraysize(axes); i++) {
   556                 updateplayerindex |= (joystick->axes[i].value != axes[i]);
   557                 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
   558             }
   559 
   560             /* Apparently the dpad values are not accurate enough to be useful. */
   561             /* hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad); */
   562 
   563             Uint8 buttons[] = {
   564                 gamepad.buttonA.isPressed,
   565                 gamepad.buttonX.isPressed,
   566             };
   567 
   568             for (i = 0; i < SDL_arraysize(buttons); i++) {
   569                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   570                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   571             }
   572 
   573             /* TODO: Figure out what to do with reportsAbsoluteDpadValues */
   574         }
   575 #endif /* TARGET_OS_TV */
   576 
   577         if (joystick->nhats > 0) {
   578             updateplayerindex |= (joystick->hats[0] != hatstate);
   579             SDL_PrivateJoystickHat(joystick, 0, hatstate);
   580         }
   581 
   582         for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   583             /* The pause button is always last. */
   584             Uint8 pausebutton = joystick->nbuttons - 1;
   585 
   586             SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED);
   587             SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED);
   588 
   589             updateplayerindex = YES;
   590         }
   591 
   592         joystick->hwdata->num_pause_presses = 0;
   593 
   594         if (updateplayerindex && controller.playerIndex == -1) {
   595             BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
   596 
   597             /* Find the player index of all other connected controllers. */
   598             for (GCController *c in [GCController controllers]) {
   599                 if (c != controller && c.playerIndex >= 0) {
   600                     usedPlayerIndexSlots[c.playerIndex] = YES;
   601                 }
   602             }
   603 
   604             /* Set this controller's player index to the first unused index.
   605              * FIXME: This logic isn't great... but SDL doesn't expose this
   606              * concept in its external API, so we don't have much to go on. */
   607             for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) {
   608                 if (!usedPlayerIndexSlots[i]) {
   609                     controller.playerIndex = i;
   610                     break;
   611                 }
   612             }
   613         }
   614     }
   615 #endif /* SDL_JOYSTICK_MFI */
   616 }
   617 
   618 /* Function to update the state of a joystick - called as a device poll.
   619  * This function shouldn't update the joystick structure directly,
   620  * but instead should call SDL_PrivateJoystick*() to deliver events
   621  * and update joystick device state.
   622  */
   623 void
   624 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   625 {
   626     SDL_JoystickDeviceItem *device = joystick->hwdata;
   627 
   628     if (device == NULL) {
   629         return;
   630     }
   631 
   632     if (device->accelerometer) {
   633         SDL_SYS_AccelerometerUpdate(joystick);
   634     } else if (device->controller) {
   635         SDL_SYS_MFIJoystickUpdate(joystick);
   636     }
   637 }
   638 
   639 /* Function to close a joystick after use */
   640 void
   641 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   642 {
   643     SDL_JoystickDeviceItem *device = joystick->hwdata;
   644 
   645     if (device == NULL) {
   646         return;
   647     }
   648 
   649     device->joystick = NULL;
   650 
   651     @autoreleasepool {
   652         if (device->accelerometer) {
   653 #if !TARGET_OS_TV
   654             [motionManager stopAccelerometerUpdates];
   655 #endif /* !TARGET_OS_TV */
   656         } else if (device->controller) {
   657 #ifdef SDL_JOYSTICK_MFI
   658             GCController *controller = device->controller;
   659             controller.controllerPausedHandler = nil;
   660             controller.playerIndex = -1;
   661 #endif
   662         }
   663     }
   664 }
   665 
   666 /* Function to perform any system-specific joystick related cleanup */
   667 void
   668 SDL_SYS_JoystickQuit(void)
   669 {
   670     @autoreleasepool {
   671 #ifdef SDL_JOYSTICK_MFI
   672         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   673 
   674         if (connectObserver) {
   675             [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
   676             connectObserver = nil;
   677         }
   678 
   679         if (disconnectObserver) {
   680             [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
   681             disconnectObserver = nil;
   682         }
   683 
   684 #if TARGET_OS_TV
   685         SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
   686                             SDL_AppleTVRemoteRotationHintChanged, NULL);
   687 #endif /* TARGET_OS_TV */
   688 #endif /* SDL_JOYSTICK_MFI */
   689 
   690         while (deviceList != NULL) {
   691             SDL_SYS_RemoveJoystickDevice(deviceList);
   692         }
   693 
   694 #if !TARGET_OS_TV
   695         motionManager = nil;
   696 #endif /* !TARGET_OS_TV */
   697     }
   698 
   699     numjoysticks = 0;
   700 }
   701 
   702 SDL_JoystickGUID
   703 SDL_SYS_JoystickGetDeviceGUID( int device_index )
   704 {
   705     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   706     SDL_JoystickGUID guid;
   707     if (device) {
   708         guid = device->guid;
   709     } else {
   710         SDL_zero(guid);
   711     }
   712     return guid;
   713 }
   714 
   715 SDL_JoystickGUID
   716 SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
   717 {
   718     SDL_JoystickGUID guid;
   719     if (joystick->hwdata) {
   720         guid = joystick->hwdata->guid;
   721     } else {
   722         SDL_zero(guid);
   723     }
   724     return guid;
   725 }
   726 
   727 /* vi: set ts=4 sw=4 expandtab: */