src/joystick/darwin/SDL_sysjoystick.c
author Misty De Meo <mistydemeo@gmail.com>
Wed, 25 Jan 2017 22:22:05 -0800
changeset 10853 f4a771647ecd
parent 10780 4ea5472ed455
child 11201 813a8510bd0c
permissions -rw-r--r--
Darwin: fix detection of Xbox One S controller

Firmware revision 3.1.1221.0 changes the mapping of the Xbox One S
controller in Bluetooth mode. Aside from changing the layout of
other buttons, this revision also changes the triggers to act as
Accelerator and Brake axes from the simulation controls page.

The Darwin sysjoystick code didn't previously map anything at these
axes, making it impossible to detect input on these two buttons.
slouken@172
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@10737
     3
  Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
slouken@172
     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@172
     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@172
    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@172
    20
*/
icculus@8093
    21
#include "../../SDL_internal.h"
slouken@172
    22
slouken@1635
    23
#ifdef SDL_JOYSTICK_IOKIT
slouken@1635
    24
slouken@172
    25
#include <IOKit/hid/IOHIDLib.h>
slouken@172
    26
slouken@2713
    27
/* For force feedback testing. */
slouken@2713
    28
#include <ForceFeedback/ForceFeedback.h>
slouken@2713
    29
#include <ForceFeedback/ForceFeedbackConstants.h>
slouken@2713
    30
slouken@172
    31
#include "SDL_joystick.h"
slouken@1361
    32
#include "../SDL_sysjoystick.h"
slouken@1361
    33
#include "../SDL_joystick_c.h"
slouken@2713
    34
#include "SDL_sysjoystick_c.h"
slouken@6690
    35
#include "SDL_events.h"
urkle@8173
    36
#include "../../haptic/darwin/SDL_syshaptic_c.h"    /* For haptic hot plugging */
slouken@172
    37
icculus@8739
    38
#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
icculus@8739
    39
icculus@8230
    40
/* The base object of the HID Manager API */
icculus@8230
    41
static IOHIDManagerRef hidman = NULL;
slouken@172
    42
slouken@172
    43
/* Linked list of all available devices */
slouken@172
    44
static recDevice *gpDeviceList = NULL;
icculus@8230
    45
slouken@6690
    46
/* static incrementing counter for new joystick devices seen on the system. Devices should start with index 0 */
slouken@6690
    47
static int s_joystick_instance_id = -1;
slouken@172
    48
slouken@9629
    49
static recDevice *GetDeviceForIndex(int device_index)
slouken@9629
    50
{
slouken@9629
    51
    recDevice *device = gpDeviceList;
slouken@9629
    52
    while (device) {
slouken@9629
    53
        if (!device->removed) {
slouken@9629
    54
            if (device_index == 0)
slouken@9629
    55
                break;
slouken@9629
    56
slouken@9629
    57
            --device_index;
slouken@9629
    58
        }
slouken@9629
    59
        device = device->pNext;
slouken@9629
    60
    }
slouken@9629
    61
    return device;
slouken@9629
    62
}
icculus@8230
    63
slouken@1895
    64
static void
icculus@8230
    65
FreeElementList(recElement *pElement)
slouken@172
    66
{
icculus@8230
    67
    while (pElement) {
icculus@8230
    68
        recElement *pElementNext = pElement->pNext;
icculus@8230
    69
        SDL_free(pElement);
icculus@8230
    70
        pElement = pElementNext;
icculus@8230
    71
    }
slouken@172
    72
}
slouken@172
    73
icculus@8230
    74
static recDevice *
icculus@8230
    75
FreeDevice(recDevice *removeDevice)
icculus@8230
    76
{
icculus@8230
    77
    recDevice *pDeviceNext = NULL;
icculus@8230
    78
    if (removeDevice) {
icculus@8739
    79
        if (removeDevice->deviceRef) {
icculus@8739
    80
            IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
icculus@8739
    81
            removeDevice->deviceRef = NULL;
icculus@8739
    82
        }
icculus@8739
    83
icculus@8230
    84
        /* save next device prior to disposing of this device */
icculus@8230
    85
        pDeviceNext = removeDevice->pNext;
slouken@172
    86
icculus@8230
    87
        if ( gpDeviceList == removeDevice ) {
icculus@8230
    88
            gpDeviceList = pDeviceNext;
icculus@8230
    89
        } else {
icculus@8230
    90
            recDevice *device = gpDeviceList;
icculus@8230
    91
            while (device->pNext != removeDevice) {
icculus@8230
    92
                device = device->pNext;
icculus@8230
    93
            }
icculus@8230
    94
            device->pNext = pDeviceNext;
icculus@8230
    95
        }
icculus@8230
    96
        removeDevice->pNext = NULL;
icculus@8230
    97
icculus@8230
    98
        /* free element lists */
icculus@8230
    99
        FreeElementList(removeDevice->firstAxis);
icculus@8230
   100
        FreeElementList(removeDevice->firstButton);
icculus@8230
   101
        FreeElementList(removeDevice->firstHat);
icculus@8230
   102
icculus@8230
   103
        SDL_free(removeDevice);
icculus@8230
   104
    }
icculus@8230
   105
    return pDeviceNext;
icculus@8230
   106
}
slouken@172
   107
