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