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