src/joystick/iphoneos/SDL_sysjoystick.m
author David Ludwig <dludwig@pobox.com>
Sat, 28 Mar 2020 15:43:55 -0400
changeset 13679 304c6020419a
parent 13611 bcbfec15e25f
permissions -rw-r--r--
Fixed Bug 4883, redux - connect SDL_GetDisplayDPI to UIKit_GetDisplayDPI

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