src/joystick/darwin/SDL_sysjoystick.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 26 Aug 2016 12:18:08 -0700
changeset 10226 cb13d22b7f09
parent 10218 42768c568a50
child 10595 9da2701eeb4a
permissions -rw-r--r--
Added SDL_PrivateJoystickAdded() and SDL_PrivateJoystickRemoved()
Updated the removal code to iterate over all joystick add messages instead of just the first one.
slouken@172
     1
/*
slouken@5535
     2
  Simple DirectMedia Layer
slouken@9998
     3
  Copyright (C) 1997-2016 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);
icculus@8230
   159
icculus@8230
   160
/* Call AddHIDElement() on all elements in an array of IOHIDElementRefs */
icculus@8230
   161
static void
icculus@8230
   162
AddHIDElements(CFArrayRef array, recDevice *pDevice)
slouken@6690
   163
{
icculus@8230
   164
    const CFRange range = { 0, CFArrayGetCount(array) };
icculus@8230
   165
    CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
slouken@6690
   166
}
slouken@6690
   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. */
icculus@8230
   180
static void
icculus@8230
   181
AddHIDElement(const void *value, void *parameter)
icculus@8230
   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@858
   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;
slouken@172
   193
icculus@8230
   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: {
icculus@8230
   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;
slouken@858
   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;
icculus@8230
   244
                        }
icculus@8230
   245
                        break;
slouken@6690
   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:
icculus@8242
   251
                                if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
icculus@8242
   252
                                    element = (recElement *) SDL_calloc(1, sizeof (recElement));
icculus@8242
   253
                                    if (element) {
icculus@8242
   254
                                        pDevice->axes++;
icculus@8242
   255
                                        headElement = &(pDevice->firstAxis);
icculus@8242
   256
                                    }
icculus@8230
   257
                                }
icculus@8230
   258
                                break;
slouken@7191
   259
icculus@8230
   260
                            default:
icculus@8230
   261
                                break;
icculus@8230
   262
                        }
icculus@8230
   263
                        break;
slouken@7191
   264
icculus@8230
   265
                    case kHIDPage_Button:
slime73@9912
   266
                    case kHIDPage_Consumer: /* e.g. 'pause' button on Steelseries MFi gamepads. */
icculus@8242
   267
                        if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
icculus@8242
   268
                            element = (recElement *) SDL_calloc(1, sizeof (recElement));
icculus@8242
   269
                            if (element) {
icculus@8242
   270
                                pDevice->buttons++;
icculus@8242
   271
                                headElement = &(pDevice->firstButton);
icculus@8242
   272
                            }
icculus@8230
   273
                        }
icculus@8230
   274
                        break;
slouken@7191
   275
icculus@8230
   276
                    default:
icculus@8230
   277
                        break;
icculus@8230
   278
                }
slouken@7191
   279
            }
icculus@8230
   280
            break;
icculus@8230
   281
icculus@8230
   282
            case kIOHIDElementTypeCollection: {
icculus@8230
   283
                CFArrayRef array = IOHIDElementGetChildren(refElement);
icculus@8230
   284
                if (array) {
icculus@8230
   285
                    AddHIDElements(array, pDevice);
icculus@8230
   286
                }
icculus@8230
   287
            }
icculus@8230
   288
            break;
icculus@8230
   289
icculus@8230
   290
            default:
icculus@8230
   291
                break;
slouken@7191
   292
        }
slouken@1895
   293
