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