slouken@1895
   108
static SInt32
icculus@8230
   109
GetHIDElementState(recDevice *pDevice, recElement *pElement)
slouken@172
   110
{
icculus@8230
   111
    SInt32 value = 0;
slouken@172
   112
icculus@8230
   113
    if (pDevice && pElement) {
icculus@8230
   114
        IOHIDValueRef valueRef;
icculus@8230
   115
        if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
icculus@8230
   116
            value = (SInt32) IOHIDValueGetIntegerValue(valueRef);
icculus@8230
   117
slouken@1895
   118
            /* record min and max for auto calibration */
icculus@8230
   119
            if (value < pElement->minReport) {
icculus@8230
   120
                pElement->minReport = value;
icculus@8230
   121
            }
icculus@8230
   122
            if (value > pElement->maxReport) {
icculus@8230
   123
                pElement->maxReport = value;
icculus@8230
   124
            }
slouken@1895
   125
        }
slouken@1895
   126
    }
slouken@1895
   127
icculus@8230
   128
    return value;
slouken@172
   129
}
slouken@172
   130
slouken@1895
   131
static SInt32
icculus@8230
   132
GetHIDScaledCalibratedState(recDevice * pDevice, recElement * pElement, SInt32 min, SInt32 max)
slouken@172
   133
{
icculus@8230
   134
    const float deviceScale = max - min;
icculus@8230
   135
    const float readScale = pElement->maxReport - pElement->minReport;
icculus@8230
   136
    const SInt32 value = GetHIDElementState(pDevice, pElement);
icculus@8230
   137
    if (readScale == 0) {
slouken@1895
   138
        return value;           /* no scaling at all */
icculus@8230
   139
    }
icculus@8230
   140
    return ((value - pElement->minReport) * deviceScale / readScale) + min;
slouken@172
   141
}
slouken@172
   142
slouken@858
   143
slouken@1895
   144
static void
icculus@8230
   145
JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
slouken@858
   146
{
icculus@8230
   147
    recDevice *device = (recDevice *) ctx;
icculus@9433
   148
    device->removed = SDL_TRUE;
alfred@8858
   149
    device->deviceRef = NULL; // deviceRef was invalidated due to the remove
urkle@8173
   150
#if SDL_HAPTIC_IOKIT
icculus@8176
   151
    MacHaptic_MaybeRemoveDevice(device->ffservice);
urkle@8173
   152
#endif
slouken@9629
   153
slouken@10226
   154
    SDL_PrivateJoystickRemoved(device->instance_id);
slouken@858
   155
}
slouken@858
   156
slouken@858
   157
icculus@8230
   158
static void AddHIDElement(const void *value, void *parameter);
slouken@1895
   159
icculus@8230
   160
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
slouken@1895
   161
static void
icculus@8230
   162
AddHIDElements(CFArrayRef array, recDevice *pDevice)
slouken@172
   163
{
icculus@8230
   164
    const CFRange range = { 0, CFArrayGetCount(array) };
icculus@8230
   165
    CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
slouken@1895
   166
}
slouken@172
   167
icculus@8242
   168
static SDL_bool
icculus@8242
   169
ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem) {
icculus@8242
   170
    while (listitem) {
icculus@8242
   171
        if (listitem->cookie == cookie) {
icculus@8242
   172
            return SDL_TRUE;
icculus@8242
   173
        }
icculus@8242
   174
        listitem = listitem->pNext;
icculus@8242
   175
    }
icculus@8242
   176
    return SDL_FALSE;
icculus@8242
   177
}
icculus@8242
   178
icculus@8230
   179
/* See if we care about this HID element, and if so, note it in our recDevice. */
slouken@1895
   180
static void
icculus@8230
   181