icculus@8230
   294
        if (element && headElement) {       /* add to list */
icculus@8230
   295
            recElement *elementPrevious = NULL;
icculus@8230
   296
            recElement *elementCurrent = *headElement;
icculus@8230
   297
            while (elementCurrent && usage >= elementCurrent->usage) {
icculus@8230
   298
                elementPrevious = elementCurrent;
icculus@8230
   299
                elementCurrent = elementCurrent->pNext;
slouken@1895
   300
            }
icculus@8230
   301
            if (elementPrevious) {
icculus@8230
   302
                elementPrevious->pNext = element;
icculus@8230
   303
            } else {
icculus@8230
   304
                *headElement = element;
slouken@1895
   305
            }
slouken@1895
   306
icculus@8230
   307
            element->elementRef = refElement;
icculus@8230
   308
            element->usagePage = usagePage;
icculus@8230
   309
            element->usage = usage;
icculus@8230
   310
            element->pNext = elementCurrent;
slouken@7026
   311
icculus@8230
   312
            element->minReport = element->min = (SInt32) IOHIDElementGetLogicalMin(refElement);
icculus@8230
   313
            element->maxReport = element->max = (SInt32) IOHIDElementGetLogicalMax(refElement);
icculus@8242
   314
            element->cookie = IOHIDElementGetCookie(refElement);
slouken@1895
   315
icculus@8230
   316
            pDevice->elements++;
slouken@7026
   317
        }
slouken@1895
   318
    }
slouken@172
   319
}
slouken@172
   320
icculus@8230
   321
static SDL_bool
icculus@8230
   322
GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
icculus@8230
   323
{
icculus@8230
   324
    Uint32 *guid32 = NULL;
icculus@8230
   325
    CFTypeRef refCF = NULL;
icculus@8230
   326
    CFArrayRef array = NULL;
slouken@172
   327
icculus@8230
   328
    /* get usage page and usage */
icculus@8230
   329
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
icculus@8230
   330
    if (refCF) {
icculus@8230
   331
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
slouken@1895
   332
    }
icculus@8230
   333
    if (pDevice->usagePage != kHIDPage_GenericDesktop) {
icculus@8230
   334
        return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
icculus@8230
   335
    }
icculus@8230
   336
icculus@8230
   337
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
icculus@8230
   338
    if (refCF) {
icculus@8230
   339
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
icculus@8230
   340
    }
icculus@8230
   341
icculus@8230
   342
    if ((pDevice->usage != kHIDUsage_GD_Joystick &&
icculus@8230
   343
         pDevice->usage != kHIDUsage_GD_GamePad &&
icculus@8230
   344
         pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
icculus@8230
   345
        return SDL_FALSE; /* Filter device list to non-keyboard/mouse stuff */
icculus@8230
   346
    }
icculus@8230
   347
icculus@8230
   348
    pDevice->deviceRef = hidDevice;
icculus@8230
   349
icculus@8230
   350
    /* get device name */
icculus@8230
   351
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
icculus@8230
   352
    if (!refCF) {
icculus@8230
   353
        /* Maybe we can't get "AwesomeJoystick2000", but we can get "Logitech"? */
icculus@8230
   354
        refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
icculus@8230
   355
    }
icculus@8230
   356
    if ((!refCF) || (!CFStringGetCString(refCF, pDevice->product, sizeof (pDevice->product), kCFStringEncodingUTF8))) {
icculus@8230
   357
        SDL_strlcpy(pDevice->product, "Unidentified joystick", sizeof (pDevice->product));
icculus@8230
   358
    }
icculus@8230
   359
icculus@8230
   360
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
icculus@8230
   361
    if (refCF) {
icculus@8230
   362
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->guid.data[0]);
icculus@8230
   363
    }
icculus@8230
   364
icculus@8230
   365
    refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
icculus@8230
   366
    if (refCF) {
icculus@8230
   367
        CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->guid.data[8]);
icculus@8230
   368
    }
icculus@8230
   369
icculus@8230
   370
    /* Check to make sure we have a vendor and product ID
icculus@8230
   371
       If we don't, use the same algorithm as the Linux code for Bluetooth devices */
icculus@8230
   372
    guid32 = (Uint32*)pDevice->guid.data;
icculus@8230
   373
    if (!guid32[0] && !guid32[1]) {
icculus@8230
   374
        /* If we don't have a vendor and product ID this is probably a Bluetooth device */
icculus@8230
   375
        const Uint16 BUS_BLUETOOTH = 0x05;
icculus@8230
   376
        Uint16 *guid16 = (Uint16 *)guid32;
icculus@8230
   377
        *guid16++ = BUS_BLUETOOTH;
icculus@8230
   378
        *guid16++ = 0;
icculus@8230
   379
        SDL_strlcpy((char*)guid16, pDevice->product, sizeof(pDevice->guid.data) - 4);
icculus@8230
   380
    }
