src/joystick/iphoneos/SDL_sysjoystick.m
author Alex Szpakowski <slime73@gmail.com>
Sat, 17 Sep 2016 01:31:07 -0300
changeset 10351 12f90eb6b52b
parent 10341 75ac5b0ed013
child 10499 363c1c7e7a41
permissions -rw-r--r--
Added a new hint SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION.

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