AddHIDElement(const void *value, void *parameter)
slouken@172
   182
{
icculus@8230
   183
    recDevice *pDevice = (recDevice *) parameter;
icculus@8230
   184
    IOHIDElementRef refElement = (IOHIDElementRef) value;
icculus@8230
   185
    const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
slouken@172
   186
icculus@8230
   187
    if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
icculus@8242
   188
        const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
icculus@8230
   189
        const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
icculus@8230
   190
        const uint32_t usage = IOHIDElementGetUsage(refElement);
icculus@8230
   191
        recElement *element = NULL;
icculus@8230
   192
        recElement **headElement = NULL;
icculus@8230
   193
slouken@1895
   194
        /* look at types of interest */
icculus@8230
   195
        switch (IOHIDElementGetType(refElement)) {
icculus@8230
   196
            case kIOHIDElementTypeInput_Misc:
icculus@8230
   197
            case kIOHIDElementTypeInput_Button:
icculus@8230
   198
            case kIOHIDElementTypeInput_Axis: {
slouken@1895
   199
                switch (usagePage) {    /* only interested in kHIDPage_GenericDesktop and kHIDPage_Button */
icculus@8230
   200
                    case kHIDPage_GenericDesktop:
icculus@8230
   201
                        switch (usage) {
icculus@8230
   202
                            case kHIDUsage_GD_X:
icculus@8230
   203
                            case kHIDUsage_GD_Y:
icculus@8230
   204
                            case kHIDUsage_GD_Z:
icculus@8230
   205
                            case kHIDUsage_GD_Rx:
icculus@8230
   206
                            case kHIDUsage_GD_Ry:
icculus@8230
   207
                            case kHIDUsage_GD_Rz:
icculus@8230
   208
                            case kHIDUsage_GD_Slider:
icculus@8230
   209
                            case kHIDUsage_GD_Dial:
icculus@8230
   210
                            case kHIDUsage_GD_Wheel:
icculus@8242
   211
                                if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
icculus@8242
   212
                                    element = (recElement *) SDL_calloc(1, sizeof (recElement));
icculus@8242
   213
                                    if (element) {
icculus@8242
   214
                                        pDevice->axes++;
icculus@8242
   215
                                        headElement = &(pDevice->firstAxis);
icculus@8242
   216
                                    }
icculus@8230
   217
                                }
icculus@8230
   218
                                break;
icculus@8230
   219
icculus@8230
   220
                            case kHIDUsage_GD_Hatswitch:
icculus@8242
   221
                                if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
icculus@8242
   222
                                    element = (recElement *) SDL_calloc(1, sizeof (recElement));
icculus@8242
   223
                                    if (element) {
icculus@8242
   224
                                        pDevice->hats++;
icculus@8242
   225
                                        headElement = &(pDevice->firstHat);
icculus@8242
   226
                                    }
icculus@8230
   227
                                }
icculus@8230
   228
                                break;
slime73@9912
   229
                            case kHIDUsage_GD_DPadUp:
slime73@9912
   230
                            case kHIDUsage_GD_DPadDown:
slime73@9912
   231
                            case kHIDUsage_GD_DPadRight:
slime73@9912
   232
                            case kHIDUsage_GD_DPadLeft:
slime73@9976
   233
                            case kHIDUsage_GD_Start:
slime73@9976
   234
                            case kHIDUsage_GD_Select:
slouken@10218
   235
                            case kHIDUsage_GD_SystemMainMenu:
slime73@9912
   236
                                if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
slime73@9912
   237
                                    element = (recElement *) SDL_calloc(1, sizeof (recElement));
slime73@9912
   238
                                    if (element) {
slime73@9912
   239
                                        pDevice->buttons++;
slime73@9912
   240
                                        headElement = &(pDevice->firstButton);
slime73@9912
   241
                                    }
slime73@9912
   242
                                }
slime73@9912
   243
                                break;
slouken@1895
   244
                        }
icculus@8230
   245
                        break;
icculus@8230
   246
icculus@8230
   247
                    case kHIDPage_Simulation:
icculus@8230
   248
                        switch (usage) {
icculus@8230
   249
                            case kHIDUsage_Sim_Rudder:
icculus@8230
   250
                            case kHIDUsage_Sim_Throttle:
mistydemeo@10853
   251
                            case kHIDUsage_Sim_Accelerator:
mistydemeo@10853
   252
                            case kHIDUsage_Sim_Brake:
icculus@8242
   253
                                if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
icculus@8242
   254
                                    element = (recElement *) SDL_calloc(1, sizeof (recElement));
icculus@8242
   255
                                    if (element) {
icculus@8242
   256
                                        pDevice->axes++;
icculus@8242
   257
                                        headElement = &(pDevice->firstAxis);
icculus@8242
   258
                                    }
icculus@8230
   259
                                }
icculus@8230
   260
                                break;
slouken@7626
   261
icculus@8230
   262
                            default:
icculus@8230
   263
                                break;
icculus@8230
   264
                        }
icculus@8230
   265
                        break;
icculus@8230
   266
icculus@8230
   267
                    case kHIDPage_Button:
slime73@9912
   268
                    case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
icculus@8242
   269
                        if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
icculus@8242
   270
                            element = (recElement *) SDL_calloc(1, sizeof (recElement));
icculus@8242
   271
                            if (element) {
icculus@8242
   272
                                pDevice->buttons++;
icculus@8242
   273
                                headElement = &(pDevice->firstButton);
icculus@8242
   274
                            }
icculus@8230
   275
                        }
icculus@8230
   276
                        break;
icculus@8230
   277
icculus@8230
   278
                    default:
icculus@8230
   279
                        break;