icculus@8230
   381
icculus@8230
   382
    array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
icculus@8230
   383
    if (array) {
icculus@8230
   384
        AddHIDElements(array, pDevice);
icculus@8230
   385
        CFRelease(array);
icculus@8230
   386
    }
icculus@8230
   387
icculus@8230
   388
    return SDL_TRUE;
slouken@172
   389
}
slouken@172
   390
icculus@9120
   391
static SDL_bool
icculus@9120
   392
JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
icculus@9120
   393
{
icculus@9120
   394
    recDevice *i;
icculus@9120
   395
    for (i = gpDeviceList; i != NULL; i = i->pNext) {
icculus@9120
   396
        if (i->deviceRef == ioHIDDeviceObject) {
icculus@9120
   397
            return SDL_TRUE;
icculus@9120
   398
        }
icculus@9120
   399
    }
icculus@9120
   400
    return SDL_FALSE;
icculus@9120
   401
}
icculus@9120
   402
slouken@172
   403
slouken@1895
   404
static void
icculus@8230
   405
JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
slouken@172
   406
{
icculus@9120
   407
    recDevice *device;
slouken@9629
   408
    int device_index = 0;
slime73@10176
   409
    io_service_t ioservice;
icculus@9120
   410
icculus@8230
   411
    if (res != kIOReturnSuccess) {
icculus@8230
   412
        return;
slouken@1895
   413
    }
slouken@172
   414
icculus@9120
   415
    if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
icculus@9120
   416
        return;  /* IOKit sent us a duplicate. */
icculus@9120
   417
    }
icculus@9120
   418
icculus@9120
   419
    device = (recDevice *) SDL_calloc(1, sizeof(recDevice));
slouken@172
   420
icculus@8230
   421
    if (!device) {
icculus@8230
   422
        SDL_OutOfMemory();
icculus@8230
   423
        return;
icculus@8230
   424
    }
slouken@1895
   425
icculus@8230
   426
    if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
icculus@8230
   427
        SDL_free(device);
icculus@8230
   428
        return;   /* not a device we care about, probably. */
icculus@8230
   429
    }
slouken@2713
   430
icculus@8230
   431
    /* Get notified when this device is disconnected. */
icculus@8230
   432
    IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
icculus@8739
   433
    IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
slouken@7191
   434
slouken@7791
   435
    /* Allocate an instance ID for this device */
slouken@7791
   436
    device->instance_id = ++s_joystick_instance_id;
slouken@7791
   437
icculus@8230
   438
    /* We have to do some storage of the io_service_t for SDL_HapticOpenFromJoystick */
slime73@10176
   439
    ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
icculus@9432
   440
#if SDL_HAPTIC_IOKIT
slime73@10176
   441
    if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
slime73@10176
   442
        device->ffservice = ioservice;
slime73@10176
   443
        MacHaptic_MaybeAddDevice(ioservice);
slouken@7191
   444
    }
icculus@9632
   445
#endif
slouken@7191
   446
slouken@7191
   447
    /* Add device to the end of the list */
slouken@8986
   448
    if ( !gpDeviceList ) {
slouken@7191
   449
        gpDeviceList = device;
slouken@8986
   450
    } else {
slouken@7191
   451
        recDevice *curdevice;
slouken@7191
   452
slouken@7191
   453
        curdevice = gpDeviceList;
slouken@8986
   454
        while ( curdevice->pNext ) {
slouken@9629
   455
            ++device_index;
slouken@7191
   456
            curdevice = curdevice->pNext;
slouken@7191
   457
        }
slouken@7191
   458
        curdevice->pNext = device;
icculus@9862
   459
        ++device_index;  /* bump by one since we counted by pNext. */
slouken@7191
   460
    }
slouken@9629
   461
slouken@10226
   462
    SDL_PrivateJoystickAdded(device_index);
