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