slouken@1895
   280
                }
slouken@1895
   281
            }
icculus@8230
   282
            break;
slouken@172
   283
icculus@8230
   284
            case kIOHIDElementTypeCollection: {
icculus@8230
   285
                CFArrayRef array = IOHIDElementGetChildren(refElement);
icculus@8230
   286
                if (array) {
icculus@8230
   287
                    AddHIDElements(array, pDevice);
slouken@7026
   288
                }
slouken@1895
   289
            }
icculus@8230
   290
            break;
slouken@172
   291
icculus@8230
   292
            default:
icculus@8230
   293
                break;
icculus@8230
   294
        }
slouken@7026
   295
icculus@8230
   296
        if (element && headElement) {       /* add to list */
icculus@8230
   297
            recElement *elementPrevious = NULL;
icculus@8230
   298
            recElement *elementCurrent = *headElement;
icculus@8230
   299
            while (elementCurrent && usage >= elementCurrent->usage) {
icculus@8230
   300
                elementPrevious = elementCurrent;
icculus@8230
   301
                elementCurrent = elementCurrent->pNext;
slouken@7026
   302
            }
icculus@8230
   303
            if (elementPrevious) {
icculus@8230
   304
                elementPrevious->pNext = element;
icculus@8230
   305
            } else {
icculus@8230
   306
                *headElement = element;
slouken@6690
   307
            }
slouken@6690
   308
icculus@8230
   309
            element->elementRef = refElement;
icculus@8230
   310
            element->usagePage = usagePage;
icculus@8230
   311
            element->usage = usage;
icculus@8230
   312
            element->pNext = elementCurrent;
slouken@7026
   313
icculus@8230
   314
            element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
icculus@8230
   315
            element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
icculus@8242
   316
            element->cookie = IOHIDElementGetCookie(refElement);
slouken@172
   317
icculus@8230
   318
            pDevice->elements++;
slouken@7026
   319
        }
slouken@1895
   320
    }
slouken@172
   321
}
slouken@172
   322
icculus@8230
   323
static SDL_bool
icculus@8230
   324
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
slouken@172
   325
{
slouken@10595
   326
    const Uint16 BUS_USB = 0x03;
slouken@10595
   327
    const Uint16 BUS_BLUETOOTH = 0x05;
slouken@10595
   328
    Sint32 vendor = 0;
slouken@10595
   329
    Sint32 product = 0;
slouken@10595
   330
    Sint32 version = 0;
icculus@8230
   331
    CFTypeRef refCF = NULL;
icculus@8230
   332
    CFArrayRef array = NULL;
slouken@10598
   333
    Uint16 *guid16 = (Uint16 *)pDevice->guid.data;
icculus@8230
   334
icculus@8230
   335
    /* get usage page and usage */
icculus@8230
   336
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
icculus@8230
   337
    if (refCF) {
icculus@8230
   338
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
icculus@8230
   339
    }
icculus@8230
   340
    if (pDevice->usagePage != kHIDPage_GenericDesktop) {
icculus@8230
   341
        return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
slouken@1895
   342
    }
icculus@8230
   343
icculus@8230
   344
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
icculus@8230
   345
    if (refCF) {
icculus@8230
   346
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
icculus@8230
   347
    }
slouken@172
   348
icculus@8230
   349
    if ((pDevice->usage != kHIDUsage_GD_Joystick &&
icculus@8230
   350
         pDevice->usage != kHIDUsage_GD_GamePad &&
icculus@8230
   351
         pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
icculus@8230
   352
        return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
icculus@8230
   353
    }
icculus@8230
   354
icculus@8230
   355
    pDevice->deviceRef = hidDevice;
slouken@172
   356
icculus@8230
   357
    /* get device name */
icculus@8230
   358
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
icculus@8230
   359
    if (!refCF) {
icculus@8230
   360
        /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
icculus@8230
   361
        refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
icculus@8230
   362
    }
icculus@8230
   363
    if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
icculus@8230
   364
        SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
icculus@8230
   365
    }
slouken@1895
   366
icculus@8230
   367
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
icculus@8230
   368
    if (refCF) {
slouken@10595
   369
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
icculus@8230
   370
    }
icculus@8230
   371
icculus@8230
   372
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
icculus@8230
   373
    if (refCF) {
slouken@10595
   374
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
slouken@10595
   375
    }
slouken@10595
   376
slouken@10595
   377
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
slouken@10595
   378
    if (refCF) {
slouken@10595
   379
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
icculus@8230
   380
    }
slouken@2713
   381
slouken@10598
   382
    SDL_memset(pDevice->guid.data, 0, sizeof(pDevice->guid.data));
slouken@10595
   383
slouken@10595
   384
    if (vendor && product) {
slouken@10595
   385
        *guid16++ = SDL_SwapLE16(BUS_USB);
slouken@10595
   386
        *guid16++ = 0;
slouken@10595
   387
        *guid16++ = SDL_SwapLE16((Uint16)vendor);
slouken@10595
   388
        *guid16++ = 0;
slouken@10595
   389
        *guid16++ = SDL_SwapLE16((Uint16)product);
slouken@10595
   390
        *guid16++ = 0;
slouken@10595
   391
        *guid16++ = SDL_SwapLE16((Uint16)version);
slouken@10595
   392
        *guid16++ = 0;
slouken@10595
   393
    } else {
slouken@10595
   394
        *guid16++ = SDL_SwapLE16(BUS_BLUETOOTH);
icculus@8230
   395
        *guid16++ = 0;
icculus@8230
   396
        SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
icculus@8230
   397
    }
slouken@1895
   398
icculus@8230
   399
    array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
icculus@8230
   400
    if (array) {
icculus@8230
   401
        AddHIDElements(array, pDevice);
icculus@8230
   402
        CFRelease(array);
slouken@1895
   403
    }
icculus@8230
   404
icculus@8230
   405
    return SDL_TRUE;
slouken@172
   406
}
slouken@172
   407