slouken@6690
   463
}
slouken@6690
   464
icculus@8230
   465
static SDL_bool
icculus@8230
   466
ConfigHIDManager(CFArrayRef matchingArray)
icculus@8230
   467
{
icculus@8230
   468
    CFRunLoopRef runloop = CFRunLoopGetCurrent();
slouken@6690
   469
icculus@8230
   470
    if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
icculus@8230
   471
        return SDL_FALSE;
icculus@8230
   472
    }
icculus@8230
   473
icculus@9370
   474
    IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
icculus@8230
   475
    IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
icculus@8739
   476
    IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
icculus@8230
   477
icculus@8739
   478
    while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
icculus@8230
   479
        /* no-op. Callback fires once per existing device. */
icculus@8230
   480
    }
icculus@8230
   481
icculus@8739
   482
    /* future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now. */
icculus@8230
   483
icculus@8230
   484
    return SDL_TRUE;  /* good to go. */
icculus@8230
   485
}
icculus@8230
   486
icculus@8230
   487
icculus@8230
   488
static CFDictionaryRef
icculus@8230
   489
CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
slouken@6690
   490
{
icculus@8230
   491
    CFDictionaryRef retval = NULL;
icculus@8230
   492
    CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
icculus@8230
   493
    CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
icculus@8230
   494
    const void *keys[2] = { (void *) CFSTR(kIOHIDDeviceUsagePageKey), (void *) CFSTR(kIOHIDDeviceUsageKey) };
icculus@8230
   495
    const void *vals[2] = { (void *) pageNumRef, (void *) usageNumRef };
slouken@7191
   496
icculus@8230
   497
    if (pageNumRef && usageNumRef) {
icculus@8230
   498
        retval = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
icculus@8230
   499
    }
icculus@8230
   500
icculus@8230
   501
    if (pageNumRef) {
icculus@8230
   502
        CFRelease(pageNumRef);
icculus@8230
   503
    }
icculus@8230
   504
    if (usageNumRef) {
icculus@8245
   505
        CFRelease(usageNumRef);
icculus@8230
   506
    }
icculus@8230
   507
icculus@8230
   508
    if (!retval) {
icculus@8230
   509
        *okay = 0;
icculus@8230
   510
    }
icculus@8230
   511
icculus@8230
   512
    return retval;
icculus@8230
   513
}
icculus@8230
   514
icculus@8230
   515
static SDL_bool
icculus@8230
   516
