src/joystick/iphoneos/SDL_sysjoystick.m
author Sam Lantinga <slouken@libsdl.org>
Sat, 19 Oct 2019 01:54:02 -0700
changeset 13150 29acbbbb41b2
parent 12939 68977584a5d9
permissions -rw-r--r--
Don't try to use the Xbox HID protocol with the NVIDIA Shield controllers
     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 
    52 #include <Availability.h>
    53 #include <objc/message.h>
    54 
    55 /* remove compilation warnings for strict builds by defining these selectors, even though
    56  * they are only ever used indirectly through objc_msgSend
    57  */
    58 @interface GCExtendedGamepad (SDL)
    59 #if (__IPHONE_OS_VERSION_MAX_ALLOWED < 121000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 121000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1401000)
    60 @property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
    61 @property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
    62 #endif
    63 #if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
    64 @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
    65 @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions;
    66 #endif
    67 @end
    68 @interface GCMicroGamepad (SDL)
    69 #if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
    70 @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
    71 #endif
    72 @end
    73 
    74 #endif /* SDL_JOYSTICK_MFI */
    75 
    76 #if !TARGET_OS_TV
    77 static const char *accelerometerName = "iOS Accelerometer";
    78 static CMMotionManager *motionManager = nil;
    79 #endif /* !TARGET_OS_TV */
    80 
    81 static SDL_JoystickDeviceItem *deviceList = NULL;
    82 
    83 static int numjoysticks = 0;
    84 int SDL_AppleTVRemoteOpenedAsJoystick = 0;
    85 
    86 static SDL_JoystickDeviceItem *
    87 GetDeviceForIndex(int device_index)
    88 {
    89     SDL_JoystickDeviceItem *device = deviceList;
    90     int i = 0;
    91 
    92     while (i < device_index) {
    93         if (device == NULL) {
    94             return NULL;
    95         }
    96         device = device->next;
    97         i++;
    98     }
    99 
   100     return device;
   101 }
   102 
   103 static void
   104 IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
   105 {
   106 #ifdef SDL_JOYSTICK_MFI
   107     const Uint16 VENDOR_APPLE = 0x05AC;
   108     const Uint16 VENDOR_MICROSOFT = 0x045e;
   109     const Uint16 VENDOR_SONY = 0x054C;
   110     Uint16 *guid16 = (Uint16 *)device->guid.data;
   111     Uint16 vendor = 0;
   112     Uint16 product = 0;
   113     Uint8 subtype = 0;
   114 
   115     const char *name = NULL;
   116     /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
   117      * struct, and ARC doesn't work with structs. */
   118     device->controller = (__bridge GCController *) CFBridgingRetain(controller);
   119 
   120     if (controller.vendorName) {
   121         name = controller.vendorName.UTF8String;
   122     }
   123 
   124     if (!name) {
   125         name = "MFi Gamepad";
   126     }
   127 
   128     device->name = SDL_strdup(name);
   129 
   130     if (controller.extendedGamepad) {
   131         GCExtendedGamepad *gamepad = controller.extendedGamepad;
   132         int nbuttons = 0;
   133 
   134         /* These buttons are part of the original MFi spec */
   135         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
   136         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
   137         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
   138         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
   139         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
   140         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
   141         nbuttons += 6;
   142 
   143         /* These buttons are available on some newer controllers */
   144 #pragma clang diagnostic push
   145 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
   146         if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
   147             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK);
   148             ++nbuttons;
   149         }
   150         if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
   151             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK);
   152             ++nbuttons;
   153         }
   154         if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
   155             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK);
   156             ++nbuttons;
   157         }
   158         if ([gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu) {
   159             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   160             ++nbuttons;
   161         } else {
   162             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   163             ++nbuttons;
   164             device->uses_pause_handler = SDL_TRUE;
   165         }
   166 #pragma clang diagnostic pop
   167 
   168         if ([controller.vendorName containsString: @"Xbox"]) {
   169             vendor = VENDOR_MICROSOFT;
   170             product = 0x02E0; /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */
   171         } else if ([controller.vendorName containsString: @"DUALSHOCK"]) {
   172             vendor = VENDOR_SONY;
   173             product = 0x09CC; /* Assume DS4 Slim unless/until GCController flows VID/PID */
   174         } else {
   175             vendor = VENDOR_APPLE;
   176             product = 1;
   177             subtype = 1;
   178         }
   179 
   180         device->naxes = 6; /* 2 thumbsticks and 2 triggers */
   181         device->nhats = 1; /* d-pad */
   182         device->nbuttons = nbuttons;
   183 
   184     } else if (controller.gamepad) {
   185         int nbuttons = 0;
   186 
   187         /* These buttons are part of the original MFi spec */
   188         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
   189         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
   190         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
   191         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
   192         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
   193         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
   194         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   195         nbuttons += 7;
   196         device->uses_pause_handler = SDL_TRUE;
   197 
   198         vendor = VENDOR_APPLE;
   199         product = 2;
   200         subtype = 2;
   201         device->naxes = 0; /* no traditional analog inputs */
   202         device->nhats = 1; /* d-pad */
   203         device->nbuttons = nbuttons;
   204     }
   205 #if TARGET_OS_TV
   206     else if (controller.microGamepad) {
   207         GCMicroGamepad *gamepad = controller.microGamepad;
   208         int nbuttons = 0;
   209 
   210         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
   211         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */
   212         nbuttons += 2;
   213 
   214         if ([gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu) {
   215             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   216             ++nbuttons;
   217         } else {
   218             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   219             ++nbuttons;
   220             device->uses_pause_handler = SDL_TRUE;
   221         }
   222 
   223         vendor = VENDOR_APPLE;
   224         product = 3;
   225         subtype = 3;
   226         device->naxes = 2; /* treat the touch surface as two axes */
   227         device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
   228         device->nbuttons = nbuttons;
   229 
   230         controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
   231     }
   232 #endif /* TARGET_OS_TV */
   233 
   234     /* We only need 16 bits for each of these; space them out to fill 128. */
   235     /* Byteswap so devices get same GUID on little/big endian platforms. */
   236     *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
   237     *guid16++ = 0;
   238     *guid16++ = SDL_SwapLE16(vendor);
   239     *guid16++ = 0;
   240     *guid16++ = SDL_SwapLE16(product);
   241     *guid16++ = 0;
   242 
   243     *guid16++ = SDL_SwapLE16(device->button_mask);
   244 
   245     if (subtype != 0) {
   246         /* Note that this is an MFI controller and what subtype it is */
   247         device->guid.data[14] = 'm';
   248         device->guid.data[15] = subtype;
   249     }
   250 
   251     /* This will be set when the first button press of the controller is
   252      * detected. */
   253     controller.playerIndex = -1;
   254 
   255 #endif /* SDL_JOYSTICK_MFI */
   256 }
   257 
   258 static void
   259 IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
   260 {
   261     SDL_JoystickDeviceItem *device = deviceList;
   262 
   263 #if TARGET_OS_TV
   264     if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) {
   265         /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */
   266         if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) {
   267             return;
   268         }
   269     }
   270 #endif
   271 
   272     while (device != NULL) {
   273         if (device->controller == controller) {
   274             return;
   275         }
   276         device = device->next;
   277     }
   278 
   279     device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
   280     if (device == NULL) {
   281         return;
   282     }
   283 
   284     device->accelerometer = accelerometer;
   285     device->instance_id = SDL_GetNextJoystickInstanceID();
   286 
   287     if (accelerometer) {
   288 #if TARGET_OS_TV
   289         SDL_free(device);
   290         return;
   291 #else
   292         device->name = SDL_strdup(accelerometerName);
   293         device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
   294         device->nhats = 0;
   295         device->nbuttons = 0;
   296 
   297         /* Use the accelerometer name as a GUID. */
   298         SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
   299 #endif /* TARGET_OS_TV */
   300     } else if (controller) {
   301         IOS_AddMFIJoystickDevice(device, controller);
   302     }
   303 
   304     if (deviceList == NULL) {
   305         deviceList = device;
   306     } else {
   307         SDL_JoystickDeviceItem *lastdevice = deviceList;
   308         while (lastdevice->next != NULL) {
   309             lastdevice = lastdevice->next;
   310         }
   311         lastdevice->next = device;
   312     }
   313 
   314     ++numjoysticks;
   315 
   316     SDL_PrivateJoystickAdded(device->instance_id);
   317 }
   318 
   319 static SDL_JoystickDeviceItem *
   320 IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
   321 {
   322     SDL_JoystickDeviceItem *prev = NULL;
   323     SDL_JoystickDeviceItem *next = NULL;
   324     SDL_JoystickDeviceItem *item = deviceList;
   325 
   326     if (device == NULL) {
   327         return NULL;
   328     }
   329 
   330     next = device->next;
   331 
   332     while (item != NULL) {
   333         if (item == device) {
   334             break;
   335         }
   336         prev = item;
   337         item = item->next;
   338     }
   339 
   340     /* Unlink the device item from the device list. */
   341     if (prev) {
   342         prev->next = device->next;
   343     } else if (device == deviceList) {
   344         deviceList = device->next;
   345     }
   346 
   347     if (device->joystick) {
   348         device->joystick->hwdata = NULL;
   349     }
   350 
   351 #ifdef SDL_JOYSTICK_MFI
   352     @autoreleasepool {
   353         if (device->controller) {
   354             /* The controller was explicitly retained in the struct, so it
   355              * should be explicitly released before freeing the struct. */
   356             GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
   357             controller.controllerPausedHandler = nil;
   358             device->controller = nil;
   359         }
   360     }
   361 #endif /* SDL_JOYSTICK_MFI */
   362 
   363     --numjoysticks;
   364 
   365     SDL_PrivateJoystickRemoved(device->instance_id);
   366 
   367     SDL_free(device->name);
   368     SDL_free(device);
   369 
   370     return next;
   371 }
   372 
   373 #if TARGET_OS_TV
   374 static void SDLCALL
   375 SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
   376 {
   377     BOOL allowRotation = newValue != NULL && *newValue != '0';
   378 
   379     @autoreleasepool {
   380         for (GCController *controller in [GCController controllers]) {
   381             if (controller.microGamepad) {
   382                 controller.microGamepad.allowsRotation = allowRotation;
   383             }
   384         }
   385     }
   386 }
   387 #endif /* TARGET_OS_TV */
   388 
   389 static int
   390 IOS_JoystickInit(void)
   391 {
   392     @autoreleasepool {
   393         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   394 
   395 #if !TARGET_OS_TV
   396         if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
   397             /* Default behavior, accelerometer as joystick */
   398             IOS_AddJoystickDevice(nil, SDL_TRUE);
   399         }
   400 #endif /* !TARGET_OS_TV */
   401 
   402 #ifdef SDL_JOYSTICK_MFI
   403         /* GameController.framework was added in iOS 7. */
   404         if (![GCController class]) {
   405             return 0;
   406         }
   407 
   408         for (GCController *controller in [GCController controllers]) {
   409             IOS_AddJoystickDevice(controller, SDL_FALSE);
   410         }
   411 
   412 #if TARGET_OS_TV
   413         SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
   414                             SDL_AppleTVRemoteRotationHintChanged, NULL);
   415 #endif /* TARGET_OS_TV */
   416 
   417         connectObserver = [center addObserverForName:GCControllerDidConnectNotification
   418                                               object:nil
   419                                                queue:nil
   420                                           usingBlock:^(NSNotification *note) {
   421                                               GCController *controller = note.object;
   422                                               IOS_AddJoystickDevice(controller, SDL_FALSE);
   423                                           }];
   424 
   425         disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
   426                                                  object:nil
   427                                                   queue:nil
   428                                              usingBlock:^(NSNotification *note) {
   429                                                  GCController *controller = note.object;
   430                                                  SDL_JoystickDeviceItem *device = deviceList;
   431                                                  while (device != NULL) {
   432                                                      if (device->controller == controller) {
   433                                                          IOS_RemoveJoystickDevice(device);
   434                                                          break;
   435                                                      }
   436                                                      device = device->next;
   437                                                  }
   438                                              }];
   439 #endif /* SDL_JOYSTICK_MFI */
   440     }
   441 
   442     return 0;
   443 }
   444 
   445 static int
   446 IOS_JoystickGetCount(void)
   447 {
   448     return numjoysticks;
   449 }
   450 
   451 static void
   452 IOS_JoystickDetect(void)
   453 {
   454 }
   455 
   456 static const char *
   457 IOS_JoystickGetDeviceName(int device_index)
   458 {
   459     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   460     return device ? device->name : "Unknown";
   461 }
   462 
   463 static int
   464 IOS_JoystickGetDevicePlayerIndex(int device_index)
   465 {
   466     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   467     return device ? (int)device->controller.playerIndex : -1;
   468 }
   469 
   470 static SDL_JoystickGUID
   471 IOS_JoystickGetDeviceGUID( int device_index )
   472 {
   473     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   474     SDL_JoystickGUID guid;
   475     if (device) {
   476         guid = device->guid;
   477     } else {
   478         SDL_zero(guid);
   479     }
   480     return guid;
   481 }
   482 
   483 static SDL_JoystickID
   484 IOS_JoystickGetDeviceInstanceID(int device_index)
   485 {
   486     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   487     return device ? device->instance_id : -1;
   488 }
   489 
   490 static int
   491 IOS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   492 {
   493     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   494     if (device == NULL) {
   495         return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
   496     }
   497 
   498     joystick->hwdata = device;
   499     joystick->instance_id = device->instance_id;
   500 
   501     joystick->naxes = device->naxes;
   502     joystick->nhats = device->nhats;
   503     joystick->nbuttons = device->nbuttons;
   504     joystick->nballs = 0;
   505 
   506     device->joystick = joystick;
   507 
   508     @autoreleasepool {
   509         if (device->accelerometer) {
   510 #if !TARGET_OS_TV
   511             if (motionManager == nil) {
   512                 motionManager = [[CMMotionManager alloc] init];
   513             }
   514 
   515             /* Shorter times between updates can significantly increase CPU usage. */
   516             motionManager.accelerometerUpdateInterval = 0.1;
   517             [motionManager startAccelerometerUpdates];
   518 #endif /* !TARGET_OS_TV */
   519         } else {
   520 #ifdef SDL_JOYSTICK_MFI
   521             if (device->uses_pause_handler) {
   522                 GCController *controller = device->controller;
   523                 controller.controllerPausedHandler = ^(GCController *c) {
   524                     if (joystick->hwdata) {
   525                         ++joystick->hwdata->num_pause_presses;
   526                     }
   527                 };
   528             }
   529 #endif /* SDL_JOYSTICK_MFI */
   530         }
   531     }
   532     if (device->remote) {
   533         ++SDL_AppleTVRemoteOpenedAsJoystick;
   534     }
   535 
   536     return 0;
   537 }
   538 
   539 static void
   540 IOS_AccelerometerUpdate(SDL_Joystick * joystick)
   541 {
   542 #if !TARGET_OS_TV
   543     const float maxgforce = SDL_IPHONE_MAX_GFORCE;
   544     const SInt16 maxsint16 = 0x7FFF;
   545     CMAcceleration accel;
   546 
   547     @autoreleasepool {
   548         if (!motionManager.isAccelerometerActive) {
   549             return;
   550         }
   551 
   552         accel = motionManager.accelerometerData.acceleration;
   553     }
   554 
   555     /*
   556      Convert accelerometer data from floating point to Sint16, which is what
   557      the joystick system expects.
   558 
   559      To do the conversion, the data is first clamped onto the interval
   560      [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
   561      by MAX_SINT16 so that it is mapped to the full range of an Sint16.
   562 
   563      You can customize the clamped range of this function by modifying the
   564      SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
   565 
   566      Once converted to Sint16, the accelerometer data no longer has coherent
   567      units. You can convert the data back to units of g-force by multiplying
   568      it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
   569      */
   570 
   571     /* clamp the data */
   572     accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
   573     accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
   574     accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
   575 
   576     /* pass in data mapped to range of SInt16 */
   577     SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
   578     SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
   579     SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
   580 #endif /* !TARGET_OS_TV */
   581 }
   582 
   583 #ifdef SDL_JOYSTICK_MFI
   584 static Uint8
   585 IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
   586 {
   587     Uint8 hat = 0;
   588 
   589     if (dpad.up.isPressed) {
   590         hat |= SDL_HAT_UP;
   591     } else if (dpad.down.isPressed) {
   592         hat |= SDL_HAT_DOWN;
   593     }
   594 
   595     if (dpad.left.isPressed) {
   596         hat |= SDL_HAT_LEFT;
   597     } else if (dpad.right.isPressed) {
   598         hat |= SDL_HAT_RIGHT;
   599     }
   600 
   601     if (hat == 0) {
   602         return SDL_HAT_CENTERED;
   603     }
   604 
   605     return hat;
   606 }
   607 #endif
   608 
   609 static void
   610 IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
   611 {
   612 #if SDL_JOYSTICK_MFI
   613     @autoreleasepool {
   614         GCController *controller = joystick->hwdata->controller;
   615         Uint8 hatstate = SDL_HAT_CENTERED;
   616         int i;
   617         int updateplayerindex = 0;
   618         int pause_button_index = 0;
   619 
   620         if (controller.extendedGamepad) {
   621             GCExtendedGamepad *gamepad = controller.extendedGamepad;
   622 
   623             /* Axis order matches the XInput Windows mappings. */
   624             Sint16 axes[] = {
   625                 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
   626                 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
   627                 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
   628                 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
   629                 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
   630                 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
   631             };
   632 
   633             /* Button order matches the XInput Windows mappings. */
   634             Uint8 buttons[joystick->nbuttons];
   635             int button_count = 0;
   636 
   637             /* These buttons are part of the original MFi spec */
   638             buttons[button_count++] = gamepad.buttonA.isPressed;
   639             buttons[button_count++] = gamepad.buttonB.isPressed;
   640             buttons[button_count++] = gamepad.buttonX.isPressed;
   641             buttons[button_count++] = gamepad.buttonY.isPressed;
   642             buttons[button_count++] = gamepad.leftShoulder.isPressed;
   643             buttons[button_count++] = gamepad.rightShoulder.isPressed;
   644 
   645             /* These buttons are available on some newer controllers */
   646 #pragma clang diagnostic push
   647 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
   648             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) {
   649                 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
   650             }
   651             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) {
   652                 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
   653             }
   654             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) {
   655                 buttons[button_count++] = gamepad.buttonOptions.isPressed;
   656             }
   657             /* This must be the last button, so we can optionally handle it with pause_button_index below */
   658             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
   659                 if (joystick->hwdata->uses_pause_handler) {
   660                     pause_button_index = button_count;
   661                     buttons[button_count++] = joystick->delayed_guide_button;
   662                 } else {
   663                     buttons[button_count++] = gamepad.buttonMenu.isPressed;
   664                 }
   665             }
   666 #pragma clang diagnostic pop
   667 
   668             hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
   669 
   670             for (i = 0; i < SDL_arraysize(axes); i++) {
   671                 /* The triggers (axes 2 and 5) are resting at -32768 but SDL
   672                  * initializes its values to 0. We only want to make sure the
   673                  * player index is up to date if the user actually moves an axis. */
   674                 if ((i != 2 && i != 5) || axes[i] != -32768) {
   675                     updateplayerindex |= (joystick->axes[i].value != axes[i]);
   676                 }
   677                 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
   678             }
   679 
   680             for (i = 0; i < button_count; i++) {
   681                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   682                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   683             }
   684         } else if (controller.gamepad) {
   685             GCGamepad *gamepad = controller.gamepad;
   686 
   687             /* Button order matches the XInput Windows mappings. */
   688             Uint8 buttons[joystick->nbuttons];
   689             int button_count = 0;
   690             buttons[button_count++] = gamepad.buttonA.isPressed;
   691             buttons[button_count++] = gamepad.buttonB.isPressed;
   692             buttons[button_count++] = gamepad.buttonX.isPressed;
   693             buttons[button_count++] = gamepad.buttonY.isPressed;
   694             buttons[button_count++] = gamepad.leftShoulder.isPressed;
   695             buttons[button_count++] = gamepad.rightShoulder.isPressed;
   696             pause_button_index = button_count;
   697             buttons[button_count++] = joystick->delayed_guide_button;
   698 
   699             hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
   700 
   701             for (i = 0; i < button_count; i++) {
   702                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   703                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   704             }
   705         }
   706 #if TARGET_OS_TV
   707         else if (controller.microGamepad) {
   708             GCMicroGamepad *gamepad = controller.microGamepad;
   709 
   710             Sint16 axes[] = {
   711                 (Sint16) (gamepad.dpad.xAxis.value * 32767),
   712                 (Sint16) (gamepad.dpad.yAxis.value * -32767),
   713             };
   714 
   715             for (i = 0; i < SDL_arraysize(axes); i++) {
   716                 updateplayerindex |= (joystick->axes[i].value != axes[i]);
   717                 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
   718             }
   719 
   720             Uint8 buttons[joystick->nbuttons];
   721             int button_count = 0;
   722             buttons[button_count++] = gamepad.buttonA.isPressed;
   723             buttons[button_count++] = gamepad.buttonX.isPressed;
   724 #pragma clang diagnostic push
   725 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
   726             /* This must be the last button, so we can optionally handle it with pause_button_index below */
   727             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
   728                 if (joystick->hwdata->uses_pause_handler) {
   729                     pause_button_index = button_count;
   730                     buttons[button_count++] = joystick->delayed_guide_button;
   731                 } else {
   732                     buttons[button_count++] = gamepad.buttonMenu.isPressed;
   733                 }
   734             }
   735 #pragma clang diagnostic pop
   736 
   737             for (i = 0; i < button_count; i++) {
   738                 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   739                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   740             }
   741         }
   742 #endif /* TARGET_OS_TV */
   743 
   744         if (joystick->nhats > 0) {
   745             updateplayerindex |= (joystick->hats[0] != hatstate);
   746             SDL_PrivateJoystickHat(joystick, 0, hatstate);
   747         }
   748 
   749         if (joystick->hwdata->uses_pause_handler) {
   750             for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   751                 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED);
   752                 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED);
   753                 updateplayerindex = YES;
   754             }
   755             joystick->hwdata->num_pause_presses = 0;
   756         }
   757 
   758         if (updateplayerindex && controller.playerIndex == -1) {
   759             BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
   760 
   761             /* Find the player index of all other connected controllers. */
   762             for (GCController *c in [GCController controllers]) {
   763                 if (c != controller && c.playerIndex >= 0) {
   764                     usedPlayerIndexSlots[c.playerIndex] = YES;
   765                 }
   766             }
   767 
   768             /* Set this controller's player index to the first unused index.
   769              * FIXME: This logic isn't great... but SDL doesn't expose this
   770              * concept in its external API, so we don't have much to go on. */
   771             for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) {
   772                 if (!usedPlayerIndexSlots[i]) {
   773                     controller.playerIndex = i;
   774                     break;
   775                 }
   776             }
   777         }
   778     }
   779 #endif /* SDL_JOYSTICK_MFI */
   780 }
   781 
   782 static int
   783 IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
   784 {
   785     return SDL_Unsupported();
   786 }
   787 
   788 static void
   789 IOS_JoystickUpdate(SDL_Joystick * joystick)
   790 {
   791     SDL_JoystickDeviceItem *device = joystick->hwdata;
   792 
   793     if (device == NULL) {
   794         return;
   795     }
   796     
   797     if (device->accelerometer) {
   798         IOS_AccelerometerUpdate(joystick);
   799     } else if (device->controller) {
   800         IOS_MFIJoystickUpdate(joystick);
   801     }
   802 }
   803 
   804 static void
   805 IOS_JoystickClose(SDL_Joystick * joystick)
   806 {
   807     SDL_JoystickDeviceItem *device = joystick->hwdata;
   808 
   809     if (device == NULL) {
   810         return;
   811     }
   812 
   813     device->joystick = NULL;
   814 
   815     @autoreleasepool {
   816         if (device->accelerometer) {
   817 #if !TARGET_OS_TV
   818             [motionManager stopAccelerometerUpdates];
   819 #endif /* !TARGET_OS_TV */
   820         } else if (device->controller) {
   821 #ifdef SDL_JOYSTICK_MFI
   822             GCController *controller = device->controller;
   823             controller.controllerPausedHandler = nil;
   824             controller.playerIndex = -1;
   825 #endif
   826         }
   827     }
   828     if (device->remote) {
   829         --SDL_AppleTVRemoteOpenedAsJoystick;
   830     }
   831 }
   832 
   833 static void
   834 IOS_JoystickQuit(void)
   835 {
   836     @autoreleasepool {
   837 #ifdef SDL_JOYSTICK_MFI
   838         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   839 
   840         if (connectObserver) {
   841             [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
   842             connectObserver = nil;
   843         }
   844 
   845         if (disconnectObserver) {
   846             [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
   847             disconnectObserver = nil;
   848         }
   849 
   850 #if TARGET_OS_TV
   851         SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
   852                             SDL_AppleTVRemoteRotationHintChanged, NULL);
   853 #endif /* TARGET_OS_TV */
   854 #endif /* SDL_JOYSTICK_MFI */
   855 
   856         while (deviceList != NULL) {
   857             IOS_RemoveJoystickDevice(deviceList);
   858         }
   859 
   860 #if !TARGET_OS_TV
   861         motionManager = nil;
   862 #endif /* !TARGET_OS_TV */
   863     }
   864 
   865     numjoysticks = 0;
   866 }
   867 
   868 SDL_JoystickDriver SDL_IOS_JoystickDriver =
   869 {
   870     IOS_JoystickInit,
   871     IOS_JoystickGetCount,
   872     IOS_JoystickDetect,
   873     IOS_JoystickGetDeviceName,
   874     IOS_JoystickGetDevicePlayerIndex,
   875     IOS_JoystickGetDeviceGUID,
   876     IOS_JoystickGetDeviceInstanceID,
   877     IOS_JoystickOpen,
   878     IOS_JoystickRumble,
   879     IOS_JoystickUpdate,
   880     IOS_JoystickClose,
   881     IOS_JoystickQuit,
   882 };
   883 
   884 /* vi: set ts=4 sw=4 expandtab: */