icculus@9120
   408
static SDL_bool
icculus@9120
   409
JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
icculus@9120
   410
{
icculus@9120
   411
    recDevice *i;
icculus@9120
   412
    for (i = gpDeviceList; i != NULL; i = i->pNext) {
icculus@9120
   413
        if (i->deviceRef == ioHIDDeviceObject) {
icculus@9120
   414
            return SDL_TRUE;
icculus@9120
   415
        }
icculus@9120
   416
    }
icculus@9120
   417
    return SDL_FALSE;
icculus@9120
   418
}
icculus@9120
   419
slouken@172
   420
icculus@8230
   421
static void
icculus@8230
   422
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
slouken@6690
   423
{
icculus@9120
   424
    recDevice *device;
slouken@9629
   425
    int device_index = 0;
slime73@10176
   426
    io_service_t ioservice;
icculus@9120
   427
icculus@8230
   428
    if (res != kIOReturnSuccess) {
icculus@8230
   429
        return;
icculus@8230
   430
    }
slouken@7191
   431
icculus@9120
   432
    if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
icculus@9120
   433
        return;  /* IOKit sent us a duplicate. */
icculus@9120
   434
    }
icculus@9120
   435
icculus@9120
   436
    device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
slouken@7191
   437
icculus@8230
   438
    if (!device) {
icculus@8230
   439
        SDL_OutOfMemory();
icculus@8230
   440
        return;
icculus@8230
   441
    }
slouken@7191
   442
icculus@8230
   443
    if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
icculus@8230
   444
        SDL_free(device);
icculus@8230
   445
        return;   /* not a device we care about, probably. */
slouken@7191
   446
    }
slouken@7191
   447
icculus@8230
   448
    /* Get notified when this device is disconnected. */
icculus@8230
   449
    IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
icculus@8739
   450
    IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
icculus@8230
   451
slouken@7791
   452
    /* Allocate an instance ID for this device */
slouken@7791
   453
    device->instance_id = ++s_joystick_instance_id;
slouken@7791
   454
icculus@8230
   455
    /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
slime73@10176
   456
    ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
icculus@9432
   457
#if SDL_HAPTIC_IOKIT
slime73@10176
   458
    if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
slime73@10176
   459
        device->ffservice = ioservice;
slime73@10176
   460
        MacHaptic_MaybeAddDevice(ioservice);
slouken@7191
   461
    }
icculus@9632
   462
#endif
slouken@7191
   463
slouken@7191
   464
    /* Add device to the end of the list */
slouken@8986
   465
    if ( !gpDeviceList ) {
slouken@7191
   466
        gpDeviceList = device;
slouken@8986
   467
    } else {
slouken@7191
   468
        recDevice *curdevice;
slouken@7191
   469
slouken@7191
   470
        curdevice = gpDeviceList;
slouken@8986
   471
        while ( curdevice->pNext ) {
slouken@9629
   472
            ++device_index;
slouken@7191
   473
            curdevice = curdevice->pNext;
slouken@7191
   474
        }
slouken@7191
   475
        curdevice->pNext = device;
icculus@9862
   476
        ++device_index;  /* bump by one since we counted by pNext. */
slouken@7191
   477
    }
slouken@9629
   478
slouken@10226
   479
    SDL_PrivateJoystickAdded(device_index);
icculus@8230
   480
}
slouken@7191
   481
icculus@8230
   482
static SDL_bool
icculus@8230
   483
ConfigHIDManager(CFArrayRef matchingArray)
icculus@8230
   484
{
icculus@8230
   485
    CFRunLoopRef runloop = CFRunLoopGetCurrent();
icculus@8230
   486
icculus@8230
   487
    if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
icculus@8230
   488
        return SDL_FALSE;
icculus@8230
   489
    }
icculus@8230
   490
icculus@9370
   491
    IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
