src/joystick/iphoneos/SDL_sysjoystick.m
author Sam Lantinga <slouken@libsdl.org>
Thu, 08 Mar 2018 16:32:22 -0800
changeset 11925 ddb8dcdaa8d0
parent 11923 96f71726c0b2
child 11934 a7061d1f4ee4
permissions -rw-r--r--
Delay delivery of the pause button release on MFI controllers so it doesn't happen in the same frame as the button press
slouken@2765
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@11811
     3
  Copyright (C) 1997-2018 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@11925
    34
#include "SDL_timer.h"
slouken@2765
    35
#include "../SDL_sysjoystick.h"
slouken@2765
    36
#include "../SDL_joystick_c.h"
slouken@11532
    37
#include "../steam/SDL_steamcontroller.h"
slouken@11532
    38
slouken@2765
    39
slime73@10340
    40
#if !SDL_EVENTS_DISABLED
slime73@10340
    41
#include "../../events/SDL_events_c.h"
slime73@10340
    42
#endif
slime73@10340
    43
slime73@10340
    44
#if !TARGET_OS_TV
slouken@8921
    45
#import <CoreMotion/CoreMotion.h>
slime73@10340
    46
#endif
slouken@8921
    47
slime73@9876
    48
#ifdef SDL_JOYSTICK_MFI
slime73@9876
    49
#import <GameController/GameController.h>
slouken@8921
    50
slime73@9876
    51
static id connectObserver = nil;
slime73@9876
    52
static id disconnectObserver = nil;
slime73@9876
    53
#endif /* SDL_JOYSTICK_MFI */
slouken@8921
    54
slime73@10340
    55
#if !TARGET_OS_TV
slime73@9876
    56
static const char *accelerometerName = "iOS Accelerometer";
slouken@8921
    57
static CMMotionManager *motionManager = nil;
slime73@10340
    58
#endif /* !TARGET_OS_TV */
slime73@9876
    59
slime73@9876
    60
static SDL_JoystickDeviceItem *deviceList = NULL;
slime73@9876
    61
slime73@9489
    62
static int numjoysticks = 0;
slime73@9876
    63
static SDL_JoystickID instancecounter = 0;
slouken@11846
    64
int SDL_AppleTVRemoteOpenedAsJoystick = 0;
slime73@9876
    65
slime73@9876
    66
static SDL_JoystickDeviceItem *
slime73@9876
    67
GetDeviceForIndex(int device_index)
slime73@9876
    68
{
slime73@9876
    69
    SDL_JoystickDeviceItem *device = deviceList;
slime73@9876
    70
    int i = 0;
slime73@9876
    71
slime73@9876
    72
    while (i < device_index) {
slime73@9876
    73
        if (device == NULL) {
slime73@9876
    74
            return NULL;
slime73@9876
    75
        }
slime73@9876
    76
        device = device->next;
slime73@9876
    77
        i++;
slime73@9876
    78
    }
slime73@9876
    79
slime73@9876
    80
    return device;
slime73@9876
    81
}
slime73@9876
    82
slime73@9876
    83
static void
slime73@9876
    84
SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
slime73@9876
    85
{
slime73@9876
    86
#ifdef SDL_JOYSTICK_MFI
slouken@11923
    87
    const Uint16 BUS_BLUETOOTH = 0x05;
slouken@11925
    88
    const Uint16 VENDOR_APPLE = 0x05AC;
slouken@11923
    89
    Uint16 *guid16 = (Uint16 *)device->guid.data;
slouken@11925
    90
    Uint16 vendor = 0;
slouken@11925
    91
    Uint16 product = 0;
slouken@11925
    92
    Uint16 version = 0;
slouken@11925
    93
    Uint8 subtype = 0;
slouken@11923
    94
slime73@9876
    95
    const char *name = NULL;
slime73@9876
    96
    /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
slime73@9876
    97
     * struct, and ARC doesn't work with structs. */
slime73@9876
    98
    device->controller = (__bridge GCController *) CFBridgingRetain(controller);
slime73@9876
    99
slime73@9876
   100
    if (controller.vendorName) {
slime73@9876
   101
        name = controller.vendorName.UTF8String;
slime73@9876
   102
    }
slime73@9876
   103
slime73@9876
   104
    if (!name) {
slime73@9876
   105
        name = "MFi Gamepad";
slime73@9876
   106
    }
slime73@9876
   107
slime73@9876
   108
    device->name = SDL_strdup(name);
slime73@9876
   109
slime73@9876
   110
    if (controller.extendedGamepad) {
slouken@11925
   111
        vendor = VENDOR_APPLE;
slouken@11925
   112
        product = 1;
slouken@11925
   113
        subtype = 1;
slime73@9876
   114
        device->naxes = 6; /* 2 thumbsticks and 2 triggers */
slime73@9876
   115
        device->nhats = 1; /* d-pad */
slime73@9876
   116
        device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
slime73@9876
   117
    } else if (controller.gamepad) {
slouken@11925
   118
        vendor = VENDOR_APPLE;
slouken@11925
   119
        product = 2;
slouken@11925
   120
        subtype = 2;
slime73@9876
   121
        device->naxes = 0; /* no traditional analog inputs */
slime73@9876
   122
        device->nhats = 1; /* d-pad */
slime73@9876
   123
        device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
slime73@9876
   124
    }
slime73@10340
   125
#if TARGET_OS_TV
slime73@10340
   126
    else if (controller.microGamepad) {
slouken@11925
   127
        vendor = VENDOR_APPLE;
slouken@11925
   128
        product = 3;
slouken@11925
   129
        subtype = 3;
slime73@10340
   130
        device->naxes = 2; /* treat the touch surface as two axes */
slime73@10340
   131
        device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
slime73@10340
   132
        device->nbuttons = 3; /* AX, pause button */
slime73@10351
   133
slouken@10499
   134
        controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
slime73@10340
   135
    }
slime73@10340
   136
#endif /* TARGET_OS_TV */
slime73@9960
   137
slouken@11923
   138
    /* We only need 16 bits for each of these; space them out to fill 128. */
slouken@11923
   139
    /* Byteswap so devices get same GUID on little/big endian platforms. */
slouken@11923
   140
    *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
slouken@11923
   141
    *guid16++ = 0;
slouken@11925
   142
    *guid16++ = SDL_SwapLE16(vendor);
slouken@11925
   143
    *guid16++ = 0;
slouken@11925
   144
    *guid16++ = SDL_SwapLE16(product);
slouken@11925
   145
    *guid16++ = 0;
slouken@11925
   146
    *guid16++ = SDL_SwapLE16(version);
slouken@11925
   147
    *guid16++ = 0;
slouken@11923
   148
slouken@11925
   149
    /* Note that this is an MFI controller and what subtype it is */
slouken@11925
   150
    device->guid.data[14] = 'm';
slouken@11925
   151
    device->guid.data[15] = subtype;
slouken@11923
   152
slime73@9960
   153
    /* This will be set when the first button press of the controller is
slime73@9960
   154
     * detected. */
slime73@9960
   155
    controller.playerIndex = -1;
slime73@10340
   156
slime73@10340
   157
#endif /* SDL_JOYSTICK_MFI */
slime73@9876
   158
}
slime73@9876
   159
slime73@9876
   160
static void
slime73@9876
   161
SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
slime73@9876
   162
{
slime73@9876
   163
    SDL_JoystickDeviceItem *device = deviceList;
slime73@9876
   164
slouken@11845
   165
#if TARGET_OS_TV
slouken@11845
   166
    if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) {
slouken@11845
   167
        /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */
slouken@11845
   168
        if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) {
slouken@11845
   169
            return;
slouken@11845
   170
        }
slouken@11845
   171
    }
slouken@11845
   172
#endif
slouken@11845
   173
slime73@9876
   174
    while (device != NULL) {
slime73@9876
   175
        if (device->controller == controller) {
slime73@9876
   176
            return;
slime73@9876
   177
        }
slime73@9876
   178
        device = device->next;
slime73@9876
   179
    }
slime73@9876
   180
slouken@11845
   181
    device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
slime73@9876
   182
    if (device == NULL) {
slime73@9876
   183
        return;
slime73@9876
   184
    }
slime73@9876
   185
slime73@9876
   186
    device->accelerometer = accelerometer;
slime73@9876
   187
    device->instance_id = instancecounter++;
slime73@9876
   188
slime73@9876
   189
    if (accelerometer) {
slime73@10340
   190
#if TARGET_OS_TV
slime73@10340
   191
        SDL_free(device);
slime73@10340
   192
        return;
slime73@10340
   193
#else
slime73@9876
   194
        device->name = SDL_strdup(accelerometerName);
slime73@9876
   195
        device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
slime73@9876
   196
        device->nhats = 0;
slime73@9876
   197
        device->nbuttons = 0;
slime73@9876
   198
slime73@9876
   199
        /* Use the accelerometer name as a GUID. */
slime73@9876
   200
        SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
slime73@10340
   201
#endif /* TARGET_OS_TV */
slime73@9876
   202
    } else if (controller) {
slime73@9876
   203
        SDL_SYS_AddMFIJoystickDevice(device, controller);
slime73@9876
   204
    }
slime73@9876
   205
slime73@9876
   206
    if (deviceList == NULL) {
slime73@9876
   207
        deviceList = device;
slime73@9876
   208
    } else {
slime73@9876
   209
        SDL_JoystickDeviceItem *lastdevice = deviceList;
slime73@9876
   210
        while (lastdevice->next != NULL) {
slime73@9876
   211
            lastdevice = lastdevice->next;
slime73@9876
   212
        }
slime73@9876
   213
        lastdevice->next = device;
slime73@9876
   214
    }
slime73@9876
   215
slime73@9876
   216
    ++numjoysticks;
slime73@9876
   217
slouken@10226
   218
    SDL_PrivateJoystickAdded(numjoysticks - 1);
slime73@9876
   219
}
slime73@9876
   220
slime73@9879
   221
static SDL_JoystickDeviceItem *
slime73@9876
   222
