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
49static id connectObserver = nil;
50static 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
77static const char *accelerometerName = "iOS Accelerometer";
78static CMMotionManager *motionManager = nil;
79#endif /* !TARGET_OS_TV */
80
81static SDL_JoystickDeviceItem *deviceList = NULL;
82
83static int numjoysticks = 0;
84int SDL_AppleTVRemoteOpenedAsJoystick = 0;
85
86static SDL_JoystickDeviceItem *
87GetDeviceForIndex(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
103static void
104IOS_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
261static void
262IOS_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
322static SDL_JoystickDeviceItem *
323IOS_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
377static void SDLCALL
378SDL_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
392static int
393IOS_JoystickInit(void)
394{
395    @autoreleasepool {
396#if !TARGET_OS_TV
397        if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
398            /* Default behavior, accelerometer as joystick */
399            IOS_AddJoystickDevice(nil, SDL_TRUE);
400        }
401#endif /* !TARGET_OS_TV */
402
403#ifdef SDL_JOYSTICK_MFI
404        /* GameController.framework was added in iOS 7. */
405        if (![GCController class]) {
406            return 0;
407        }
408
409        for (GCController *controller in [GCController controllers]) {
410            IOS_AddJoystickDevice(controller, SDL_FALSE);
411        }
412
413#if TARGET_OS_TV
414        SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
415                            SDL_AppleTVRemoteRotationHintChanged, NULL);
416#endif /* TARGET_OS_TV */
417
418        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
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
448static int
449IOS_JoystickGetCount(void)
450{
451    return numjoysticks;
452}
453
454static void
455IOS_JoystickDetect(void)
456{
457}
458
459static const char *
460IOS_JoystickGetDeviceName(int device_index)
461{
462    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
463    return device ? device->name : "Unknown";
464}
465
466static int
467IOS_JoystickGetDevicePlayerIndex(int device_index)
468{
469#ifdef SDL_JOYSTICK_MFI
470    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
471    if (device && device->controller) {
472        return (int)device->controller.playerIndex;
473    }
474#endif
475    return -1;
476}
477
478static void
479IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
480{
481#ifdef SDL_JOYSTICK_MFI
482    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
483    if (device && device->controller) {
484        device->controller.playerIndex = player_index;
485    }
486#endif
487}
488
489static SDL_JoystickGUID
490IOS_JoystickGetDeviceGUID( int device_index )
491{
492    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
493    SDL_JoystickGUID guid;
494    if (device) {
495        guid = device->guid;
496    } else {
497        SDL_zero(guid);
498    }
499    return guid;
500}
501
502static SDL_JoystickID
503IOS_JoystickGetDeviceInstanceID(int device_index)
504{
505    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
506    return device ? device->instance_id : -1;
507}
508
509static int
510IOS_JoystickOpen(SDL_Joystick * joystick, int device_index)
511{
512    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
513    if (device == NULL) {
514        return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
515    }
516
517    joystick->hwdata = device;
518    joystick->instance_id = device->instance_id;
519
520    joystick->naxes = device->naxes;
521    joystick->nhats = device->nhats;
522    joystick->nbuttons = device->nbuttons;
523    joystick->nballs = 0;
524
525    device->joystick = joystick;
526
527    @autoreleasepool {
528        if (device->accelerometer) {
529#if !TARGET_OS_TV
530            if (motionManager == nil) {
531                motionManager = [[CMMotionManager alloc] init];
532            }
533
534            /* Shorter times between updates can significantly increase CPU usage. */
535            motionManager.accelerometerUpdateInterval = 0.1;
536            [motionManager startAccelerometerUpdates];
537#endif /* !TARGET_OS_TV */
538        } else {
539#ifdef SDL_JOYSTICK_MFI
540            if (device->uses_pause_handler) {
541                GCController *controller = device->controller;
542                controller.controllerPausedHandler = ^(GCController *c) {
543                    if (joystick->hwdata) {
544                        ++joystick->hwdata->num_pause_presses;
545                    }
546                };
547            }
548#endif /* SDL_JOYSTICK_MFI */
549        }
550    }
551    if (device->remote) {
552        ++SDL_AppleTVRemoteOpenedAsJoystick;
553    }
554
555    return 0;
556}
557
558static void
559IOS_AccelerometerUpdate(SDL_Joystick * joystick)
560{
561#if !TARGET_OS_TV
562    const float maxgforce = SDL_IPHONE_MAX_GFORCE;
563    const SInt16 maxsint16 = 0x7FFF;
564    CMAcceleration accel;
565
566    @autoreleasepool {
567        if (!motionManager.isAccelerometerActive) {
568            return;
569        }
570
571        accel = motionManager.accelerometerData.acceleration;
572    }
573
574    /*
575     Convert accelerometer data from floating point to Sint16, which is what
576     the joystick system expects.
577
578     To do the conversion, the data is first clamped onto the interval
579     [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
580     by MAX_SINT16 so that it is mapped to the full range of an Sint16.
581
582     You can customize the clamped range of this function by modifying the
583     SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
584
585     Once converted to Sint16, the accelerometer data no longer has coherent
586     units. You can convert the data back to units of g-force by multiplying
587     it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
588     */
589
590    /* clamp the data */
591    accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
592    accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
593    accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
594
595    /* pass in data mapped to range of SInt16 */
596    SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
597    SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
598    SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
599#endif /* !TARGET_OS_TV */
600}
601
602#ifdef SDL_JOYSTICK_MFI
603static Uint8
604IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
605{
606    Uint8 hat = 0;
607
608    if (dpad.up.isPressed) {
609        hat |= SDL_HAT_UP;
610    } else if (dpad.down.isPressed) {
611        hat |= SDL_HAT_DOWN;
612    }
613
614    if (dpad.left.isPressed) {
615        hat |= SDL_HAT_LEFT;
616    } else if (dpad.right.isPressed) {
617        hat |= SDL_HAT_RIGHT;
618    }
619
620    if (hat == 0) {
621        return SDL_HAT_CENTERED;
622    }
623
624    return hat;
625}
626#endif
627
628static void
629IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
630{
631#if SDL_JOYSTICK_MFI
632    @autoreleasepool {
633        GCController *controller = joystick->hwdata->controller;
634        Uint8 hatstate = SDL_HAT_CENTERED;
635        int i;
636        int pause_button_index = 0;
637
638        if (controller.extendedGamepad) {
639            GCExtendedGamepad *gamepad = controller.extendedGamepad;
640
641            /* Axis order matches the XInput Windows mappings. */
642            Sint16 axes[] = {
643                (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
644                (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
645                (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
646                (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
647                (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
648                (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
649            };
650
651            /* Button order matches the XInput Windows mappings. */
652            Uint8 buttons[joystick->nbuttons];
653            int button_count = 0;
654
655            /* These buttons are part of the original MFi spec */
656            buttons[button_count++] = gamepad.buttonA.isPressed;
657            buttons[button_count++] = gamepad.buttonB.isPressed;
658            buttons[button_count++] = gamepad.buttonX.isPressed;
659            buttons[button_count++] = gamepad.buttonY.isPressed;
660            buttons[button_count++] = gamepad.leftShoulder.isPressed;
661            buttons[button_count++] = gamepad.rightShoulder.isPressed;
662
663            /* These buttons are available on some newer controllers */
664#pragma clang diagnostic push
665#pragma clang diagnostic ignored "-Wunguarded-availability-new"
666            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) {
667                buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
668            }
669            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) {
670                buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
671            }
672            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) {
673                buttons[button_count++] = gamepad.buttonOptions.isPressed;
674            }
675            /* This must be the last button, so we can optionally handle it with pause_button_index below */
676            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
677                if (joystick->hwdata->uses_pause_handler) {
678                    pause_button_index = button_count;
679                    buttons[button_count++] = joystick->delayed_guide_button;
680                } else {
681                    buttons[button_count++] = gamepad.buttonMenu.isPressed;
682                }
683            }
684#pragma clang diagnostic pop
685
686            hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
687
688            for (i = 0; i < SDL_arraysize(axes); i++) {
689                SDL_PrivateJoystickAxis(joystick, i, axes[i]);
690            }
691
692            for (i = 0; i < button_count; i++) {
693                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
694            }
695        } else if (controller.gamepad) {
696            GCGamepad *gamepad = controller.gamepad;
697
698            /* Button order matches the XInput Windows mappings. */
699            Uint8 buttons[joystick->nbuttons];
700            int button_count = 0;
701            buttons[button_count++] = gamepad.buttonA.isPressed;
702            buttons[button_count++] = gamepad.buttonB.isPressed;
703            buttons[button_count++] = gamepad.buttonX.isPressed;
704            buttons[button_count++] = gamepad.buttonY.isPressed;
705            buttons[button_count++] = gamepad.leftShoulder.isPressed;
706            buttons[button_count++] = gamepad.rightShoulder.isPressed;
707            pause_button_index = button_count;
708            buttons[button_count++] = joystick->delayed_guide_button;
709
710            hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
711
712            for (i = 0; i < button_count; i++) {
713                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
714            }
715        }
716#if TARGET_OS_TV
717        else if (controller.microGamepad) {
718            GCMicroGamepad *gamepad = controller.microGamepad;
719
720            Sint16 axes[] = {
721                (Sint16) (gamepad.dpad.xAxis.value * 32767),
722                (Sint16) (gamepad.dpad.yAxis.value * -32767),
723            };
724
725            for (i = 0; i < SDL_arraysize(axes); i++) {
726                SDL_PrivateJoystickAxis(joystick, i, axes[i]);
727            }
728
729            Uint8 buttons[joystick->nbuttons];
730            int button_count = 0;
731            buttons[button_count++] = gamepad.buttonA.isPressed;
732            buttons[button_count++] = gamepad.buttonX.isPressed;
733#pragma clang diagnostic push
734#pragma clang diagnostic ignored "-Wunguarded-availability-new"
735            /* This must be the last button, so we can optionally handle it with pause_button_index below */
736            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
737                if (joystick->hwdata->uses_pause_handler) {
738                    pause_button_index = button_count;
739                    buttons[button_count++] = joystick->delayed_guide_button;
740                } else {
741                    buttons[button_count++] = gamepad.buttonMenu.isPressed;
742                }
743            }
744#pragma clang diagnostic pop
745
746            for (i = 0; i < button_count; i++) {
747                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
748            }
749        }
750#endif /* TARGET_OS_TV */
751
752        if (joystick->nhats > 0) {
753            SDL_PrivateJoystickHat(joystick, 0, hatstate);
754        }
755
756        if (joystick->hwdata->uses_pause_handler) {
757            for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
758                SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED);
759                SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED);
760            }
761            joystick->hwdata->num_pause_presses = 0;
762        }
763    }
764#endif /* SDL_JOYSTICK_MFI */
765}
766
767static int
768IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
769{
770    return SDL_Unsupported();
771}
772
773static void
774IOS_JoystickUpdate(SDL_Joystick * joystick)
775{
776    SDL_JoystickDeviceItem *device = joystick->hwdata;
777
778    if (device == NULL) {
779        return;
780    }
781
782    if (device->accelerometer) {
783        IOS_AccelerometerUpdate(joystick);
784    } else if (device->controller) {
785        IOS_MFIJoystickUpdate(joystick);
786    }
787}
788
789static void
790IOS_JoystickClose(SDL_Joystick * joystick)
791{
792    SDL_JoystickDeviceItem *device = joystick->hwdata;
793
794    if (device == NULL) {
795        return;
796    }
797
798    device->joystick = NULL;
799
800    @autoreleasepool {
801        if (device->accelerometer) {
802#if !TARGET_OS_TV
803            [motionManager stopAccelerometerUpdates];
804#endif /* !TARGET_OS_TV */
805        } else if (device->controller) {
806#ifdef SDL_JOYSTICK_MFI
807            GCController *controller = device->controller;
808            controller.controllerPausedHandler = nil;
809            controller.playerIndex = -1;
810#endif
811        }
812    }
813    if (device->remote) {
814        --SDL_AppleTVRemoteOpenedAsJoystick;
815    }
816}
817
818static void
819IOS_JoystickQuit(void)
820{
821    @autoreleasepool {
822#ifdef SDL_JOYSTICK_MFI
823        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
824
825        if (connectObserver) {
826            [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
827            connectObserver = nil;
828        }
829
830        if (disconnectObserver) {
831            [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
832            disconnectObserver = nil;
833        }
834
835#if TARGET_OS_TV
836        SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
837                            SDL_AppleTVRemoteRotationHintChanged, NULL);
838#endif /* TARGET_OS_TV */
839#endif /* SDL_JOYSTICK_MFI */
840
841        while (deviceList != NULL) {
842            IOS_RemoveJoystickDevice(deviceList);
843        }
844
845#if !TARGET_OS_TV
846        motionManager = nil;
847#endif /* !TARGET_OS_TV */
848    }
849
850    numjoysticks = 0;
851}
852
853static SDL_bool
854IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
855{
856    return SDL_FALSE;
857}
858
859SDL_JoystickDriver SDL_IOS_JoystickDriver =
860{
861    IOS_JoystickInit,
862    IOS_JoystickGetCount,
863    IOS_JoystickDetect,
864    IOS_JoystickGetDeviceName,
865    IOS_JoystickGetDevicePlayerIndex,
866    IOS_JoystickSetDevicePlayerIndex,
867    IOS_JoystickGetDeviceGUID,
868    IOS_JoystickGetDeviceInstanceID,
869    IOS_JoystickOpen,
870    IOS_JoystickRumble,
871    IOS_JoystickUpdate,
872    IOS_JoystickClose,
873    IOS_JoystickQuit,
874    IOS_JoystickGetGamepadMapping
875};
876
877/* vi: set ts=4 sw=4 expandtab: */
878