icculus@8230
   492
    IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
icculus@8739
   493
    IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
icculus@8230
   494
icculus@8739
   495
    while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
icculus@8230
   496
        /* no-op. Callback fires once per existing device. */
icculus@8230
   497
    }
icculus@8230
   498
icculus@8739
   499
    /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
icculus@8230
   500
icculus@8230
   501
    return SDL_TRUE;  /* good to go. */
slouken@6690
   502
}
slouken@6690
   503
slouken@6690
   504
icculus@8230
   505
static CFDictionaryRef
icculus@8230
   506
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
slouken@6690
   507
{
icculus@8230
   508
    CFDictionaryRef retval = NULL;
icculus@8230
   509
    CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
icculus@8230
   510
    CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
icculus@8230
   511
    const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
icculus@8230
   512
    const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
icculus@8230
   513
icculus@8230
   514
    if (pageNumRef && usageNumRef) {
icculus@8230
   515
        retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
icculus@8230
   516
    }
icculus@8230
   517
icculus@8230
   518
    if (pageNumRef) {
icculus@8230
   519
        CFRelease(pageNumRef);
icculus@8230
   520
    }
icculus@8230
   521
    if (usageNumRef) {
icculus@8245
   522
        CFRelease(usageNumRef);
icculus@8230
   523
    }
icculus@8230
   524
icculus@8230
   525
    if (!retval) {
icculus@8230
   526
        *okay = 0;
icculus@8230
   527
    }
slouken@7191
   528
icculus@8230
   529
    return retval;
icculus@8230
   530
}
icculus@8230
   531
icculus@8230
   532
static SDL_bool
icculus@8230
   533