SDL_SYS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
slime73@9876
   223
{
slime73@9876
   224
    SDL_JoystickDeviceItem *prev = NULL;
slime73@9876
   225
    SDL_JoystickDeviceItem *next = NULL;
slime73@9876
   226
    SDL_JoystickDeviceItem *item = deviceList;
slime73@9876
   227
slime73@9876
   228
    if (device == NULL) {
slime73@9876
   229
        return NULL;
slime73@9876
   230
    }
slime73@9876
   231
slime73@9876
   232
    next = device->next;
slime73@9876
   233
slime73@9876
   234
    while (item != NULL) {
slime73@9876
   235
        if (item == device) {
slime73@9876
   236
            break;
slime73@9876
   237
        }
slime73@9876
   238
        prev = item;
slime73@9876
   239
        item = item->next;
slime73@9876
   240
    }
slime73@9876
   241
slime73@9876
   242
    /* Unlink the device item from the device list. */
slime73@9876
   243
    if (prev) {
slime73@9876
   244
        prev->next = device->next;
slime73@9876
   245
    } else if (device == deviceList) {
slime73@9876
   246
        deviceList = device->next;
slime73@9876
   247
    }
slime73@9876
   248
slime73@9876
   249
    if (device->joystick) {
slime73@9876
   250
        device->joystick->hwdata = NULL;
slime73@9876
   251
    }
slime73@9876
   252
slime73@9876
   253
#ifdef SDL_JOYSTICK_MFI
slime73@9876
   254
    @autoreleasepool {
slime73@9876
   255
        if (device->controller) {
slime73@9876
   256
            /* The controller was explicitly retained in the struct, so it
slime73@9876
   257
             * should be explicitly released before freeing the struct. */
slime73@9876
   258
            GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
slime73@9876
   259
            controller.controllerPausedHandler = nil;
slime73@9876
   260
            device->controller = nil;
slime73@9876
   261
        }
slime73@9876
   262
    }
slime73@9876
   263
#endif /* SDL_JOYSTICK_MFI */
slime73@9876
   264
slime73@9876
   265
    --numjoysticks;
slime73@9876
   266
slouken@11532
   267
    SDL_PrivateJoystickRemoved(device->instance_id);
slime73@9876
   268
philipp@9878
   269
    SDL_free(device->name);
philipp@9878
   270
    SDL_free(device);
philipp@9878
   271
slime73@9876
   272
    return next;
slime73@9876
   273
}
slouken@2765
   274
slime73@10351
   275
#if TARGET_OS_TV
slouken@11284
   276
static void SDLCALL
slime73@10351
   277
SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
slime73@10351
   278
{
slime73@10351
   279
    BOOL allowRotation = newValue != NULL && *newValue != '0';
slime73@10351
   280
slime73@10351
   281
    @autoreleasepool {
slime73@10351
   282
        for (GCController *controller in [GCController controllers]) {
slime73@10351
   283
            if (controller.microGamepad) {
slime73@10351
   284
                controller.microGamepad.allowsRotation = allowRotation;
slime73@10351
   285
            }
slime73@10351
   286
        }
slime73@10351
   287
    }
slime73@10351
   288
}
slime73@10351
   289
#endif /* TARGET_OS_TV */
slime73@10351
   290
slouken@11532
   291
static SDL_bool SteamControllerConnectedCallback(const char *name, SDL_JoystickGUID guid, int *device_instance)
slouken@11532
   292
{
slouken@11532
   293
    SDL_JoystickDeviceItem *device = (SDL_JoystickDeviceItem *)SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
slouken@11532
   294
    if (device == NULL) {
slouken@11532
   295
        return SDL_FALSE;
slouken@11532
   296
    }
slouken@11532
   297
slouken@11532
   298
    *device_instance = device->instance_id = instancecounter++;
slouken@11845
   299
    device->name = SDL_strdup(name);
slouken@11845
   300
    device->guid = guid;
slouken@11845
   301
    SDL_GetSteamControllerInputs(&device->nbuttons,
slouken@11845
   302
                                 &device->naxes,
slouken@11845
   303
                                 &device->nhats);
slouken@11532
   304
    device->m_bSteamController = SDL_TRUE;
slouken@11532
   305
slouken@11532
   306
    if (deviceList == NULL) {
slouken@11532
   307
        deviceList = device;
slouken@11532
   308
    } else {
slouken@11532
   309
        SDL_JoystickDeviceItem *lastdevice = deviceList;
slouken@11532
   310
        while (lastdevice->next != NULL) {
slouken@11532
   311
            lastdevice = lastdevice->next;
slouken@11532
   312
        }
slouken@11532
   313
        lastdevice->next = device;
slouken@11532
   314
    }
slouken@11532
   315
slouken@11532
   316
    ++numjoysticks;
slouken@11532
   317
slouken@11532
   318
    SDL_PrivateJoystickAdded(numjoysticks - 1);
slouken@11532
   319
slouken@11532
   320
    return SDL_TRUE;
slouken@11532
   321
}
slouken@11532
   322
slouken@11532
   323
