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