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