static void SteamControllerDisconnectedCallback(int device_instance)
slouken@11532
   324
{
slouken@11532
   325
    SDL_JoystickDeviceItem *item;
slouken@11532
   326
slouken@11845
   327
    for (item = deviceList; item; item = item->next) {
slouken@11532
   328
        if (item->instance_id == device_instance) {
slouken@11845
   329
            SDL_SYS_RemoveJoystickDevice(item);
slouken@11845
   330
            break;
slouken@11532
   331
        }
slouken@11532
   332
    }
slouken@11532
   333
}
slouken@11532
   334
slouken@2765
   335
/* Function to scan the system for joysticks.
philipp@9311
   336
 * Joystick 0 should be the system default joystick.
slouken@2765
   337
 * It should return 0, or -1 on an unrecoverable fatal error.
slouken@2765
   338
 */
slouken@2765
   339
int
slouken@2765
   340
SDL_SYS_JoystickInit(void)
slouken@2765
   341
{
slime73@9876
   342
    @autoreleasepool {
slime73@9876
   343
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
slime73@10340
   344
slouken@11532
   345
        SDL_InitSteamControllers(SteamControllerConnectedCallback,
slouken@11532
   346
                                 SteamControllerDisconnectedCallback);
slouken@11532
   347
slime73@10340
   348
#if !TARGET_OS_TV
slouken@10499
   349
        if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
slime73@9876
   350
            /* Default behavior, accelerometer as joystick */
slime73@9876
   351
            SDL_SYS_AddJoystickDevice(nil, SDL_TRUE);
slime73@9876
   352
        }
slime73@10340
   353
#endif /* !TARGET_OS_TV */
slime73@9876
   354
slime73@9876
   355
#ifdef SDL_JOYSTICK_MFI
slime73@9876
   356
        /* GameController.framework was added in iOS 7. */
slime73@9876
   357
        if (![GCController class]) {
slime73@9876
   358
            return numjoysticks;
slime73@9876
   359
        }
slime73@9876
   360
slime73@9876
   361
        for (GCController *controller in [GCController controllers]) {
slime73@9876
   362
            SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
slime73@9876
   363
        }
slime73@9876
   364
slime73@10351
   365
#if TARGET_OS_TV
slime73@10351
   366
        SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
slime73@10351
   367
                            SDL_AppleTVRemoteRotationHintChanged, NULL);
slime73@10351
   368
#endif /* TARGET_OS_TV */
slime73@10351
   369
slime73@9876
   370
        connectObserver = [center addObserverForName:GCControllerDidConnectNotification
slime73@9876
   371
                                              object:nil
slime73@9876
   372
                                               queue:nil
slime73@9876
   373
                                          usingBlock:^(NSNotification *note) {
slime73@9876
   374
                                              GCController *controller = note.object;
slime73@9876
   375
                                              SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
slime73@9876
   376
                                          }];
slime73@9876
   377
slime73@9876
   378
        disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
slime73@9876
   379
                                                 object:nil
slime73@9876
   380
                                                  queue:nil
slime73@9876
   381
                                             usingBlock:^(NSNotification *note) {
slime73@9876
   382
                                                 GCController *controller = note.object;
slime73@9876
   383
                                                 SDL_JoystickDeviceItem *device = deviceList;
slime73@9876
   384
                                                 while (device != NULL) {
slime73@9876
   385
                                                     if (device->controller == controller) {
slime73@9876
   386
                                                         SDL_SYS_RemoveJoystickDevice(device);
slime73@9876
   387
                                                         break;
slime73@9876
   388
                                                     }
slime73@9876
   389
                                                     device = device->next;
slime73@9876
   390
                                                 }
slime73@9876
   391
                                             }];
slime73@9876
   392
#endif /* SDL_JOYSTICK_MFI */
slime73@9489
   393
    }
slime73@9489
   394
slime73@9489
   395
    return numjoysticks;
slouken@2765
   396
}
slouken@2765
   397
philipp@10617
   398
int
philipp@10617
   399
SDL_SYS_NumJoysticks(void)
slouken@6707
   400
{
slime73@9489
   401
    return numjoysticks;
slouken@6707
   402
}
slouken@6707
   403
philipp@10617
   404
void
philipp@10617
   405
SDL_SYS_JoystickDetect(void)
slouken@6707
   406
{
slouken@11532
   407
    SDL_UpdateSteamControllers();
slouken@6707
   408
}
slouken@6707
   409
slouken@2765
   410
/* Function to get the device-dependent name of a joystick */
slouken@2765
   411
const char *
slouken@6707
   412
SDL_SYS_JoystickNameForDeviceIndex(int device_index)
slouken@2765
   413
{
slime73@9876
   414
    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
slime73@9876
   415
    return device ? device->name : "Unknown";
slouken@6707
   416
}
slouken@6707
   417
slouken@6707
   418
/* Function to perform the mapping from device index to the instance id for this index */
slouken@6707
   419
SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
slouken@6707
   420
{
slime73@9876
   421
    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
slime73@9876
   422
    return device ? device->instance_id : 0;
slouken@2765
   423
}
slouken@2765
   424
