sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

SDL_mfijoystick.m (49703B)


      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_events.h"
     25 #include "SDL_joystick.h"
     26 #include "SDL_hints.h"
     27 #include "SDL_stdinc.h"
     28 #include "../SDL_sysjoystick.h"
     29 #include "../SDL_joystick_c.h"
     30 #include "../usb_ids.h"
     31 
     32 #include "SDL_mfijoystick_c.h"
     33 
     34 #if !SDL_EVENTS_DISABLED
     35 #include "../../events/SDL_events_c.h"
     36 #endif
     37 
     38 #if TARGET_OS_IOS
     39 #define SDL_JOYSTICK_iOS_ACCELEROMETER
     40 #import <CoreMotion/CoreMotion.h>
     41 #endif
     42 
     43 #if defined(__MACOSX__)
     44 #include <IOKit/hid/IOHIDManager.h>
     45 #include <AppKit/NSApplication.h>
     46 #ifndef NSAppKitVersionNumber10_15
     47 #define NSAppKitVersionNumber10_15 1894
     48 #endif
     49 #endif /* __MACOSX__ */
     50 
     51 #ifdef SDL_JOYSTICK_MFI
     52 #import <GameController/GameController.h>
     53 
     54 static id connectObserver = nil;
     55 static id disconnectObserver = nil;
     56 
     57 #include <Availability.h>
     58 #include <objc/message.h>
     59 
     60 /* remove compilation warnings for strict builds by defining these selectors, even though
     61  * they are only ever used indirectly through objc_msgSend
     62  */
     63 @interface GCController (SDL)
     64 #if defined(__MACOSX__) && (__MAC_OS_X_VERSION_MAX_ALLOWED <= 101600)
     65 + (BOOL)supportsHIDDevice:(IOHIDDeviceRef)device;
     66 #endif
     67 @end
     68 @interface GCExtendedGamepad (SDL)
     69 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 121000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 121000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1401000))
     70 @property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
     71 @property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
     72 #endif
     73 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 130000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1500000))
     74 @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
     75 @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions;
     76 #endif
     77 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000))
     78 @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonHome;
     79 #endif
     80 @end
     81 @interface GCMicroGamepad (SDL)
     82 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 130000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1500000))
     83 @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
     84 #endif
     85 @end
     86 
     87 #if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000) || (__MAC_OS_X_VERSION_MAX_ALLOWED > 101600)
     88 #define ENABLE_MFI_BATTERY
     89 #define ENABLE_MFI_RUMBLE
     90 #define ENABLE_MFI_LIGHT
     91 #define ENABLE_MFI_SENSORS
     92 #define ENABLE_PHYSICAL_INPUT_PROFILE
     93 #endif
     94 
     95 #ifdef ENABLE_MFI_RUMBLE
     96 #import <CoreHaptics/CoreHaptics.h>
     97 #endif
     98 
     99 #endif /* SDL_JOYSTICK_MFI */
    100 
    101 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
    102 static const char *accelerometerName = "iOS Accelerometer";
    103 static CMMotionManager *motionManager = nil;
    104 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER */
    105 
    106 static SDL_JoystickDeviceItem *deviceList = NULL;
    107 
    108 static int numjoysticks = 0;
    109 int SDL_AppleTVRemoteOpenedAsJoystick = 0;
    110 
    111 static SDL_JoystickDeviceItem *
    112 GetDeviceForIndex(int device_index)
    113 {
    114     SDL_JoystickDeviceItem *device = deviceList;
    115     int i = 0;
    116 
    117     while (i < device_index) {
    118         if (device == NULL) {
    119             return NULL;
    120         }
    121         device = device->next;
    122         i++;
    123     }
    124 
    125     return device;
    126 }
    127 
    128 #ifdef SDL_JOYSTICK_MFI
    129 static void
    130 IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
    131 {
    132     Uint16 *guid16 = (Uint16 *)device->guid.data;
    133     Uint16 vendor = 0;
    134     Uint16 product = 0;
    135     Uint8 subtype = 0;
    136 
    137     const char *name = NULL;
    138     /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
    139      * struct, and ARC doesn't work with structs. */
    140     device->controller = (__bridge GCController *) CFBridgingRetain(controller);
    141 
    142     if (controller.vendorName) {
    143         name = controller.vendorName.UTF8String;
    144     }
    145 
    146     if (!name) {
    147         name = "MFi Gamepad";
    148     }
    149 
    150     device->name = SDL_CreateJoystickName(0, 0, NULL, name);
    151 
    152     if (controller.extendedGamepad) {
    153         GCExtendedGamepad *gamepad = controller.extendedGamepad;
    154         BOOL is_xbox = [controller.vendorName containsString: @"Xbox"];
    155         BOOL is_ps4 = [controller.vendorName containsString: @"DUALSHOCK"];
    156 #if TARGET_OS_TV
    157         BOOL is_MFi = (!is_xbox && !is_ps4);
    158 #endif
    159         int nbuttons = 0;
    160 
    161         /* These buttons are part of the original MFi spec */
    162         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
    163         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
    164         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
    165         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
    166         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
    167         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
    168         nbuttons += 6;
    169 
    170         /* These buttons are available on some newer controllers */
    171 #pragma clang diagnostic push
    172 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
    173         if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
    174             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK);
    175             ++nbuttons;
    176         }
    177         if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
    178             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK);
    179             ++nbuttons;
    180         }
    181         if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
    182             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK);
    183             ++nbuttons;
    184         }
    185         if ([gamepad respondsToSelector:@selector(buttonHome)] && gamepad.buttonHome) {
    186             device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_GUIDE);
    187             ++nbuttons;
    188         }
    189         BOOL has_direct_menu = [gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu;
    190 #if TARGET_OS_TV
    191         /* On tvOS MFi controller menu button brings you to the home screen */
    192         if (is_MFi) {
    193             has_direct_menu = FALSE;
    194         }
    195 #endif
    196         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
    197         ++nbuttons;
    198         if (!has_direct_menu) {
    199             device->uses_pause_handler = SDL_TRUE;
    200         }
    201 
    202 #ifdef ENABLE_PHYSICAL_INPUT_PROFILE
    203         if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
    204             if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
    205                 device->has_dualshock_touchpad = SDL_TRUE;
    206                 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_MISC1);
    207                 ++nbuttons;
    208             }
    209             if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
    210                 device->has_xbox_paddles = SDL_TRUE;
    211                 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE1);
    212                 ++nbuttons;
    213             }
    214             if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) {
    215                 device->has_xbox_paddles = SDL_TRUE;
    216                 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE2);
    217                 ++nbuttons;
    218             }
    219             if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) {
    220                 device->has_xbox_paddles = SDL_TRUE;
    221                 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE3);
    222                 ++nbuttons;
    223             }
    224             if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) {
    225                 device->has_xbox_paddles = SDL_TRUE;
    226                 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE4);
    227                 ++nbuttons;
    228             }
    229         }
    230 #endif
    231 #pragma clang diagnostic pop
    232 
    233         if (is_xbox) {
    234             vendor = USB_VENDOR_MICROSOFT;
    235             if (device->has_xbox_paddles) {
    236                 /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */
    237                 product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH;
    238                 subtype = 1;
    239             } else {
    240                 /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */
    241                 product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
    242                 subtype = 0;
    243             }
    244         } else if (is_ps4) {
    245             /* Assume DS4 Slim unless/until GCController flows VID/PID */
    246             vendor = USB_VENDOR_SONY;
    247             product = USB_PRODUCT_SONY_DS4_SLIM;
    248             if (device->has_dualshock_touchpad) {
    249                 subtype = 1;
    250             } else {
    251                 subtype = 0;
    252             }
    253         } else {
    254             vendor = USB_VENDOR_APPLE;
    255             product = 1;
    256             subtype = 1;
    257         }
    258 
    259         device->naxes = 6; /* 2 thumbsticks and 2 triggers */
    260         device->nhats = 1; /* d-pad */
    261         device->nbuttons = nbuttons;
    262 
    263     } else if (controller.gamepad) {
    264         int nbuttons = 0;
    265 
    266         /* These buttons are part of the original MFi spec */
    267         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
    268         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
    269         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
    270         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
    271         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
    272         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
    273         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
    274         nbuttons += 7;
    275         device->uses_pause_handler = SDL_TRUE;
    276 
    277         vendor = USB_VENDOR_APPLE;
    278         product = 2;
    279         subtype = 2;
    280         device->naxes = 0; /* no traditional analog inputs */
    281         device->nhats = 1; /* d-pad */
    282         device->nbuttons = nbuttons;
    283     }
    284 #if TARGET_OS_TV
    285     else if (controller.microGamepad) {
    286         int nbuttons = 0;
    287 
    288         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
    289         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */
    290         nbuttons += 2;
    291 
    292         device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
    293         ++nbuttons;
    294         device->uses_pause_handler = SDL_TRUE;
    295 
    296         vendor = USB_VENDOR_APPLE;
    297         product = 3;
    298         subtype = 3;
    299         device->naxes = 2; /* treat the touch surface as two axes */
    300         device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
    301         device->nbuttons = nbuttons;
    302 
    303         controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
    304     }
    305 #endif /* TARGET_OS_TV */
    306 
    307     /* We only need 16 bits for each of these; space them out to fill 128. */
    308     /* Byteswap so devices get same GUID on little/big endian platforms. */
    309     *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
    310     *guid16++ = 0;
    311     *guid16++ = SDL_SwapLE16(vendor);
    312     *guid16++ = 0;
    313     *guid16++ = SDL_SwapLE16(product);
    314     *guid16++ = 0;
    315 
    316     *guid16++ = SDL_SwapLE16(device->button_mask);
    317 
    318     if (vendor == USB_VENDOR_APPLE) {
    319         /* Note that this is an MFI controller and what subtype it is */
    320         device->guid.data[14] = 'm';
    321         device->guid.data[15] = subtype;
    322     } else {
    323         device->guid.data[15] = subtype;
    324     }
    325 
    326     /* This will be set when the first button press of the controller is
    327      * detected. */
    328     controller.playerIndex = -1;
    329 }
    330 #endif /* SDL_JOYSTICK_MFI */
    331 
    332 #if defined(SDL_JOYSTICK_iOS_ACCELEROMETER) || defined(SDL_JOYSTICK_MFI)
    333 static void
    334 IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
    335 {
    336     SDL_JoystickDeviceItem *device = deviceList;
    337 
    338 #if TARGET_OS_TV
    339     if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) {
    340         /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */
    341         if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) {
    342             return;
    343         }
    344     }
    345 #endif
    346 
    347     while (device != NULL) {
    348         if (device->controller == controller) {
    349             return;
    350         }
    351         device = device->next;
    352     }
    353 
    354     device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
    355     if (device == NULL) {
    356         return;
    357     }
    358 
    359     device->accelerometer = accelerometer;
    360     device->instance_id = SDL_GetNextJoystickInstanceID();
    361 
    362     if (accelerometer) {
    363 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
    364         device->name = SDL_strdup(accelerometerName);
    365         device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
    366         device->nhats = 0;
    367         device->nbuttons = 0;
    368 
    369         /* Use the accelerometer name as a GUID. */
    370         SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
    371 #else
    372         SDL_free(device);
    373         return;
    374 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER */
    375     } else if (controller) {
    376 #ifdef SDL_JOYSTICK_MFI
    377         IOS_AddMFIJoystickDevice(device, controller);
    378 #else
    379         SDL_free(device);
    380         return;
    381 #endif /* SDL_JOYSTICK_MFI */
    382     }
    383 
    384     if (deviceList == NULL) {
    385         deviceList = device;
    386     } else {
    387         SDL_JoystickDeviceItem *lastdevice = deviceList;
    388         while (lastdevice->next != NULL) {
    389             lastdevice = lastdevice->next;
    390         }
    391         lastdevice->next = device;
    392     }
    393 
    394     ++numjoysticks;
    395 
    396     SDL_PrivateJoystickAdded(device->instance_id);
    397 }
    398 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER || SDL_JOYSTICK_MFI */
    399 
    400 static SDL_JoystickDeviceItem *
    401 IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
    402 {
    403     SDL_JoystickDeviceItem *prev = NULL;
    404     SDL_JoystickDeviceItem *next = NULL;
    405     SDL_JoystickDeviceItem *item = deviceList;
    406 
    407     if (device == NULL) {
    408         return NULL;
    409     }
    410 
    411     next = device->next;
    412 
    413     while (item != NULL) {
    414         if (item == device) {
    415             break;
    416         }
    417         prev = item;
    418         item = item->next;
    419     }
    420 
    421     /* Unlink the device item from the device list. */
    422     if (prev) {
    423         prev->next = device->next;
    424     } else if (device == deviceList) {
    425         deviceList = device->next;
    426     }
    427 
    428     if (device->joystick) {
    429         device->joystick->hwdata = NULL;
    430     }
    431 
    432 #ifdef SDL_JOYSTICK_MFI
    433     @autoreleasepool {
    434         if (device->controller) {
    435             /* The controller was explicitly retained in the struct, so it
    436              * should be explicitly released before freeing the struct. */
    437             GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
    438             controller.controllerPausedHandler = nil;
    439             device->controller = nil;
    440         }
    441     }
    442 #endif /* SDL_JOYSTICK_MFI */
    443 
    444     --numjoysticks;
    445 
    446     SDL_PrivateJoystickRemoved(device->instance_id);
    447 
    448     SDL_free(device->name);
    449     SDL_free(device);
    450 
    451     return next;
    452 }
    453 
    454 #if TARGET_OS_TV
    455 static void SDLCALL
    456 SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
    457 {
    458     BOOL allowRotation = newValue != NULL && *newValue != '0';
    459 
    460     @autoreleasepool {
    461         for (GCController *controller in [GCController controllers]) {
    462             if (controller.microGamepad) {
    463                 controller.microGamepad.allowsRotation = allowRotation;
    464             }
    465         }
    466     }
    467 }
    468 #endif /* TARGET_OS_TV */
    469 
    470 #if defined(__MACOSX__)
    471 static int is_macos11(void)
    472 {
    473     return (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_15);
    474 }
    475 #endif
    476 
    477 static int
    478 IOS_JoystickInit(void)
    479 {
    480 #if defined(__MACOSX__)
    481     if (!is_macos11()) {
    482         return 0;
    483     }
    484 #endif
    485 
    486     @autoreleasepool {
    487 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
    488         if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
    489             /* Default behavior, accelerometer as joystick */
    490             IOS_AddJoystickDevice(nil, SDL_TRUE);
    491         }
    492 #endif
    493 
    494 #ifdef SDL_JOYSTICK_MFI
    495         /* GameController.framework was added in iOS 7. */
    496         if (![GCController class]) {
    497             return 0;
    498         }
    499 
    500         /* For whatever reason, this always returns an empty array on
    501          macOS 11.0.1 */
    502         for (GCController *controller in [GCController controllers]) {
    503             IOS_AddJoystickDevice(controller, SDL_FALSE);
    504         }
    505 
    506 #if TARGET_OS_TV
    507         SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
    508                             SDL_AppleTVRemoteRotationHintChanged, NULL);
    509 #endif /* TARGET_OS_TV */
    510 
    511         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    512 
    513         connectObserver = [center addObserverForName:GCControllerDidConnectNotification
    514                                               object:nil
    515                                                queue:nil
    516                                           usingBlock:^(NSNotification *note) {
    517                                               GCController *controller = note.object;
    518                                               IOS_AddJoystickDevice(controller, SDL_FALSE);
    519                                           }];
    520 
    521         disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
    522                                                  object:nil
    523                                                   queue:nil
    524                                              usingBlock:^(NSNotification *note) {
    525                                                  GCController *controller = note.object;
    526                                                  SDL_JoystickDeviceItem *device = deviceList;
    527                                                  while (device != NULL) {
    528                                                      if (device->controller == controller) {
    529                                                          IOS_RemoveJoystickDevice(device);
    530                                                          break;
    531                                                      }
    532                                                      device = device->next;
    533                                                  }
    534                                              }];
    535 #endif /* SDL_JOYSTICK_MFI */
    536     }
    537 
    538     return 0;
    539 }
    540 
    541 static int
    542 IOS_JoystickGetCount(void)
    543 {
    544     return numjoysticks;
    545 }
    546 
    547 static void
    548 IOS_JoystickDetect(void)
    549 {
    550 }
    551 
    552 static const char *
    553 IOS_JoystickGetDeviceName(int device_index)
    554 {
    555     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
    556     return device ? device->name : "Unknown";
    557 }
    558 
    559 static int
    560 IOS_JoystickGetDevicePlayerIndex(int device_index)
    561 {
    562 #ifdef SDL_JOYSTICK_MFI
    563     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
    564     if (device && device->controller) {
    565         return (int)device->controller.playerIndex;
    566     }
    567 #endif
    568     return -1;
    569 }
    570 
    571 static void
    572 IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
    573 {
    574 #ifdef SDL_JOYSTICK_MFI
    575     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
    576     if (device && device->controller) {
    577         device->controller.playerIndex = player_index;
    578     }
    579 #endif
    580 }
    581 
    582 static SDL_JoystickGUID
    583 IOS_JoystickGetDeviceGUID( int device_index )
    584 {
    585     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
    586     SDL_JoystickGUID guid;
    587     if (device) {
    588         guid = device->guid;
    589     } else {
    590         SDL_zero(guid);
    591     }
    592     return guid;
    593 }
    594 
    595 static SDL_JoystickID
    596 IOS_JoystickGetDeviceInstanceID(int device_index)
    597 {
    598     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
    599     return device ? device->instance_id : -1;
    600 }
    601 
    602 static int
    603 IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
    604 {
    605     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
    606     if (device == NULL) {
    607         return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
    608     }
    609 
    610     joystick->hwdata = device;
    611     joystick->instance_id = device->instance_id;
    612 
    613     joystick->naxes = device->naxes;
    614     joystick->nhats = device->nhats;
    615     joystick->nbuttons = device->nbuttons;
    616     joystick->nballs = 0;
    617 
    618     if (device->has_dualshock_touchpad) {
    619         SDL_PrivateJoystickAddTouchpad(joystick, 2);
    620     }
    621 
    622     device->joystick = joystick;
    623 
    624     @autoreleasepool {
    625         if (device->accelerometer) {
    626 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
    627             if (motionManager == nil) {
    628                 motionManager = [[CMMotionManager alloc] init];
    629             }
    630 
    631             /* Shorter times between updates can significantly increase CPU usage. */
    632             motionManager.accelerometerUpdateInterval = 0.1;
    633             [motionManager startAccelerometerUpdates];
    634 #endif
    635         } else {
    636 #ifdef SDL_JOYSTICK_MFI
    637             if (device->uses_pause_handler) {
    638                 GCController *controller = device->controller;
    639                 controller.controllerPausedHandler = ^(GCController *c) {
    640                     if (joystick->hwdata) {
    641                         ++joystick->hwdata->num_pause_presses;
    642                     }
    643                 };
    644             }
    645 
    646 #ifdef ENABLE_MFI_SENSORS
    647             if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
    648                 GCController *controller = joystick->hwdata->controller;
    649                 GCMotion *motion = controller.motion;
    650                 if (motion && motion.hasRotationRate) {
    651                     SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO);
    652                 }
    653                 if (motion && motion.hasGravityAndUserAcceleration) {
    654                     SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL);
    655                 }
    656             }
    657 #endif /* ENABLE_MFI_SENSORS */
    658 
    659 #endif /* SDL_JOYSTICK_MFI */
    660         }
    661     }
    662     if (device->remote) {
    663         ++SDL_AppleTVRemoteOpenedAsJoystick;
    664     }
    665 
    666     return 0;
    667 }
    668 
    669 static void
    670 IOS_AccelerometerUpdate(SDL_Joystick *joystick)
    671 {
    672 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
    673     const float maxgforce = SDL_IPHONE_MAX_GFORCE;
    674     const SInt16 maxsint16 = 0x7FFF;
    675     CMAcceleration accel;
    676 
    677     @autoreleasepool {
    678         if (!motionManager.isAccelerometerActive) {
    679             return;
    680         }
    681 
    682         accel = motionManager.accelerometerData.acceleration;
    683     }
    684 
    685     /*
    686      Convert accelerometer data from floating point to Sint16, which is what
    687      the joystick system expects.
    688 
    689      To do the conversion, the data is first clamped onto the interval
    690      [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
    691      by MAX_SINT16 so that it is mapped to the full range of an Sint16.
    692 
    693      You can customize the clamped range of this function by modifying the
    694      SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
    695 
    696      Once converted to Sint16, the accelerometer data no longer has coherent
    697      units. You can convert the data back to units of g-force by multiplying
    698      it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
    699      */
    700 
    701     /* clamp the data */
    702     accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
    703     accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
    704     accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
    705 
    706     /* pass in data mapped to range of SInt16 */
    707     SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
    708     SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
    709     SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
    710 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER */
    711 }
    712 
    713 #ifdef SDL_JOYSTICK_MFI
    714 static Uint8
    715 IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
    716 {
    717     Uint8 hat = 0;
    718 
    719     if (dpad.up.isPressed) {
    720         hat |= SDL_HAT_UP;
    721     } else if (dpad.down.isPressed) {
    722         hat |= SDL_HAT_DOWN;
    723     }
    724 
    725     if (dpad.left.isPressed) {
    726         hat |= SDL_HAT_LEFT;
    727     } else if (dpad.right.isPressed) {
    728         hat |= SDL_HAT_RIGHT;
    729     }
    730 
    731     if (hat == 0) {
    732         return SDL_HAT_CENTERED;
    733     }
    734 
    735     return hat;
    736 }
    737 #endif
    738 
    739 static void
    740 IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
    741 {
    742 #if SDL_JOYSTICK_MFI
    743     @autoreleasepool {
    744         GCController *controller = joystick->hwdata->controller;
    745         Uint8 hatstate = SDL_HAT_CENTERED;
    746         int i;
    747         int pause_button_index = 0;
    748 
    749         if (controller.extendedGamepad) {
    750             GCExtendedGamepad *gamepad = controller.extendedGamepad;
    751 
    752             /* Axis order matches the XInput Windows mappings. */
    753             Sint16 axes[] = {
    754                 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
    755                 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
    756                 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
    757                 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
    758                 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
    759                 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
    760             };
    761 
    762             /* Button order matches the XInput Windows mappings. */
    763             Uint8 buttons[joystick->nbuttons];
    764             int button_count = 0;
    765 
    766             /* These buttons are part of the original MFi spec */
    767             buttons[button_count++] = gamepad.buttonA.isPressed;
    768             buttons[button_count++] = gamepad.buttonB.isPressed;
    769             buttons[button_count++] = gamepad.buttonX.isPressed;
    770             buttons[button_count++] = gamepad.buttonY.isPressed;
    771             buttons[button_count++] = gamepad.leftShoulder.isPressed;
    772             buttons[button_count++] = gamepad.rightShoulder.isPressed;
    773 
    774             /* These buttons are available on some newer controllers */
    775 #pragma clang diagnostic push
    776 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
    777             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) {
    778                 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
    779             }
    780             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) {
    781                 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
    782             }
    783             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) {
    784                 buttons[button_count++] = gamepad.buttonOptions.isPressed;
    785             }
    786             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_GUIDE)) {
    787                 buttons[button_count++] = gamepad.buttonHome.isPressed;
    788             }
    789             /* This must be the last button, so we can optionally handle it with pause_button_index below */
    790             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
    791                 if (joystick->hwdata->uses_pause_handler) {
    792                     pause_button_index = button_count;
    793                     buttons[button_count++] = joystick->delayed_guide_button;
    794                 } else {
    795                     buttons[button_count++] = gamepad.buttonMenu.isPressed;
    796                 }
    797             }
    798 
    799 #ifdef ENABLE_PHYSICAL_INPUT_PROFILE
    800             if (joystick->hwdata->has_dualshock_touchpad) {
    801                 buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed;
    802 
    803                 GCControllerDirectionPad *dpad;
    804 
    805                 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne];
    806                 if (dpad.xAxis.value || dpad.yAxis.value) {
    807                     SDL_PrivateJoystickTouchpad(joystick, 0, 0, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
    808                 } else {
    809                     SDL_PrivateJoystickTouchpad(joystick, 0, 0, SDL_RELEASED, 0.0f, 0.0f, 1.0f);
    810                 }
    811 
    812                 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo];
    813                 if (dpad.xAxis.value || dpad.yAxis.value) {
    814                     SDL_PrivateJoystickTouchpad(joystick, 0, 1, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
    815                 } else {
    816                     SDL_PrivateJoystickTouchpad(joystick, 0, 1, SDL_RELEASED, 0.0f, 0.0f, 1.0f);
    817                 }
    818             }
    819 
    820             if (joystick->hwdata->has_xbox_paddles) {
    821                 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE1)) {
    822                     buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed;
    823                 }
    824                 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE2)) {
    825                     buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed;
    826                 }
    827                 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE3)) {
    828                     buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed;
    829                 }
    830                 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE4)) {
    831                     buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed;
    832                 }
    833 
    834                 /*
    835                 SDL_Log("Paddles: [%d,%d,%d,%d]",
    836                     controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed,
    837                     controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed,
    838                     controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed,
    839                     controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed);
    840                 */
    841             }
    842 #endif
    843 #pragma clang diagnostic pop
    844 
    845             hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
    846 
    847             for (i = 0; i < SDL_arraysize(axes); i++) {
    848                 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
    849             }
    850 
    851             for (i = 0; i < button_count; i++) {
    852                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
    853             }
    854 
    855 #ifdef ENABLE_MFI_SENSORS
    856             if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
    857                 GCMotion *motion = controller.motion;
    858                 if (motion && motion.sensorsActive) {
    859                     float data[3];
    860 
    861                     if (motion.hasRotationRate) {
    862                         GCRotationRate rate = motion.rotationRate;
    863                         data[0] = rate.x;
    864                         data[1] = rate.z;
    865                         data[2] = -rate.y;
    866                         SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3);
    867                     }
    868                     if (motion.hasGravityAndUserAcceleration) {
    869                         GCAcceleration accel = motion.acceleration;
    870                         data[0] = -accel.x * SDL_STANDARD_GRAVITY;
    871                         data[1] = -accel.y * SDL_STANDARD_GRAVITY;
    872                         data[2] = -accel.z * SDL_STANDARD_GRAVITY;
    873                         SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3);
    874                     }
    875                 }
    876             }
    877 #endif /* ENABLE_MFI_SENSORS */
    878 
    879         } else if (controller.gamepad) {
    880             GCGamepad *gamepad = controller.gamepad;
    881 
    882             /* Button order matches the XInput Windows mappings. */
    883             Uint8 buttons[joystick->nbuttons];
    884             int button_count = 0;
    885             buttons[button_count++] = gamepad.buttonA.isPressed;
    886             buttons[button_count++] = gamepad.buttonB.isPressed;
    887             buttons[button_count++] = gamepad.buttonX.isPressed;
    888             buttons[button_count++] = gamepad.buttonY.isPressed;
    889             buttons[button_count++] = gamepad.leftShoulder.isPressed;
    890             buttons[button_count++] = gamepad.rightShoulder.isPressed;
    891             pause_button_index = button_count;
    892             buttons[button_count++] = joystick->delayed_guide_button;
    893 
    894             hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
    895 
    896             for (i = 0; i < button_count; i++) {
    897                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
    898             }
    899         }
    900 #if TARGET_OS_TV
    901         else if (controller.microGamepad) {
    902             GCMicroGamepad *gamepad = controller.microGamepad;
    903 
    904             Sint16 axes[] = {
    905                 (Sint16) (gamepad.dpad.xAxis.value * 32767),
    906                 (Sint16) (gamepad.dpad.yAxis.value * -32767),
    907             };
    908 
    909             for (i = 0; i < SDL_arraysize(axes); i++) {
    910                 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
    911             }
    912 
    913             Uint8 buttons[joystick->nbuttons];
    914             int button_count = 0;
    915             buttons[button_count++] = gamepad.buttonA.isPressed;
    916             buttons[button_count++] = gamepad.buttonX.isPressed;
    917 #pragma clang diagnostic push
    918 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
    919             /* This must be the last button, so we can optionally handle it with pause_button_index below */
    920             if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
    921                 if (joystick->hwdata->uses_pause_handler) {
    922                     pause_button_index = button_count;
    923                     buttons[button_count++] = joystick->delayed_guide_button;
    924                 } else {
    925                     buttons[button_count++] = gamepad.buttonMenu.isPressed;
    926                 }
    927             }
    928 #pragma clang diagnostic pop
    929 
    930             for (i = 0; i < button_count; i++) {
    931                 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
    932             }
    933         }
    934 #endif /* TARGET_OS_TV */
    935 
    936         if (joystick->nhats > 0) {
    937             SDL_PrivateJoystickHat(joystick, 0, hatstate);
    938         }
    939 
    940         if (joystick->hwdata->uses_pause_handler) {
    941             for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
    942                 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED);
    943                 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED);
    944             }
    945             joystick->hwdata->num_pause_presses = 0;
    946         }
    947 
    948 #ifdef ENABLE_MFI_BATTERY
    949         if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) {
    950             GCDeviceBattery *battery = controller.battery;
    951             if (battery) {
    952                 SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN;
    953 
    954                 switch (battery.batteryState) {
    955                 case GCDeviceBatteryStateDischarging:
    956                     {
    957                         float power_level = battery.batteryLevel;
    958                         if (power_level <= 0.05f) {
    959                             ePowerLevel = SDL_JOYSTICK_POWER_EMPTY;
    960                         } else if (power_level <= 0.20f) {
    961                             ePowerLevel = SDL_JOYSTICK_POWER_LOW;
    962                         } else if (power_level <= 0.70f) {
    963                             ePowerLevel = SDL_JOYSTICK_POWER_MEDIUM;
    964                         } else {
    965                             ePowerLevel = SDL_JOYSTICK_POWER_FULL;
    966                         }
    967                     }
    968                     break;
    969                 case GCDeviceBatteryStateCharging:
    970                     ePowerLevel = SDL_JOYSTICK_POWER_WIRED;
    971                     break;
    972                 case GCDeviceBatteryStateFull:
    973                     ePowerLevel = SDL_JOYSTICK_POWER_FULL;
    974                     break;
    975                 default:
    976                     break;
    977                 }
    978 
    979                 SDL_PrivateJoystickBatteryLevel(joystick, ePowerLevel);
    980             }
    981         }
    982 #endif /* ENABLE_MFI_BATTERY */
    983     }
    984 #endif /* SDL_JOYSTICK_MFI */
    985 }
    986 
    987 #ifdef ENABLE_MFI_RUMBLE
    988 
    989 @interface SDL_RumbleMotor : NSObject
    990 @end
    991 
    992 @implementation SDL_RumbleMotor {
    993     CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
    994     id<CHHapticPatternPlayer> player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
    995     bool active;
    996 }
    997 
    998 -(void)cleanup
    999 {
   1000     if (self->player != nil) {
   1001         [self->player cancelAndReturnError:nil];
   1002         self->player = nil;
   1003     }
   1004     if (self->engine != nil) {
   1005         [self->engine stopWithCompletionHandler:nil];
   1006         self->engine = nil;
   1007     }
   1008 }
   1009 
   1010 -(int)setIntensity:(float)intensity
   1011 {
   1012     @autoreleasepool {
   1013         if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) {
   1014             NSError *error;
   1015 
   1016             if (self->engine == nil) {
   1017                 return SDL_SetError("Haptics engine was stopped");
   1018             }
   1019 
   1020             if (intensity == 0.0f) {
   1021                 if (self->player && self->active) {
   1022                     [self->player stopAtTime:0 error:&error];
   1023                 }
   1024                 self->active = false;
   1025                 return 0;
   1026             }
   1027 
   1028             if (self->player == nil) {
   1029                 CHHapticEventParameter *param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
   1030                 CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:param, nil] relativeTime:0 duration:GCHapticDurationInfinite];
   1031                 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error];
   1032                 if (error != nil) {
   1033                     return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]);
   1034                 }
   1035 
   1036                 self->player = [self->engine createPlayerWithPattern:pattern error:&error];
   1037                 if (error != nil) {
   1038                     return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]);
   1039                 }
   1040                 self->active = false;
   1041             }
   1042 
   1043             CHHapticDynamicParameter *param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0];
   1044             [self->player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error];
   1045             if (error != nil) {
   1046                 return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]);
   1047             }
   1048 
   1049             if (!self->active) {
   1050                 [self->player startAtTime:0 error:&error];
   1051                 self->active = true;
   1052             }
   1053         }
   1054 
   1055         return 0;
   1056     }
   1057 }
   1058 
   1059 -(id) initWithController:(GCController*)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
   1060 {
   1061     @autoreleasepool {
   1062         self = [super init];
   1063         NSError *error;
   1064 
   1065         self->engine = [controller.haptics createEngineWithLocality:locality];
   1066         if (self->engine == nil) {
   1067             SDL_SetError("Couldn't create haptics engine");
   1068             return nil;
   1069         }
   1070 
   1071         [self->engine startAndReturnError:&error];
   1072         if (error != nil) {
   1073             SDL_SetError("Couldn't start haptics engine");
   1074             return nil;
   1075         }
   1076 
   1077         __weak typeof(self) weakSelf = self;
   1078         self->engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
   1079             SDL_RumbleMotor *_this = weakSelf;
   1080             if (_this == nil) {
   1081                 return;
   1082             }
   1083 
   1084             _this->player = nil;
   1085             _this->engine = nil;
   1086         };
   1087         self->engine.resetHandler = ^{
   1088             SDL_RumbleMotor *_this = weakSelf;
   1089             if (_this == nil) {
   1090                 return;
   1091             }
   1092 
   1093             _this->player = nil;
   1094             [_this->engine startAndReturnError:nil];
   1095         };
   1096 
   1097         return self;
   1098     }
   1099 }
   1100 
   1101 @end
   1102 
   1103 @interface SDL_RumbleContext : NSObject
   1104 @end
   1105 
   1106 @implementation SDL_RumbleContext {
   1107     SDL_RumbleMotor *m_low_frequency_motor;
   1108     SDL_RumbleMotor *m_high_frequency_motor;
   1109     SDL_RumbleMotor *m_left_trigger_motor;
   1110     SDL_RumbleMotor *m_right_trigger_motor;
   1111 }
   1112 
   1113 -(id) initWithLowFrequencyMotor:(SDL_RumbleMotor*)low_frequency_motor
   1114              HighFrequencyMotor:(SDL_RumbleMotor*)high_frequency_motor
   1115                LeftTriggerMotor:(SDL_RumbleMotor*)left_trigger_motor
   1116               RightTriggerMotor:(SDL_RumbleMotor*)right_trigger_motor
   1117 {
   1118     self = [super init];
   1119     self->m_low_frequency_motor = low_frequency_motor;
   1120     self->m_high_frequency_motor = high_frequency_motor;
   1121     self->m_left_trigger_motor = left_trigger_motor;
   1122     self->m_right_trigger_motor = right_trigger_motor;
   1123     return self;
   1124 }
   1125 
   1126 -(int) rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble
   1127 {
   1128     int result = 0;
   1129 
   1130     result += [self->m_low_frequency_motor setIntensity:((float)low_frequency_rumble / 65535.0f)];
   1131     result += [self->m_high_frequency_motor setIntensity:((float)high_frequency_rumble / 65535.0f)];
   1132     return ((result < 0) ? -1 : 0);
   1133 }
   1134 
   1135 -(int) rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble
   1136 {
   1137     int result = 0;
   1138 
   1139     if (self->m_left_trigger_motor && self->m_right_trigger_motor) {
   1140         result += [self->m_left_trigger_motor setIntensity:((float)left_rumble / 65535.0f)];
   1141         result += [self->m_right_trigger_motor setIntensity:((float)right_rumble / 65535.0f)];
   1142     } else {
   1143         result = SDL_Unsupported();
   1144     }
   1145     return ((result < 0) ? -1 : 0);
   1146 }
   1147 
   1148 -(void)cleanup
   1149 {
   1150     [self->m_low_frequency_motor cleanup];
   1151     [self->m_high_frequency_motor cleanup];
   1152 }
   1153 
   1154 @end
   1155 
   1156 static SDL_RumbleContext *IOS_JoystickInitRumble(GCController *controller)
   1157 {
   1158     @autoreleasepool {
   1159         if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
   1160             SDL_RumbleMotor *low_frequency_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
   1161             SDL_RumbleMotor *high_frequency_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
   1162             SDL_RumbleMotor *left_trigger_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger];
   1163             SDL_RumbleMotor *right_trigger_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger];
   1164             if (low_frequency_motor && high_frequency_motor) {
   1165                 return [[SDL_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor
   1166                                                          HighFrequencyMotor:high_frequency_motor
   1167                                                            LeftTriggerMotor:left_trigger_motor
   1168                                                           RightTriggerMotor:right_trigger_motor];
   1169             }
   1170         }
   1171     }
   1172     return nil;
   1173 }
   1174 
   1175 #endif /* ENABLE_MFI_RUMBLE */
   1176 
   1177 static int
   1178 IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
   1179 {
   1180 #ifdef ENABLE_MFI_RUMBLE
   1181     SDL_JoystickDeviceItem *device = joystick->hwdata;
   1182 
   1183     if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
   1184         if (!device->rumble && device->controller && device->controller.haptics) {
   1185             SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
   1186             if (rumble) {
   1187                 device->rumble = (void *)CFBridgingRetain(rumble);
   1188             }
   1189         }
   1190     }
   1191 
   1192     if (device->rumble) {
   1193         SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
   1194         return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble];
   1195     } else {
   1196         return SDL_Unsupported();
   1197     }
   1198 #else
   1199     return SDL_Unsupported();
   1200 #endif
   1201 }
   1202 
   1203 static int
   1204 IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
   1205 {
   1206 #ifdef ENABLE_MFI_RUMBLE
   1207     SDL_JoystickDeviceItem *device = joystick->hwdata;
   1208 
   1209     if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
   1210         if (!device->rumble && device->controller && device->controller.haptics) {
   1211             SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
   1212             if (rumble) {
   1213                 device->rumble = (void *)CFBridgingRetain(rumble);
   1214             }
   1215         }
   1216     }
   1217 
   1218     if (device->rumble) {
   1219         SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
   1220         return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble];
   1221     } else {
   1222         return SDL_Unsupported();
   1223     }
   1224 #else
   1225     return SDL_Unsupported();
   1226 #endif
   1227 }
   1228 
   1229 static SDL_bool
   1230 IOS_JoystickHasLED(SDL_Joystick *joystick)
   1231 {
   1232 #ifdef ENABLE_MFI_LIGHT
   1233     @autoreleasepool {
   1234         if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) {
   1235             GCController *controller = joystick->hwdata->controller;
   1236             GCDeviceLight *light = controller.light;
   1237             if (light) {
   1238                 return SDL_TRUE;
   1239             }
   1240         }
   1241     }
   1242 #endif /* ENABLE_MFI_LIGHT */
   1243 
   1244     return SDL_FALSE;
   1245 }
   1246 
   1247 static int
   1248 IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
   1249 {
   1250 #ifdef ENABLE_MFI_LIGHT
   1251     @autoreleasepool {
   1252         if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) {
   1253             GCController *controller = joystick->hwdata->controller;
   1254             GCDeviceLight *light = controller.light;
   1255             if (light) {
   1256                 light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f
   1257                                                      green:(float)green / 255.0f
   1258                                                       blue:(float)blue / 255.0f];
   1259                 return 0;
   1260             }
   1261         }
   1262     }
   1263 #endif /* ENABLE_MFI_LIGHT */
   1264 
   1265     return SDL_Unsupported();
   1266 }
   1267 
   1268 static int
   1269 IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
   1270 {
   1271 #ifdef ENABLE_MFI_SENSORS
   1272     @autoreleasepool {
   1273         if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
   1274             GCController *controller = joystick->hwdata->controller;
   1275             GCMotion *motion = controller.motion;
   1276             if (motion) {
   1277                 motion.sensorsActive = enabled ? YES : NO;
   1278                 return 0;
   1279             }
   1280         }
   1281     }
   1282 #endif /* ENABLE_MFI_SENSORS */
   1283 
   1284     return SDL_Unsupported();
   1285 }
   1286 
   1287 static void
   1288 IOS_JoystickUpdate(SDL_Joystick *joystick)
   1289 {
   1290     SDL_JoystickDeviceItem *device = joystick->hwdata;
   1291 
   1292     if (device == NULL) {
   1293         return;
   1294     }
   1295 
   1296     if (device->accelerometer) {
   1297         IOS_AccelerometerUpdate(joystick);
   1298     } else if (device->controller) {
   1299         IOS_MFIJoystickUpdate(joystick);
   1300     }
   1301 }
   1302 
   1303 static void
   1304 IOS_JoystickClose(SDL_Joystick *joystick)
   1305 {
   1306     SDL_JoystickDeviceItem *device = joystick->hwdata;
   1307 
   1308     if (device == NULL) {
   1309         return;
   1310     }
   1311 
   1312     device->joystick = NULL;
   1313 
   1314     @autoreleasepool {
   1315 #ifdef ENABLE_MFI_RUMBLE
   1316         if (device->rumble) {
   1317             SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
   1318 
   1319             [rumble cleanup];
   1320             CFRelease(device->rumble);
   1321             device->rumble = NULL;
   1322         }
   1323 #endif /* ENABLE_MFI_RUMBLE */
   1324 
   1325         if (device->accelerometer) {
   1326 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
   1327             [motionManager stopAccelerometerUpdates];
   1328 #endif
   1329         } else if (device->controller) {
   1330 #ifdef SDL_JOYSTICK_MFI
   1331             GCController *controller = device->controller;
   1332             controller.controllerPausedHandler = nil;
   1333             controller.playerIndex = -1;
   1334 #endif
   1335         }
   1336     }
   1337     if (device->remote) {
   1338         --SDL_AppleTVRemoteOpenedAsJoystick;
   1339     }
   1340 }
   1341 
   1342 static void
   1343 IOS_JoystickQuit(void)
   1344 {
   1345     @autoreleasepool {
   1346 #ifdef SDL_JOYSTICK_MFI
   1347         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   1348 
   1349         if (connectObserver) {
   1350             [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
   1351             connectObserver = nil;
   1352         }
   1353 
   1354         if (disconnectObserver) {
   1355             [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
   1356             disconnectObserver = nil;
   1357         }
   1358 
   1359 #if TARGET_OS_TV
   1360         SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
   1361                             SDL_AppleTVRemoteRotationHintChanged, NULL);
   1362 #endif /* TARGET_OS_TV */
   1363 #endif /* SDL_JOYSTICK_MFI */
   1364 
   1365         while (deviceList != NULL) {
   1366             IOS_RemoveJoystickDevice(deviceList);
   1367         }
   1368 
   1369 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER
   1370         motionManager = nil;
   1371 #endif
   1372     }
   1373 
   1374     numjoysticks = 0;
   1375 }
   1376 
   1377 static SDL_bool
   1378 IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
   1379 {
   1380     return SDL_FALSE;
   1381 }
   1382 
   1383 #if defined(SDL_JOYSTICK_MFI) && defined(__MACOSX__)
   1384 SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device)
   1385 {
   1386     if (is_macos11()) {
   1387         return [GCController supportsHIDDevice:device] ? SDL_TRUE : SDL_FALSE;
   1388     }
   1389     return SDL_FALSE;
   1390 }
   1391 #endif
   1392 
   1393 SDL_JoystickDriver SDL_IOS_JoystickDriver =
   1394 {
   1395     IOS_JoystickInit,
   1396     IOS_JoystickGetCount,
   1397     IOS_JoystickDetect,
   1398     IOS_JoystickGetDeviceName,
   1399     IOS_JoystickGetDevicePlayerIndex,
   1400     IOS_JoystickSetDevicePlayerIndex,
   1401     IOS_JoystickGetDeviceGUID,
   1402     IOS_JoystickGetDeviceInstanceID,
   1403     IOS_JoystickOpen,
   1404     IOS_JoystickRumble,
   1405     IOS_JoystickRumbleTriggers,
   1406     IOS_JoystickHasLED,
   1407     IOS_JoystickSetLED,
   1408     IOS_JoystickSetSensorsEnabled,
   1409     IOS_JoystickUpdate,
   1410     IOS_JoystickClose,
   1411     IOS_JoystickQuit,
   1412     IOS_JoystickGetGamepadMapping
   1413 };
   1414 
   1415 /* vi: set ts=4 sw=4 expandtab: */