CreateHIDManager(void)
icculus@8230
   517
{
icculus@8230
   518
    SDL_bool retval = SDL_FALSE;
icculus@8230
   519
    int okay = 1;
icculus@8230
   520
    const void *vals[] = {
icculus@8230
   521
        (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
icculus@8230
   522
        (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
icculus@8230
   523
        (void *) CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
icculus@8230
   524
    };
icculus@8230
   525
    const size_t numElements = SDL_arraysize(vals);
icculus@8230
   526
    CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
icculus@8230
   527
    size_t i;
icculus@8230
   528
icculus@8230
   529
    for (i = 0; i < numElements; i++) {
icculus@8230
   530
        if (vals[i]) {
icculus@8230
   531
            CFRelease((CFTypeRef) vals[i]);
slouken@7191
   532
        }
slouken@7191
   533
    }
icculus@8230
   534
icculus@8230
   535
    if (array) {
icculus@8230
   536
        hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
icculus@8230
   537
        if (hidman != NULL) {
icculus@8230
   538
            retval = ConfigHIDManager(array);
icculus@8230
   539
        }
icculus@8230
   540
        CFRelease(array);
icculus@8230
   541
    }
icculus@8230
   542
icculus@8230
   543
    return retval;
slouken@6690
   544
}
slouken@7191
   545
slouken@6690
   546
slouken@172
   547
/* Function to scan the system for joysticks.
slouken@172
   548
 * Joystick 0 should be the system default joystick.
slouken@172
   549
 * This function should return the number of available joysticks, or -1
slouken@172
   550
 * on an unrecoverable fatal error.
slouken@172
   551
 */
slouken@1895
   552
int
slouken@1895
   553
SDL_SYS_JoystickInit(void)
slouken@172
   554
{
slouken@1895
   555
    if (gpDeviceList) {
icculus@7037
   556
        return SDL_SetError("Joystick: Device list already inited.");
slouken@1895
   557
    }
slouken@172
   558
icculus@8230
   559
    if (!CreateHIDManager()) {
icculus@8230
   560
        return SDL_SetError("Joystick: Couldn't initialize HID Manager");
slouken@1895
   561
    }
slouken@172
   562
slouken@6690
   563
    return SDL_SYS_NumJoysticks();
slouken@172
   564
}
slouken@172
   565
slouken@6707
   566
/* Function to return the number of joystick devices plugged in right now */
slouken@6707
   567
int
slouken@6707
   568
SDL_SYS_NumJoysticks()
slouken@6707
   569
{
slouken@7191
   570
    recDevice *device = gpDeviceList;
slouken@6707
   571
    int nJoySticks = 0;
slouken@7191
   572
icculus@8230
   573
    while (device) {
icculus@8230
   574
        if (!device->removed) {
slouken@7191
   575
            nJoySticks++;
icculus@8230
   576
        }
slouken@6707
   577
        device = device->pNext;
slouken@7191
   578
    }
slouken@6707
   579
slouken@7191
   580
    return nJoySticks;
slouken@6707
   581
}
slouken@6707
   582
slouken@6707
   583
/* Function to cause any queued joystick insertions to be processed
slouken@6707
   584
 */
slouken@6707
   585
void
slouken@6707
   586
SDL_SYS_JoystickDetect()
slouken@6707
   587
{
slouken@9629
   588
    recDevice *device = gpDeviceList;
slouken@9629
   589
    while (device) {
slouken@9629
   590
        if (device->removed) {
slouken@9629
   591
            device = FreeDevice(device);
slouken@9629
   592
        } else {
slouken@9629
   593
            device = device->pNext;
slouken@7191
   594
        }
slouken@7191
   595
    }
alfred@9375
   596
alfred@9375
   597
	// run this after the checks above so we don't set device->removed and delete the device before
alfred@9375
   598
	// SDL_SYS_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device
alfred@9375
   599
	while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE,0,TRUE) == kCFRunLoopRunHandledSource) {
alfred@9375
   600
		/* no-op. Pending callbacks will fire in CFRunLoopRunInMode(). */
alfred@9375
   601
	}
slouken@6707
   602
}
slouken@6707
   603
slouken@172
   604
/* Function to get the device-dependent name of a joystick */
slouken@1895
   605
const char *
slouken@6707
   606
SDL_SYS_JoystickNameForDeviceIndex(int device_index)
slouken@172
   607
{
slouken@9629
   608
    recDevice *device = GetDeviceForIndex(device_index);
slouken@9629
   609
    return device ? device->product : "UNKNOWN";
slouken@172
   610
}
slouken@172
   611
slouken@6707
   612
/* Function to return the instance id of the joystick at device_index
slouken@6707
   613
 */
slouken@6707
   614
SDL_JoystickID
slouken@6707
   615
SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
slouken@6707
   616
{
slouken@9629
   617
    recDevice *device = GetDeviceForIndex(device_index);
slouken@9629
   618
    return device ? device->instance_id : 0;
slouken@6707
   619
}
slouken@6707
   620
slouken@172
   621
/* Function to open a joystick for use.
philipp@9380
   622
 * The joystick to open is specified by the device index.
slouken@172
   623
 * This should fill the nbuttons and naxes fields of the joystick structure.
slouken@172
   624
 * It returns 0, or -1 if there is an error.
slouken@172
   625
 */
slouken@1895
   626
int
slouken@6690
   627
SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
slouken@172
   628
{
slouken@9629
   629
    recDevice *device = GetDeviceForIndex(device_index);
slouken@172
   630
slouken@7191
   631
    joystick->instance_id = device->instance_id;
slouken@1895
   632
    joystick->hwdata = device;
slouken@7191
   633
    joystick->name = device->product;
slouken@172
   634
slouken@7191
   635
    joystick->naxes = device->axes;
slouken@7191
   636
    joystick->nhats = device->hats;
slouken@7191
   637
    joystick->nballs = 0;
slouken@7191
   638
    joystick->nbuttons = device->buttons;
slouken@1895
   639
    return 0;
slouken@172
   640
}
slouken@172
   641
slouken@6707
   642
/* Function to query if the joystick is currently attached
philipp@9380
   643
 * It returns SDL_TRUE if attached, SDL_FALSE otherwise.
slouken@6690
   644
 */
slouken@6707
   645
SDL_bool
slouken@6707
   646
SDL_SYS_JoystickAttached(SDL_Joystick * joystick)
slouken@6690
   647
{
icculus@9433
   648
    return joystick->hwdata != NULL;
slouken@6690
   649
}
slouken@6690
   650
slouken@172
   651
/* Function to update the state of a joystick - called as a device poll.
slouken@172
   652
 * This function shouldn't update the joystick structure directly,
slouken@172
   653
 * but instead should call SDL_PrivateJoystick*() to deliver events
slouken@172
   654
 * and update joystick device state.
slouken@172
   655
 */
slouken@1895
   656
void
slouken@1895
   657
SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
slouken@172
   658
{
slouken@7191
   659
    recDevice *device = joystick->hwdata;
slouken@1895
   660
    recElement *element;
slouken@2287
   661
    SInt32 value, range;
slouken@1895
   662
    int i;
slouken@858
   663
icculus@8230
   664
    if (!device) {
slouken@7191
   665
        return;
icculus@8230
   666
    }
slouken@6690
   667
slouken@1895
   668
    if (device->removed) {      /* device was unplugged; ignore it. */
icculus@9433
   669
        if (joystick->hwdata) {
icculus@9433
   670
            joystick->force_recentering = SDL_TRUE;
icculus@9433
   671
            joystick->hwdata = NULL;
icculus@9433
   672
        }
slouken@1895
   673
        return;
slouken@1895
   674
    }
slouken@858
   675
slouken@1895
   676
    element = device->firstAxis;
slouken@1895
   677
    i = 0;
slouken@1895
   678
    while (element) {
icculus@8230
   679
        value = GetHIDScaledCalibratedState(device, element, -32768, 32767);
icculus@8230
   680
        if (value != joystick->axes[i]) {
slouken@1895
   681
            SDL_PrivateJoystickAxis(joystick, i, value);
icculus@8230
   682
        }
slouken@1895
   683
        element = element->pNext;
slouken@1895
   684
        ++i;
slouken@1895
   685
    }
slouken@1895
   686
slouken@1895
   687
    element = device->firstButton;
slouken@1895
   688
    i = 0;
slouken@1895
   689
    while (element) {
icculus@8230
   690
        value = GetHIDElementState(device, element);
icculus@8230
   691
        if (value > 1) {          /* handle pressure-sensitive buttons */
slouken@629
   692
            value = 1;
icculus@8230
   693
        }
icculus@8230
   694
        if (value != joystick->buttons[i]) {
slouken@1895
   695
            SDL_PrivateJoystickButton(joystick, i, value);
icculus@8230
   696
        }
slouken@1895
   697
        element = element->pNext;
slouken@1895
   698
        ++i;
slouken@1895
   699
    }
slouken@172
   700
slouken@1895
   701
    element = device->firstHat;
slouken@1895
   702
    i = 0;
slouken@1895
   703
    while (element) {
slouken@1895
   704
        Uint8 pos = 0;
slouken@1895
   705
slouken@2287
   706
        range = (element->max - element->min + 1);
icculus@8230
   707
        value = GetHIDElementState(device, element) - element->min;
icculus@8230
   708
        if (range == 4) {         /* 4 position hatswitch - scale up value */
slouken@1895
   709
            value *= 2;
icculus@8230
   710
        } else if (range != 8) {    /* Neither a 4 nor 8 positions - fall back to default position (centered) */
slouken@1895
   711
            value = -1;
icculus@8230
   712
        }
slouken@1895
   713
        switch (value) {
slouken@1895
   714
        case 0:
slouken@1895
   715
            pos = SDL_HAT_UP;
slouken@1895
   716
            break;
slouken@1895
   717
        case 1:
slouken@1895
   718
            pos = SDL_HAT_RIGHTUP;
slouken@1895
   719
            break;
slouken@1895
   720
        case 2:
slouken@1895
   721
            pos = SDL_HAT_RIGHT;
slouken@1895
   722
            break;
slouken@1895
   723
        case 3:
slouken@1895
   724
            pos = SDL_HAT_RIGHTDOWN;
slouken@1895
   725
            break;
slouken@1895
   726
        case 4:
slouken@1895
   727
            pos = SDL_HAT_DOWN;
slouken@1895
   728
            break;
slouken@1895
   729
        case 5:
slouken@1895
   730
            pos = SDL_HAT_LEFTDOWN;
slouken@1895
   731
            break;
slouken@1895
   732
        case 6:
slouken@1895
   733
            pos = SDL_HAT_LEFT;
slouken@1895
   734
            break;
slouken@1895
   735
        case 7:
slouken@1895
   736
            pos = SDL_HAT_LEFTUP;
slouken@1895
   737
            break;
slouken@1895
   738
        default:
slouken@1895
   739
            /* Every other value is mapped to center. We do that because some
slouken@1895
   740
             * joysticks use 8 and some 15 for this value, and apparently
slouken@1895
   741
             * there are even more variants out there - so we try to be generous.
slouken@1895
   742
             */
slouken@1895
   743
            pos = SDL_HAT_CENTERED;
slouken@1895
   744
            break;
slouken@1895
   745
        }
icculus@8230
   746
icculus@8230
   747
        if (pos != joystick->hats[i]) {
slouken@1895
   748
            SDL_PrivateJoystickHat(joystick, i, pos);
icculus@8230
   749
        }
icculus@8230
   750
slouken@1895
   751
        element = element->pNext;
slouken@1895
   752
        ++i;
slouken@1895
   753
    }
slouken@172
   754
}
slouken@172
   755