slouken@2765
   425
/* Function to open a joystick for use.
philipp@9380
   426
   The joystick to open is specified by the device index.
slouken@2765
   427
   This should fill the nbuttons and naxes fields of the joystick structure.
slouken@2765
   428
   It returns 0, or -1 if there is an error.
slouken@2765
   429
 */
slouken@2765
   430
int
slouken@6693
   431
SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
slouken@2765
   432
{
slime73@9876
   433
    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
slime73@9876
   434
    if (device == NULL) {
slime73@9876
   435
        return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
slime73@9876
   436
    }
slime73@9876
   437
slime73@9876
   438
    joystick->hwdata = device;
slime73@9876
   439
    joystick->instance_id = device->instance_id;
slime73@9876
   440
slime73@9876
   441
    joystick->naxes = device->naxes;
slime73@9876
   442
    joystick->nhats = device->nhats;
slime73@9876
   443
    joystick->nbuttons = device->nbuttons;
slouken@6707
   444
    joystick->nballs = 0;
slime73@9876
   445
slime73@9876
   446
    device->joystick = joystick;
slouken@8921
   447
slime73@9506
   448
    @autoreleasepool {
slime73@9876
   449
        if (device->accelerometer) {
slime73@10340
   450
#if !TARGET_OS_TV
slime73@9876
   451
            if (motionManager == nil) {
slime73@9876
   452
                motionManager = [[CMMotionManager alloc] init];
slime73@9876
   453
            }
slime73@9876
   454
slime73@9876
   455
            /* Shorter times between updates can significantly increase CPU usage. */
slime73@9876
   456
            motionManager.accelerometerUpdateInterval = 0.1;
slime73@9876
   457
            [motionManager startAccelerometerUpdates];
slime73@10340
   458
#endif /* !TARGET_OS_TV */
slime73@9876
   459
        } else {
slime73@9876
   460
#ifdef SDL_JOYSTICK_MFI
slime73@9876
   461
            GCController *controller = device->controller;
slime73@9960
   462
            controller.controllerPausedHandler = ^(GCController *c) {
slime73@9876
   463
                if (joystick->hwdata) {
slime73@9876
   464
                    ++joystick->hwdata->num_pause_presses;
slime73@9876
   465
                }
slime73@9876
   466
            };
slime73@9876
   467
#endif /* SDL_JOYSTICK_MFI */
slime73@9506
   468
        }
slouken@8921
   469
    }
slouken@11846
   470
    if (device->remote) {
slouken@11846
   471
        ++SDL_AppleTVRemoteOpenedAsJoystick;
slouken@11846
   472
    }
slouken@8921
   473
slouken@6707
   474
    return 0;
slouken@6707
   475
}
slouken@6707
   476
philipp@9561
   477
/* Function to determine if this joystick is attached to the system right now */
slime73@9879
   478
SDL_bool
slime73@9879
   479
SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
slouken@6707
   480
{
slime73@9876
   481
    return joystick->hwdata != NULL;
slouken@2765
   482
}
slouken@2765
   483
slime73@9879
   484
static void
slime73@9879
   485
SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
slouken@8921
   486
{
slime73@10340
   487
#if !TARGET_OS_TV
slouken@8921
   488
    const float maxgforce = SDL_IPHONE_MAX_GFORCE;
slouken@8921
   489
    const SInt16 maxsint16 = 0x7FFF;
slouken@8921
   490
    CMAcceleration accel;
slouken@8921
   491
slime73@9506
   492
    @autoreleasepool {
slime73@9876
   493
        if (!motionManager.isAccelerometerActive) {
slime73@9506
   494
            return;
slime73@9506
   495
        }
slime73@9506
   496
slime73@9506
   497
        accel = motionManager.accelerometerData.acceleration;
slouken@8921
   498
    }
slouken@8921
   499
slouken@8921
   500
    /*
slouken@8921
   501
     Convert accelerometer data from floating point to Sint16, which is what
slouken@8921
   502
     the joystick system expects.
slouken@8921
   503
slouken@8921
   504
     To do the conversion, the data is first clamped onto the interval
slouken@8921
   505
     [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
slouken@8921
   506
     by MAX_SINT16 so that it is mapped to the full range of an Sint16.
slouken@8921
   507
slouken@8921
   508
     You can customize the clamped range of this function by modifying the
slouken@8921
   509
     SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
slouken@8921
   510
slouken@8921
   511
     Once converted to Sint16, the accelerometer data no longer has coherent
slouken@8921
   512
     units. You can convert the data back to units of g-force by multiplying
slouken@8921
   513
     it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
slouken@8921
   514
     */
slouken@8921
   515
slouken@8921
   516
    /* clamp the data */
slouken@8921
   517
    accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
slouken@8921
   518
    accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
slouken@8921
   519
    accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
slouken@8921
   520
slouken@8921
   521
    /* pass in data mapped to range of SInt16 */
slime73@9876
   522
    SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
slouken@8921
   523
    SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
slime73@9876
   524
    SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
slime73@10340
   525
#endif /* !TARGET_OS_TV */
slime73@9876
   526
}
slime73@9876
   527