CreateHIDManager(void)
icculus@8230
   534
{
icculus@8230
   535
    SDL_bool retval = SDL_FALSE;
icculus@8230
   536
    int okay = 1;
icculus@8230
   537
    const void *vals[] = {
icculus@8230
   538
        (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
icculus@8230
   539
        (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
icculus@8230
   540
        (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
icculus@8230
   541
    };
icculus@8230
   542
    const size_t numElements = SDL_arraysize(vals);
icculus@8230
   543
    CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
icculus@8230
   544
    size_t i;
icculus@8230
   545
icculus@8230
   546
    for (i = 0; i < numElements; i++) {
icculus@8230
   547
        if (vals[i]) {
icculus@8230
   548
            CFRelease((CFTypeRef) vals[i]);
slouken@7191
   549
        }
slouken@7191
   550
    }
icculus@8230
   551
icculus@8230
   552
    if (array) {
icculus@8230
   553
        hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
icculus@8230
   554
        if (hidman != NULL) {
icculus@8230
   555
            retval = ConfigHIDManager(array);
icculus@8230
   556
        }
icculus@8230
   557
        CFRelease(array);
icculus@8230
   558
    }
icculus@8230
   559
icculus@8230
   560
    return retval;
slouken@6690
   561
}
slouken@7191
   562
slouken@6690
   563
slouken@172
   564
/* Function to scan the system for joysticks.
slouken@172
   565
 * Joystick 0 should be the system default joystick.
slouken@172
   566
 * This function should return the number of available joysticks, or -1
slouken@172
   567
 * on an unrecoverable fatal error.
slouken@172
   568
 */
slouken@1895
   569
int
slouken@1895
   570
SDL_SYS_JoystickInit(void)
slouken@172
   571
{
slouken@1895
   572
    if (gpDeviceList) {
icculus@7037
   573
        return SDL_SetError("Joystick: Device list already inited.");
slouken@1895
   574
    }
slouken@172
   575
icculus@8230
   576
    if (!CreateHIDManager()) {
icculus@8230
   577
        return SDL_SetError("Joystick: Couldn't initialize HID Manager");
slouken@1895
   578
    }
slouken@172
   579
slouken@6690
   580
    return SDL_SYS_NumJoysticks();
slouken@172
   581
}
slouken@172
   582
slouken@6707
   583
/* Function to return the number of joystick devices plugged in right now */
slouken@6707
   584
int
philipp@10617
   585
SDL_SYS_NumJoysticks(void)
slouken@6707
   586
{
slouken@7191
   587
    recDevice *device = gpDeviceList;
slouken@6707
   588
    int nJoySticks = 0;
slouken@7191
   589
icculus@8230
   590
    while (device) {
icculus@8230
   591
        if (!device->removed) {
slouken@7191
   592
            nJoySticks++;
icculus@8230
   593
        }
slouken@6707
   594
        device = device->pNext;
slouken@7191
   595
    }
slouken@6707
   596
slouken@7191
   597
    return nJoySticks;
slouken@6707
   598
}
slouken@6707
   599
slouken@6707
   600
/* Function to cause any queued joystick insertions to be processed
slouken@6707
   601
 */
slouken@6707
   602
void
philipp@10617
   603
SDL_SYS_JoystickDetect(void)
slouken@6707
   604
{
slouken@9629
   605
    recDevice *device = gpDeviceList;
slouken@9629
   606
    while (device) {
slouken@9629
   607
        if (device->removed) {
slouken@9629
   608
            device = FreeDevice(device);
slouken@9629
   609
        } else {
slouken@9629
   610
            device = device->pNext;
slouken@7191
   611
        }
slouken@7191
   612
    }
alfred@9375
   613
icculus@10780
   614
	/* run this after the checks above so we don't set device->removed and delete the device before
icculus@10780
   615
	   SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
alfred@9375
   616
	while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
alfred@9375
   617
		/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
alfred@9375
   618
	}
slouken@6707
   619
}
slouken@6707
   620
slouken@172
   621
/* Function to get the device-dependent name of a joystick */
slouken@1895
   622
const char *
slouken@6707
   623
SDL_SYS_JoystickNameForDeviceIndex(int device_index)
slouken@172
   624
{
slouken@9629
   625
    recDevice *device = GetDeviceForIndex(device_index);
slouken@9629
   626
    return device ? device->product : "UNKNOWN";
slouken@172
   627
}
slouken@172
   628
slouken@6707
   629
/* Function to return the instance id of the joystick at device_index
slouken@6707
   630
 */
slouken@6707
   631
SDL_JoystickID
slouken@6707
   632
SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
slouken@6707
   633
{
slouken@9629
   634
    recDevice *device = GetDeviceForIndex(device_index);
slouken@9629
   635
    return device ? device->instance_id : 0;
slouken@6707
   636
}
slouken@6707
   637
slouken@172
   638
/* Function to open a joystick for use.
philipp@9380
   639
 * The joystick to open is specified by the device index.
slouken@172
   640
 * This should fill the nbuttons and naxes fields of the joystick structure.
slouken@172
   641
 * It returns 0, or -1 if there is an error.
slouken@172
   642
 */
slouken@1895
   643
int
slouken@6690
   644
SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
slouken@172
   645
{
slouken@9629
   646
    recDevice *device = GetDeviceForIndex(device_index);
slouken@172
   647
slouken@7191
   648
    joystick->instance_id = device->instance_id;
slouken@1895
   649
    joystick->hwdata = device;
slouken@7191
   650
    joystick->name = device->product;
slouken@172
   651
slouken@7191
   652
    joystick->naxes = device->axes;
slouken@7191
   653
    joystick->nhats = device->hats;
slouken@7191
   654
    joystick->nballs = 0;
slouken@7191
   655
    joystick->nbuttons = device->buttons;
slouken@1895
   656
    return 0;
slouken@172
   657
}
slouken@172
   658
slouken@6707
   659
/* Function to query if the joystick is currently attached
philipp@9380
   660
 * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
slouken@6690
   661
 */
slouken@6707
   662
SDL_bool
slouken@6707
   663
SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
slouken@6690
   664
{
icculus@9433
   665
    return joystick->hwdata != NULL;
slouken@6690
   666
}
slouken@6690
   667
slouken@172
   668
/* Function to update the state of a joystick - called as a device poll.
slouken@172
   669
 * This function shouldn't update the joystick structure directly,
slouken@172
   670
 * but instead should call SDL_PrivateJoystick*() to deliver events
slouken@172
   671
 * and update joystick device state.
slouken@172
   672
 */
slouken@1895
   673
void
slouken@1895
   674
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
slouken@172
   675
{
slouken@7191
   676
    recDevice *device = joystick->hwdata;
slouken@1895
   677
    recElement *element;
slouken@2287
   678
    SInt32 value, range;
slouken@1895
   679
    int i;
slouken@858
   680
icculus@8230
   681
    if (!device) {
slouken@7191
   682
        return;
icculus@8230
   683
    }
slouken@858
   684
slouken@6690
   685
    if (device->removed) {      /* device was unplugged; ignore it. */
icculus@9433
   686
        if (joystick->hwdata) {
icculus@9433
   687
            joystick->force_recentering = SDL_TRUE;
icculus@9433
   688
            joystick->hwdata = NULL;
icculus@9433
   689
        }
slouken@1895
   690
        return;
slouken@1895
   691
    }
slouken@858
   692
slouken@1895
   693
    element = device->firstAxis;
slouken@1895
   694
    i = 0;
slouken@1895
   695
    while (element) {
icculus@8230
   696
        value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
slouken@10745
   697
        SDL_PrivateJoystickAxis(joystick, i, value);
slouken@1895
   698
        element = element->pNext;
slouken@1895
   699
        ++i;
slouken@1895
   700
    }
slouken@1895
   701
slouken@1895
   702
    element = device->firstButton;
slouken@1895
   703
    i = 0;
slouken@1895
   704
    while (element) {
icculus@8230
   705
        value = GetHIDElementState(device, element);
icculus@8230
   706
        if (value > 1) {          /* handle pressure-sensitive buttons */
slouken@629
   707
            value = 1;
icculus@8230
   708
        }
slouken@10745
   709
        SDL_PrivateJoystickButton(joystick, i, value);
slouken@1895
   710
        element = element->pNext;
slouken@1895
   711
        ++i;
slouken@1895
   712
    }
slouken@1895
   713
slouken@1895
   714
    element = device->firstHat;
slouken@1895
   715
    i = 0;
slouken@1895
   716
    while (element) {
slouken@1895
   717
        Uint8 pos = 0;
slouken@172
   718
slouken@2287
   719
        range = (element->max - element->min + 1);
icculus@8230
   720
        value = GetHIDElementState(device, element) - element->min;
icculus@8230
   721
        if (range == 4) {         /* 4 position hatswitch - scale up value */
slouken@1895
   722
            value *= 2;
icculus@8230
   723
        } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
slouken@1895
   724
            value = -1;
icculus@8230
   725
        }
slouken@1895
   726
        switch (value) {
slouken@1895
   727
        case 0:
slouken@1895
   728
            pos = SDL_HAT_UP;
slouken@1895
   729
            break;
slouken@1895
   730
        case 1:
slouken@1895
   731
            pos = SDL_HAT_RIGHTUP;
slouken@1895
   732
            break;
slouken@1895
   733
        case 2:
slouken@1895
   734
            pos = SDL_HAT_RIGHT;
slouken@1895
   735
            break;
slouken@1895
   736
        case 3:
slouken@1895
   737
            pos = SDL_HAT_RIGHTDOWN;
slouken@1895
   738
            break;
slouken@1895
   739
        case 4:
slouken@1895
   740
            pos = SDL_HAT_DOWN;
slouken@1895
   741
            break;
slouken@1895
   742
        case 5:
slouken@1895
   743
            pos = SDL_HAT_LEFTDOWN;
slouken@1895
   744
            break;
slouken@1895
   745
        case 6:
slouken@1895
   746
            pos = SDL_HAT_LEFT;
slouken@1895
   747
            break;
slouken@1895
   748
        case 7:
slouken@1895
   749
            pos = SDL_HAT_LEFTUP;
slouken@1895
   750
            break;
slouken@1895
   751
        default:
slouken@1895
   752
            /* Every other value is mapped to center. We do that because some
slouken@1895
   753
             * joysticks use 8 and some 15 for this value, and apparently
slouken@1895
   754
             * there are even more variants out there - so we try to be generous.
slouken@1895
   755
             */
slouken@1895
   756
            pos = SDL_HAT_CENTERED;
slouken@1895
   757
            break;
slouken@1895
   758
        }
icculus@8230
   759
slouken@10745
   760
        SDL_PrivateJoystickHat(joystick, i, pos);
icculus@8230
   761
slouken@1895
   762
        element = element->pNext;
slouken@1895
   763
        ++i;
slouken@1895
   764
    }
slouken@172
   765
}
slouken@172
   766
slouken@172
   767
/* Function to close a joystick after use */
slouken@1895
   768
void
slouken@1895
   769
SDL_SYS_JoystickClose(SDL_Joystick * joystick)
slouken@7191
   770
{
slouken@172
   771
}
slouken@172
   772
slouken@172
   773
/* Function to perform any system-specific joystick related cleanup */
slouken@1895
   774
void
slouken@1895
   775
SDL_SYS_JoystickQuit(void)
slouken@172
   776
{
icculus@8230
   777
    while (FreeDevice(gpDeviceList)) {
icculus@8230
   778
        /* spin */
icculus@8230
   779
    }
slouken@7191
   780
icculus@8230
   781
    if (hidman) {
icculus@8739
   782
        IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
icculus@8230
   783
        IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
icculus@8230
   784
        CFRelease(hidman);
icculus@8230
   785
        hidman = NULL;
slouken@7191
   786
    }
slouken@6690
   787
}
slouken@6690
   788
slouken@6690
   789
slouken@6738
   790
SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
slouken@6690
   791
{
slouken@9629
   792
    recDevice *device = GetDeviceForIndex(device_index);
slouken@9629
   793
    SDL_JoystickGUID guid;
slouken@9629
   794
    if (device) {
slouken@9629
   795
        guid = device->guid;
slouken@9629
   796
    } else {
slouken@9629
   797
        SDL_zero(guid);
icculus@8230
   798
    }
slouken@9629
   799
    return guid;
slouken@6690
   800
}
slouken@6690
   801
slouken@6738
   802
SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
slouken@6690
   803
{
slouken@7191
   804
    return joystick->hwdata->guid;
slouken@172
   805
}
slouken@1635
   806
slouken@1635
   807
#endif /* SDL_JOYSTICK_IOKIT */
slouken@7026
   808
slouken@1895
   809
/* vi: set ts=4 sw=4 expandtab: */