slouken@172
   756
/* Function to close a joystick after use */
slouken@1895
   757
void
slouken@1895
   758
SDL_SYS_JoystickClose(SDL_Joystick * joystick)
slouken@7191
   759
{
slouken@172
   760
}
slouken@172
   761
slouken@172
   762
/* Function to perform any system-specific joystick related cleanup */
slouken@1895
   763
void
slouken@1895
   764
SDL_SYS_JoystickQuit(void)
slouken@172
   765
{
icculus@8230
   766
    while (FreeDevice(gpDeviceList)) {
icculus@8230
   767
        /* spin */
icculus@8230
   768
    }
slouken@7191
   769
icculus@8230
   770
    if (hidman) {
icculus@8739
   771
        IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
icculus@8230
   772
        IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
icculus@8230
   773
        CFRelease(hidman);
icculus@8230
   774
        hidman = NULL;
slouken@7191
   775
    }
slouken@6690
   776
}
slouken@6690
   777
slouken@6690
   778
slouken@6738
   779
SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
slouken@6690
   780
{
slouken@9629
   781
    recDevice *device = GetDeviceForIndex(device_index);
slouken@9629
   782
    SDL_JoystickGUID guid;
slouken@9629
   783
    if (device) {
slouken@9629
   784
        guid = device->guid;
slouken@9629
   785
    } else {
slouken@9629
   786
        SDL_zero(guid);
icculus@8230
   787
    }
slouken@9629
   788
    return guid;
slouken@6690
   789
}
slouken@6690
   790
slouken@6738
   791
SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick *joystick)
slouken@6690
   792
{
slouken@7191
   793
    return joystick->hwdata->guid;
slouken@172
   794
}
slouken@1635
   795
slouken@1635
   796
#endif /* SDL_JOYSTICK_IOKIT */
slouken@7026
   797
slouken@1895
   798
/* vi: set ts=4 sw=4 expandtab: */