slime73@9876
   528
#ifdef SDL_JOYSTICK_MFI
slime73@9876
   529
static Uint8
slime73@9876
   530
SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
slime73@9876
   531
{
slime73@9876
   532
    Uint8 hat = 0;
slime73@9876
   533
slime73@9876
   534
    if (dpad.up.isPressed) {
slime73@9876
   535
        hat |= SDL_HAT_UP;
slime73@9876
   536
    } else if (dpad.down.isPressed) {
slime73@9876
   537
        hat |= SDL_HAT_DOWN;
slime73@9876
   538
    }
slime73@9876
   539
slime73@9876
   540
    if (dpad.left.isPressed) {
slime73@9876
   541
        hat |= SDL_HAT_LEFT;
slime73@9876
   542
    } else if (dpad.right.isPressed) {
slime73@9876
   543
        hat |= SDL_HAT_RIGHT;
slime73@9876
   544
    }
slime73@9876
   545
slime73@9876
   546
    if (hat == 0) {
slime73@9876
   547
        return SDL_HAT_CENTERED;
slime73@9876
   548
    }
slime73@9876
   549
slime73@9876
   550
    return hat;
slime73@9876
   551
}
slime73@9876
   552
#endif
slime73@9876
   553
slime73@9876
   554
static void
slime73@9876
   555
SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
slime73@9876
   556
{
slime73@10340
   557
#if SDL_JOYSTICK_MFI
slime73@9876
   558
    @autoreleasepool {
slime73@9876
   559
        GCController *controller = joystick->hwdata->controller;
slime73@9876
   560
        Uint8 hatstate = SDL_HAT_CENTERED;
slime73@9876
   561
        int i;
slime73@9960
   562
        int updateplayerindex = 0;
slouken@11925
   563
        const Uint8 pausebutton = joystick->nbuttons - 1; /* The pause button is always last. */
slouken@11925
   564
        const Uint32 PAUSE_RELEASE_DELAY_MS = 100;
slime73@9876
   565
slime73@9876
   566
        if (controller.extendedGamepad) {
slime73@9876
   567
            GCExtendedGamepad *gamepad = controller.extendedGamepad;
slime73@9876
   568
slime73@9876
   569
            /* Axis order matches the XInput Windows mappings. */
slime73@9960
   570
            Sint16 axes[] = {
slime73@9960
   571
                (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
slime73@9960
   572
                (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
slime73@9960
   573
                (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
slime73@9960
   574
                (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
slime73@9960
   575
                (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
slime73@9960
   576
                (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
slime73@9960
   577
            };
slime73@9960
   578
slime73@9960
   579
            /* Button order matches the XInput Windows mappings. */
slime73@9960
   580
            Uint8 buttons[] = {
slime73@9960
   581
                gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
slime73@9960
   582
                gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
slime73@9960
   583
                gamepad.leftShoulder.isPressed,
slime73@9960
   584
                gamepad.rightShoulder.isPressed,
slime73@9960
   585
            };
slime73@9876
   586
slime73@9876
   587
            hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
slime73@9876
   588
slime73@9960
   589
            for (i = 0; i < SDL_arraysize(axes); i++) {
slime73@9960
   590
                /* The triggers (axes 2 and 5) are resting at -32768 but SDL
slime73@9960
   591
                 * initializes its values to 0. We only want to make sure the
slime73@9960
   592
                 * player index is up to date if the user actually moves an axis. */
slime73@9960
   593
                if ((i != 2 && i != 5) || axes[i] != -32768) {
slouken@10714
   594
                    updateplayerindex |= (joystick->axes[i].value != axes[i]);
slime73@9960
   595
                }
slime73@9960
   596
                SDL_PrivateJoystickAxis(joystick, i, axes[i]);
slime73@9960
   597
            }
slime73@9960
   598
slime73@9960
   599
            for (i = 0; i < SDL_arraysize(buttons); i++) {
slime73@9960
   600
                updateplayerindex |= (joystick->buttons[i] != buttons[i]);
slime73@9960
   601
                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
slime73@9960
   602
            }
slime73@9876
   603
        } else if (controller.gamepad) {
slime73@9876
   604
            GCGamepad *gamepad = controller.gamepad;
slime73@9876
   605
slime73@9960
   606
            /* Button order matches the XInput Windows mappings. */
slime73@9960
   607
            Uint8 buttons[] = {
slime73@9960
   608
                gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
slime73@9960
   609
                gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
slime73@9960
   610
                gamepad.leftShoulder.isPressed,
slime73@9960
   611
                gamepad.rightShoulder.isPressed,
slime73@9960
   612
            };
slime73@9960
   613
slime73@9876
   614
            hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
slime73@9876
   615
slime73@9960
   616
            for (i = 0; i < SDL_arraysize(buttons); i++) {
slime73@9960
   617
                updateplayerindex |= (joystick->buttons[i] != buttons[i]);
slime73@9960
   618
                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
slime73@9960
   619
            }
slime73@9876
   620
        }
slime73@10340
   621
#if TARGET_OS_TV
slime73@10340
   622
        else if (controller.microGamepad) {
slime73@10340
   623
            GCMicroGamepad *gamepad = controller.microGamepad;
slime73@10340
   624
slime73@10340
   625
            Sint16 axes[] = {
slime73@10340
   626
                (Sint16) (gamepad.dpad.xAxis.value * 32767),
slime73@10340
   627
                (Sint16) (gamepad.dpad.yAxis.value * -32767),
slime73@10340
   628
            };
slime73@10340
   629
slime73@10340
   630
            for (i = 0; i < SDL_arraysize(axes); i++) {
slouken@10873
   631
                updateplayerindex |= (joystick->axes[i].value != axes[i]);
slime73@10340
   632
                SDL_PrivateJoystickAxis(joystick, i, axes[i]);
slime73@10340
   633
            }
slime73@10340
   634
slime73@10340
   635
            Uint8 buttons[] = {
slime73@10340
   636
                gamepad.buttonA.isPressed,
slime73@10340
   637
                gamepad.buttonX.isPressed,
slime73@10340
   638
            };
slime73@10340
   639
slime73@10340
   640
            for (i = 0; i < SDL_arraysize(buttons); i++) {
slime73@10340
   641
                updateplayerindex |= (joystick->buttons[i] != buttons[i]);
slime73@10340
   642
                SDL_PrivateJoystickButton(joystick, i, buttons[i]);
slime73@10340
   643
            }
slime73@10340
   644
        }
slime73@10340
   645
#endif /* TARGET_OS_TV */
slime73@9876
   646
slime73@9960
   647
        if (joystick->nhats > 0) {
slime73@9960
   648
            updateplayerindex |= (joystick->hats[0] != hatstate);
slime73@9960
   649
            SDL_PrivateJoystickHat(joystick, 0, hatstate);
slime73@9960
   650
        }
slime73@9876
   651
slime73@9876
   652
        for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
slime73@9960
   653
            SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED);
slouken@11925
   654
            joystick->hwdata->pause_button_down_time = SDL_GetTicks();
slouken@11925
   655
            if (!joystick->hwdata->pause_button_down_time) {
slouken@11925
   656
                joystick->hwdata->pause_button_down_time = 1;
slouken@11925
   657
            }
slime73@9960
   658
            updateplayerindex = YES;
slime73@9876
   659
        }
slouken@11925
   660
        joystick->hwdata->num_pause_presses = 0;
slime73@9876
   661
slouken@11925
   662
        if (joystick->hwdata->pause_button_down_time &&
slouken@11925
   663
            SDL_TICKS_PASSED(SDL_GetTicks(), joystick->hwdata->pause_button_down_time + PAUSE_RELEASE_DELAY_MS)) {
slouken@11925
   664
            SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED);
slouken@11925
   665
            joystick->hwdata->pause_button_down_time = 0;
slouken@11925
   666
        }
slime73@9960
   667
slime73@9960
   668
        if (updateplayerindex && controller.playerIndex == -1) {
slime73@9960
   669
            BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
slime73@9960
   670
slime73@9960
   671
            /* Find the player index of all other connected controllers. */
slime73@9960
   672
            for (GCController *c in [GCController controllers]) {
slime73@9960
   673
                if (c != controller && c.playerIndex >= 0) {
slime73@9960
   674
                    usedPlayerIndexSlots[c.playerIndex] = YES;
slime73@9960
   675
                }
slime73@9960
   676
            }
slime73@9960
   677
slime73@9960
   678
            /* Set this controller's player index to the first unused index.
slime73@9960
   679
             * FIXME: This logic isn't great... but SDL doesn't expose this
slime73@9960
   680
             * concept in its external API, so we don't have much to go on. */
slime73@9960
   681
            for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) {
slime73@9960
   682
                if (!usedPlayerIndexSlots[i]) {
slime73@9960
   683
                    controller.playerIndex = i;
slime73@9960
   684
                    break;
slime73@9960
   685
                }
slime73@9960
   686
            }
slime73@9960
   687
        }
slime73@9876
   688
    }
slime73@10340
   689
#endif /* SDL_JOYSTICK_MFI */
slouken@8921
   690
}
slouken@8921
   691
slouken@2765
   692
/* Function to update the state of a joystick - called as a device poll.
slouken@2765
   693
 * This function shouldn't update the joystick structure directly,
slouken@2765
   694
 * but instead should call SDL_PrivateJoystick*() to deliver events
slouken@2765
   695
 * and update joystick device state.
slouken@2765
   696
 */
slouken@2765
   697
void
slouken@2765
   698
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
slouken@2765
   699
{
slime73@9876
   700
    SDL_JoystickDeviceItem *device = joystick->hwdata;
slime73@9876
   701
slime73@9876
   702
    if (device == NULL) {
slime73@9876
   703
        return;
slime73@9876
   704
    }
slouken@11532
   705
    
slouken@11532
   706
    if (device->m_bSteamController) {
slouken@11532
   707
        SDL_UpdateSteamController(joystick);
slouken@11532
   708
        return;
slouken@11532
   709
    }
slime73@9876
   710
slime73@9876
   711
    if (device->accelerometer) {
slime73@9876
   712
        SDL_SYS_AccelerometerUpdate(joystick);
slime73@9876
   713
    } else if (device->controller) {
slime73@9876
   714
        SDL_SYS_MFIJoystickUpdate(joystick);
slime73@9876
   715
    }
slouken@2765
   716
}
slouken@2765
   717
slouken@2765
   718
/* Function to close a joystick after use */
slouken@2765
   719
void
slouken@2765
   720
SDL_SYS_JoystickClose(SDL_Joystick * joystick)
slouken@2765
   721
{
slime73@9876
   722
    SDL_JoystickDeviceItem *device = joystick->hwdata;
slime73@9876
   723
slime73@9876
   724
    if (device == NULL) {
slime73@9876
   725
        return;
slime73@9876
   726
    }
slime73@9876
   727
slime73@9876
   728
    device->joystick = NULL;
slime73@9876
   729
slime73@9506
   730
    @autoreleasepool {
slime73@9876
   731
        if (device->accelerometer) {
slime73@10340
   732
#if !TARGET_OS_TV
slime73@9876
   733
            [motionManager stopAccelerometerUpdates];
slime73@10340
   734
#endif /* !TARGET_OS_TV */
slime73@9876
   735
        } else if (device->controller) {
slime73@9876
   736
#ifdef SDL_JOYSTICK_MFI
slime73@9876
   737
            GCController *controller = device->controller;
slime73@9876
   738
            controller.controllerPausedHandler = nil;
slime73@9960
   739
            controller.playerIndex = -1;
slime73@9876
   740
#endif
slime73@9876
   741
        }
slime73@9506
   742
    }
slouken@11846
   743
    if (device->remote) {
slouken@11846
   744
        --SDL_AppleTVRemoteOpenedAsJoystick;
slouken@11846
   745
    }
slouken@2765
   746
}
slouken@2765
   747
slouken@2765
   748
/* Function to perform any system-specific joystick related cleanup */
slouken@2765
   749
void
slouken@2765
   750
SDL_SYS_JoystickQuit(void)
slouken@2765
   751
{
slime73@9506
   752
    @autoreleasepool {
slime73@9876
   753
#ifdef SDL_JOYSTICK_MFI
slime73@9876
   754
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
slime73@9876
   755
slime73@9876
   756
        if (connectObserver) {
slime73@9876
   757
            [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
slime73@9876
   758
            connectObserver = nil;
slime73@9876
   759
        }
slime73@9876
   760
slime73@9876
   761
        if (disconnectObserver) {
slime73@9876
   762
            [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
slime73@9876
   763
            disconnectObserver = nil;
slime73@9876
   764
        }
slime73@10351
   765
slime73@10351
   766
#if TARGET_OS_TV
slime73@10351
   767
        SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
slime73@10351
   768
                            SDL_AppleTVRemoteRotationHintChanged, NULL);
slime73@10351
   769
#endif /* TARGET_OS_TV */
slime73@9876
   770
#endif /* SDL_JOYSTICK_MFI */
slime73@9876
   771
slime73@9876
   772
        while (deviceList != NULL) {
slime73@9876
   773
            SDL_SYS_RemoveJoystickDevice(deviceList);
slime73@9876
   774
        }
slime73@9876
   775
slime73@10340
   776
#if !TARGET_OS_TV
slouken@8921
   777
        motionManager = nil;
slime73@10340
   778
#endif /* !TARGET_OS_TV */
slouken@8921
   779
    }
slime73@9489
   780
slouken@11532
   781
    SDL_QuitSteamControllers();
slouken@11532
   782
slime73@9489
   783
    numjoysticks = 0;
slouken@2765
   784
}
slouken@6693
   785
slime73@9879
   786
SDL_JoystickGUID
slime73@9879
   787
SDL_SYS_JoystickGetDeviceGUID( int device_index )
slouken@6693
   788
{
slime73@9876
   789
    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
slouken@6744
   790
    SDL_JoystickGUID guid;
slime73@9876
   791
    if (device) {
slime73@9876
   792
        guid = device->guid;
slime73@9876
   793
    } else {
slime73@9876
   794
        SDL_zero(guid);
slime73@9876
   795
    }
slouken@6693
   796
    return guid;
slouken@6693
   797
}
slouken@6693
   798
slime73@9879
   799
SDL_JoystickGUID
slime73@9879
   800
SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
slouken@6693
   801
{
slouken@6744
   802
    SDL_JoystickGUID guid;
slime73@9876
   803
    if (joystick->hwdata) {
slime73@9876
   804
        guid = joystick->hwdata->guid;
slime73@9876
   805
    } else {
slime73@9876
   806
        SDL_zero(guid);
slime73@9876
   807
    }
slouken@6693
   808
    return guid;
slouken@6693
   809
}
slouken@6693
   810
slouken@2765
   811
/* vi: set ts=4 sw=4 expandtab: */