Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/darwin/SDL_iokitjoystick.c
22572 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
4
5
This software is provided 'as-is', without any express or implied
6
warranty. In no event will the authors be held liable for any damages
7
arising from the use of this software.
8
9
Permission is granted to anyone to use this software for any purpose,
10
including commercial applications, and to alter it and redistribute it
11
freely, subject to the following restrictions:
12
13
1. The origin of this software must not be misrepresented; you must not
14
claim that you wrote the original software. If you use this software
15
in a product, an acknowledgment in the product documentation would be
16
appreciated but is not required.
17
2. Altered source versions must be plainly marked as such, and must not be
18
misrepresented as being the original software.
19
3. This notice may not be removed or altered from any source distribution.
20
*/
21
#include "SDL_internal.h"
22
23
#ifdef SDL_JOYSTICK_IOKIT
24
25
#include "../SDL_sysjoystick.h"
26
#include "../SDL_joystick_c.h"
27
#include "SDL_iokitjoystick_c.h"
28
#include "../hidapi/SDL_hidapijoystick_c.h"
29
#include "../../haptic/darwin/SDL_syshaptic_c.h" // For haptic hot plugging
30
#include "../usb_ids.h"
31
32
#define SDL_JOYSTICK_RUNLOOP_MODE CFSTR("SDLJoystick")
33
34
#define CONVERT_MAGNITUDE(x) (((x)*10000) / 0x7FFF)
35
36
// The base object of the HID Manager API
37
static IOHIDManagerRef hidman = NULL;
38
39
// Linked list of all available devices
40
static recDevice *gpDeviceList = NULL;
41
42
void FreeRumbleEffectData(FFEFFECT *effect)
43
{
44
if (!effect) {
45
return;
46
}
47
SDL_free(effect->rgdwAxes);
48
SDL_free(effect->rglDirection);
49
SDL_free(effect->lpvTypeSpecificParams);
50
SDL_free(effect);
51
}
52
53
FFEFFECT *CreateRumbleEffectData(Sint16 magnitude)
54
{
55
FFEFFECT *effect;
56
FFPERIODIC *periodic;
57
58
// Create the effect
59
effect = (FFEFFECT *)SDL_calloc(1, sizeof(*effect));
60
if (!effect) {
61
return NULL;
62
}
63
effect->dwSize = sizeof(*effect);
64
effect->dwGain = 10000;
65
effect->dwFlags = FFEFF_OBJECTOFFSETS;
66
effect->dwDuration = SDL_MAX_RUMBLE_DURATION_MS * 1000; // In microseconds.
67
effect->dwTriggerButton = FFEB_NOTRIGGER;
68
69
effect->cAxes = 2;
70
effect->rgdwAxes = (DWORD *)SDL_calloc(effect->cAxes, sizeof(DWORD));
71
if (!effect->rgdwAxes) {
72
FreeRumbleEffectData(effect);
73
return NULL;
74
}
75
76
effect->rglDirection = (LONG *)SDL_calloc(effect->cAxes, sizeof(LONG));
77
if (!effect->rglDirection) {
78
FreeRumbleEffectData(effect);
79
return NULL;
80
}
81
effect->dwFlags |= FFEFF_CARTESIAN;
82
83
periodic = (FFPERIODIC *)SDL_calloc(1, sizeof(*periodic));
84
if (!periodic) {
85
FreeRumbleEffectData(effect);
86
return NULL;
87
}
88
periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
89
periodic->dwPeriod = 1000000;
90
91
effect->cbTypeSpecificParams = sizeof(*periodic);
92
effect->lpvTypeSpecificParams = periodic;
93
94
return effect;
95
}
96
97
static recDevice *GetDeviceForIndex(int device_index)
98
{
99
recDevice *device = gpDeviceList;
100
while (device) {
101
if (!device->removed) {
102
if (device_index == 0) {
103
break;
104
}
105
106
--device_index;
107
}
108
device = device->pNext;
109
}
110
return device;
111
}
112
113
static void FreeElementList(recElement *pElement)
114
{
115
while (pElement) {
116
recElement *pElementNext = pElement->pNext;
117
SDL_free(pElement);
118
pElement = pElementNext;
119
}
120
}
121
122
static recDevice *FreeDevice(recDevice *removeDevice)
123
{
124
recDevice *pDeviceNext = NULL;
125
if (removeDevice) {
126
if (removeDevice->deviceRef) {
127
if (removeDevice->runLoopAttached) {
128
/* Calling IOHIDDeviceUnscheduleFromRunLoop without a prior,
129
* paired call to IOHIDDeviceScheduleWithRunLoop can lead
130
* to crashes in MacOS 10.14.x and earlier. This doesn't
131
* appear to be a problem in MacOS 10.15.x, but we'll
132
* do it anyways. (Part-of fix for Bug 5034)
133
*/
134
IOHIDDeviceUnscheduleFromRunLoop(removeDevice->deviceRef, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
135
}
136
CFRelease(removeDevice->deviceRef);
137
removeDevice->deviceRef = NULL;
138
}
139
140
/* clear out any reference to removeDevice from an associated,
141
* live instance of SDL_Joystick (Part-of fix for Bug 5034)
142
*/
143
SDL_LockJoysticks();
144
if (removeDevice->joystick) {
145
removeDevice->joystick->hwdata = NULL;
146
}
147
SDL_UnlockJoysticks();
148
149
// save next device prior to disposing of this device
150
pDeviceNext = removeDevice->pNext;
151
152
if (gpDeviceList == removeDevice) {
153
gpDeviceList = pDeviceNext;
154
} else if (gpDeviceList) {
155
recDevice *device;
156
157
for (device = gpDeviceList; device; device = device->pNext) {
158
if (device->pNext == removeDevice) {
159
device->pNext = pDeviceNext;
160
break;
161
}
162
}
163
}
164
removeDevice->pNext = NULL;
165
166
// free element lists
167
FreeElementList(removeDevice->firstAxis);
168
FreeElementList(removeDevice->firstButton);
169
FreeElementList(removeDevice->firstHat);
170
171
SDL_free(removeDevice);
172
}
173
return pDeviceNext;
174
}
175
176
static bool GetHIDElementState(recDevice *pDevice, recElement *pElement, SInt32 *pValue)
177
{
178
SInt32 value = 0;
179
bool result = false;
180
181
if (pDevice && pDevice->deviceRef && pElement) {
182
IOHIDValueRef valueRef;
183
if (IOHIDDeviceGetValue(pDevice->deviceRef, pElement->elementRef, &valueRef) == kIOReturnSuccess) {
184
value = (SInt32)IOHIDValueGetIntegerValue(valueRef);
185
186
// record min and max for auto calibration
187
if (value < pElement->minReport) {
188
pElement->minReport = value;
189
}
190
if (value > pElement->maxReport) {
191
pElement->maxReport = value;
192
}
193
*pValue = value;
194
195
result = true;
196
}
197
}
198
return result;
199
}
200
201
static bool GetHIDScaledCalibratedState(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue)
202
{
203
const float deviceScale = max - min;
204
const float readScale = pElement->maxReport - pElement->minReport;
205
bool result = false;
206
if (GetHIDElementState(pDevice, pElement, pValue)) {
207
if (readScale == 0) {
208
result = true; // no scaling at all
209
} else {
210
*pValue = (Sint32)(((*pValue - pElement->minReport) * deviceScale / readScale) + min);
211
result = true;
212
}
213
}
214
return result;
215
}
216
217
static bool GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(recDevice *pDevice, recElement *pElement, SInt32 min, SInt32 max, SInt32 *pValue)
218
{
219
if (pElement->minReport == 0 && pElement->maxReport == 255) {
220
return GetHIDScaledCalibratedState(pDevice, pElement, min, max, pValue);
221
}
222
223
// This device thumbstick axes have an unusual axis range that
224
// doesn't work with GetHIDScaledCalibratedState() above.
225
//
226
// See https://github.com/libsdl-org/SDL/issues/13143 for details
227
if (GetHIDElementState(pDevice, pElement, pValue)) {
228
if (*pValue >= 0) {
229
// Negative axis values range from 32767 (at rest) to 0 (minimum)
230
*pValue = -32767 + *pValue;
231
} else if (*pValue < 0) {
232
// Positive axis values range from -32768 (at rest) to 0 (maximum)
233
*pValue = 32768 + *pValue;
234
}
235
return true;
236
}
237
return false;
238
}
239
240
static void JoystickDeviceWasRemovedCallback(void *ctx, IOReturn result, void *sender)
241
{
242
recDevice *device = (recDevice *)ctx;
243
device->removed = true;
244
if (device->deviceRef) {
245
// deviceRef was invalidated due to the remove
246
CFRelease(device->deviceRef);
247
device->deviceRef = NULL;
248
}
249
if (device->ffeffect_ref) {
250
FFDeviceReleaseEffect(device->ffdevice, device->ffeffect_ref);
251
device->ffeffect_ref = NULL;
252
}
253
if (device->ffeffect) {
254
FreeRumbleEffectData(device->ffeffect);
255
device->ffeffect = NULL;
256
}
257
if (device->ffdevice) {
258
FFReleaseDevice(device->ffdevice);
259
device->ffdevice = NULL;
260
device->ff_initialized = false;
261
}
262
#ifdef SDL_HAPTIC_IOKIT
263
MacHaptic_MaybeRemoveDevice(device->ffservice);
264
#endif
265
266
SDL_PrivateJoystickRemoved(device->instance_id);
267
}
268
269
static void AddHIDElement(const void *value, void *parameter);
270
271
// Call AddHIDElement() on all elements in an array of IOHIDElementRefs
272
static void AddHIDElements(CFArrayRef array, recDevice *pDevice)
273
{
274
const CFRange range = { 0, CFArrayGetCount(array) };
275
CFArrayApplyFunction(array, range, AddHIDElement, pDevice);
276
}
277
278
static bool ElementAlreadyAdded(const IOHIDElementCookie cookie, const recElement *listitem)
279
{
280
while (listitem) {
281
if (listitem->cookie == cookie) {
282
return true;
283
}
284
listitem = listitem->pNext;
285
}
286
return false;
287
}
288
289
// See if we care about this HID element, and if so, note it in our recDevice.
290
static void AddHIDElement(const void *value, void *parameter)
291
{
292
recDevice *pDevice = (recDevice *)parameter;
293
IOHIDElementRef refElement = (IOHIDElementRef)value;
294
const CFTypeID elementTypeID = refElement ? CFGetTypeID(refElement) : 0;
295
296
if (refElement && (elementTypeID == IOHIDElementGetTypeID())) {
297
const IOHIDElementCookie cookie = IOHIDElementGetCookie(refElement);
298
const uint32_t usagePage = IOHIDElementGetUsagePage(refElement);
299
const uint32_t usage = IOHIDElementGetUsage(refElement);
300
recElement *element = NULL;
301
recElement **headElement = NULL;
302
303
// look at types of interest
304
switch (IOHIDElementGetType(refElement)) {
305
case kIOHIDElementTypeInput_Misc:
306
case kIOHIDElementTypeInput_Button:
307
case kIOHIDElementTypeInput_Axis:
308
{
309
switch (usagePage) { // only interested in kHIDPage_GenericDesktop and kHIDPage_Button
310
case kHIDPage_GenericDesktop:
311
switch (usage) {
312
case kHIDUsage_GD_X:
313
case kHIDUsage_GD_Y:
314
case kHIDUsage_GD_Z:
315
case kHIDUsage_GD_Rx:
316
case kHIDUsage_GD_Ry:
317
case kHIDUsage_GD_Rz:
318
case kHIDUsage_GD_Slider:
319
case kHIDUsage_GD_Dial:
320
case kHIDUsage_GD_Wheel:
321
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
322
element = (recElement *)SDL_calloc(1, sizeof(recElement));
323
if (element) {
324
pDevice->axes++;
325
headElement = &(pDevice->firstAxis);
326
}
327
}
328
break;
329
330
case kHIDUsage_GD_Hatswitch:
331
if (!ElementAlreadyAdded(cookie, pDevice->firstHat)) {
332
element = (recElement *)SDL_calloc(1, sizeof(recElement));
333
if (element) {
334
pDevice->hats++;
335
headElement = &(pDevice->firstHat);
336
}
337
}
338
break;
339
case kHIDUsage_GD_DPadUp:
340
case kHIDUsage_GD_DPadDown:
341
case kHIDUsage_GD_DPadRight:
342
case kHIDUsage_GD_DPadLeft:
343
case kHIDUsage_GD_Start:
344
case kHIDUsage_GD_Select:
345
case kHIDUsage_GD_SystemMainMenu:
346
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
347
element = (recElement *)SDL_calloc(1, sizeof(recElement));
348
if (element) {
349
pDevice->buttons++;
350
headElement = &(pDevice->firstButton);
351
}
352
}
353
break;
354
}
355
break;
356
357
case kHIDPage_Simulation:
358
switch (usage) {
359
case kHIDUsage_Sim_Rudder:
360
case kHIDUsage_Sim_Throttle:
361
case kHIDUsage_Sim_Accelerator:
362
case kHIDUsage_Sim_Brake:
363
if (!ElementAlreadyAdded(cookie, pDevice->firstAxis)) {
364
element = (recElement *)SDL_calloc(1, sizeof(recElement));
365
if (element) {
366
pDevice->axes++;
367
headElement = &(pDevice->firstAxis);
368
}
369
}
370
break;
371
372
default:
373
break;
374
}
375
break;
376
377
case kHIDPage_Button:
378
case kHIDPage_Consumer: // e.g. 'pause' button on Steelseries MFi gamepads.
379
if (!ElementAlreadyAdded(cookie, pDevice->firstButton)) {
380
element = (recElement *)SDL_calloc(1, sizeof(recElement));
381
if (element) {
382
pDevice->buttons++;
383
headElement = &(pDevice->firstButton);
384
}
385
}
386
break;
387
388
default:
389
break;
390
}
391
} break;
392
393
case kIOHIDElementTypeCollection:
394
{
395
CFArrayRef array = IOHIDElementGetChildren(refElement);
396
if (array) {
397
AddHIDElements(array, pDevice);
398
}
399
} break;
400
401
default:
402
break;
403
}
404
405
if (element && headElement) { // add to list
406
recElement *elementPrevious = NULL;
407
recElement *elementCurrent = *headElement;
408
while (elementCurrent && usage >= elementCurrent->usage) {
409
elementPrevious = elementCurrent;
410
elementCurrent = elementCurrent->pNext;
411
}
412
if (elementPrevious) {
413
elementPrevious->pNext = element;
414
} else {
415
*headElement = element;
416
}
417
418
element->elementRef = refElement;
419
element->usagePage = usagePage;
420
element->usage = usage;
421
element->pNext = elementCurrent;
422
423
element->minReport = element->min = (SInt32)IOHIDElementGetLogicalMin(refElement);
424
element->maxReport = element->max = (SInt32)IOHIDElementGetLogicalMax(refElement);
425
element->cookie = IOHIDElementGetCookie(refElement);
426
427
pDevice->elements++;
428
}
429
}
430
}
431
432
static int GetSteamVirtualGamepadSlot(Uint16 vendor_id, Uint16 product_id, const char *product_string)
433
{
434
int slot = -1;
435
436
if (vendor_id == USB_VENDOR_MICROSOFT && product_id == USB_PRODUCT_XBOX360_WIRED_CONTROLLER) {
437
// Gamepad name is "GamePad-N", where N is slot + 1
438
if (SDL_sscanf(product_string, "GamePad-%d", &slot) == 1) {
439
slot -= 1;
440
}
441
}
442
return slot;
443
}
444
445
static bool GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice)
446
{
447
Sint32 vendor = 0;
448
Sint32 product = 0;
449
Sint32 version = 0;
450
char *name;
451
char manufacturer_string[256];
452
char product_string[256];
453
CFTypeRef refCF = NULL;
454
CFArrayRef array = NULL;
455
456
// get usage page and usage
457
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsagePageKey));
458
if (refCF) {
459
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usagePage);
460
}
461
if (pDevice->usagePage != kHIDPage_GenericDesktop) {
462
return false; // Filter device list to non-keyboard/mouse stuff
463
}
464
465
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDPrimaryUsageKey));
466
if (refCF) {
467
CFNumberGetValue(refCF, kCFNumberSInt32Type, &pDevice->usage);
468
}
469
470
if ((pDevice->usage != kHIDUsage_GD_Joystick &&
471
pDevice->usage != kHIDUsage_GD_GamePad &&
472
pDevice->usage != kHIDUsage_GD_MultiAxisController)) {
473
return false; // Filter device list to non-keyboard/mouse stuff
474
}
475
476
/* Make sure we retain the use of the IOKit-provided device-object,
477
lest the device get disconnected and we try to use it. (Fixes
478
SDL-Bugzilla #4961, aka. https://bugzilla.libsdl.org/show_bug.cgi?id=4961 )
479
*/
480
CFRetain(hidDevice);
481
482
/* Now that we've CFRetain'ed the device-object (for our use), we'll
483
save the reference to it.
484
*/
485
pDevice->deviceRef = hidDevice;
486
487
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVendorIDKey));
488
if (refCF) {
489
CFNumberGetValue(refCF, kCFNumberSInt32Type, &vendor);
490
}
491
492
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductIDKey));
493
if (refCF) {
494
CFNumberGetValue(refCF, kCFNumberSInt32Type, &product);
495
}
496
497
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDVersionNumberKey));
498
if (refCF) {
499
CFNumberGetValue(refCF, kCFNumberSInt32Type, &version);
500
}
501
502
if (SDL_IsJoystickXboxOne(vendor, product)) {
503
// We can't actually use this API for Xbox controllers
504
return false;
505
}
506
507
// get device name
508
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDManufacturerKey));
509
if ((!refCF) || (!CFStringGetCString(refCF, manufacturer_string, sizeof(manufacturer_string), kCFStringEncodingUTF8))) {
510
manufacturer_string[0] = '\0';
511
}
512
refCF = IOHIDDeviceGetProperty(hidDevice, CFSTR(kIOHIDProductKey));
513
if ((!refCF) || (!CFStringGetCString(refCF, product_string, sizeof(product_string), kCFStringEncodingUTF8))) {
514
product_string[0] = '\0';
515
}
516
name = SDL_CreateJoystickName(vendor, product, manufacturer_string, product_string);
517
if (name) {
518
SDL_strlcpy(pDevice->product, name, sizeof(pDevice->product));
519
SDL_free(name);
520
}
521
522
if (SDL_ShouldIgnoreJoystick(vendor, product, version, pDevice->product)) {
523
return false;
524
}
525
526
if (SDL_JoystickHandledByAnotherDriver(&SDL_DARWIN_JoystickDriver, vendor, product, version, pDevice->product)) {
527
return false;
528
}
529
530
pDevice->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_USB, (Uint16)vendor, (Uint16)product, (Uint16)version, manufacturer_string, product_string, 0, 0);
531
pDevice->steam_virtual_gamepad_slot = GetSteamVirtualGamepadSlot((Uint16)vendor, (Uint16)product, product_string);
532
533
if (vendor == USB_VENDOR_NACON_ALT &&
534
product == USB_PRODUCT_NACON_REVOLUTION_X_UNLIMITED_BT) {
535
pDevice->nacon_revolution_x_unlimited = true;
536
}
537
538
array = IOHIDDeviceCopyMatchingElements(hidDevice, NULL, kIOHIDOptionsTypeNone);
539
if (array) {
540
AddHIDElements(array, pDevice);
541
CFRelease(array);
542
}
543
544
return true;
545
}
546
547
static bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject)
548
{
549
recDevice *i;
550
551
#ifdef SDL_JOYSTICK_MFI
552
extern bool IOS_SupportedHIDDevice(IOHIDDeviceRef device);
553
if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) {
554
return true;
555
}
556
#endif
557
558
for (i = gpDeviceList; i; i = i->pNext) {
559
if (i->deviceRef == ioHIDDeviceObject) {
560
return true;
561
}
562
}
563
return false;
564
}
565
566
static void JoystickDeviceWasAddedCallback(void *ctx, IOReturn res, void *sender, IOHIDDeviceRef ioHIDDeviceObject)
567
{
568
recDevice *device;
569
io_service_t ioservice;
570
571
if (res != kIOReturnSuccess) {
572
return;
573
}
574
575
if (JoystickAlreadyKnown(ioHIDDeviceObject)) {
576
return; // IOKit sent us a duplicate.
577
}
578
579
device = (recDevice *)SDL_calloc(1, sizeof(recDevice));
580
if (!device) {
581
return;
582
}
583
584
if (!GetDeviceInfo(ioHIDDeviceObject, device)) {
585
FreeDevice(device);
586
return; // not a device we care about, probably.
587
}
588
589
// Get notified when this device is disconnected.
590
IOHIDDeviceRegisterRemovalCallback(ioHIDDeviceObject, JoystickDeviceWasRemovedCallback, device);
591
IOHIDDeviceScheduleWithRunLoop(ioHIDDeviceObject, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
592
device->runLoopAttached = true;
593
594
// Allocate an instance ID for this device
595
device->instance_id = SDL_GetNextObjectID();
596
597
// We have to do some storage of the io_service_t for SDL_OpenHapticFromJoystick
598
ioservice = IOHIDDeviceGetService(ioHIDDeviceObject);
599
if ((ioservice) && (FFIsForceFeedback(ioservice) == FF_OK)) {
600
device->ffservice = ioservice;
601
#ifdef SDL_HAPTIC_IOKIT
602
MacHaptic_MaybeAddDevice(ioservice);
603
#endif
604
}
605
606
// Add device to the end of the list
607
if (!gpDeviceList) {
608
gpDeviceList = device;
609
} else {
610
recDevice *curdevice;
611
612
curdevice = gpDeviceList;
613
while (curdevice->pNext) {
614
curdevice = curdevice->pNext;
615
}
616
curdevice->pNext = device;
617
}
618
619
SDL_PrivateJoystickAdded(device->instance_id);
620
}
621
622
static bool ConfigHIDManager(CFArrayRef matchingArray)
623
{
624
CFRunLoopRef runloop = CFRunLoopGetCurrent();
625
626
if (IOHIDManagerOpen(hidman, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
627
return false;
628
}
629
630
IOHIDManagerSetDeviceMatchingMultiple(hidman, matchingArray);
631
IOHIDManagerRegisterDeviceMatchingCallback(hidman, JoystickDeviceWasAddedCallback, NULL);
632
IOHIDManagerScheduleWithRunLoop(hidman, runloop, SDL_JOYSTICK_RUNLOOP_MODE);
633
634
while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
635
// no-op. Callback fires once per existing device.
636
}
637
638
// future hotplug events will come through SDL_JOYSTICK_RUNLOOP_MODE now.
639
640
return true; // good to go.
641
}
642
643
static CFDictionaryRef CreateHIDDeviceMatchDictionary(const UInt32 page, const UInt32 usage, int *okay)
644
{
645
CFDictionaryRef result = NULL;
646
CFNumberRef pageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page);
647
CFNumberRef usageNumRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
648
const void *keys[2] = { (void *)CFSTR(kIOHIDDeviceUsagePageKey), (void *)CFSTR(kIOHIDDeviceUsageKey) };
649
const void *vals[2] = { (void *)pageNumRef, (void *)usageNumRef };
650
651
if (pageNumRef && usageNumRef) {
652
result = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
653
}
654
655
if (pageNumRef) {
656
CFRelease(pageNumRef);
657
}
658
if (usageNumRef) {
659
CFRelease(usageNumRef);
660
}
661
662
if (!result) {
663
*okay = 0;
664
}
665
666
return result;
667
}
668
669
static bool CreateHIDManager(void)
670
{
671
bool result = false;
672
int okay = 1;
673
const void *vals[] = {
674
(void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_Joystick, &okay),
675
(void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_GamePad, &okay),
676
(void *)CreateHIDDeviceMatchDictionary(kHIDPage_GenericDesktop, kHIDUsage_GD_MultiAxisController, &okay),
677
};
678
const size_t numElements = SDL_arraysize(vals);
679
CFArrayRef array = okay ? CFArrayCreate(kCFAllocatorDefault, vals, numElements, &kCFTypeArrayCallBacks) : NULL;
680
size_t i;
681
682
for (i = 0; i < numElements; i++) {
683
if (vals[i]) {
684
CFRelease((CFTypeRef)vals[i]);
685
}
686
}
687
688
if (array) {
689
hidman = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone);
690
if (hidman != NULL) {
691
result = ConfigHIDManager(array);
692
}
693
CFRelease(array);
694
}
695
696
return result;
697
}
698
699
static bool DARWIN_JoystickInit(void)
700
{
701
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_IOKIT, true)) {
702
return true;
703
}
704
705
if (!CreateHIDManager()) {
706
return SDL_SetError("Joystick: Couldn't initialize HID Manager");
707
}
708
709
return true;
710
}
711
712
static int DARWIN_JoystickGetCount(void)
713
{
714
recDevice *device = gpDeviceList;
715
int nJoySticks = 0;
716
717
while (device) {
718
if (!device->removed) {
719
nJoySticks++;
720
}
721
device = device->pNext;
722
}
723
724
return nJoySticks;
725
}
726
727
static void DARWIN_JoystickDetect(void)
728
{
729
recDevice *device = gpDeviceList;
730
while (device) {
731
if (device->removed) {
732
device = FreeDevice(device);
733
} else {
734
device = device->pNext;
735
}
736
}
737
738
if (hidman) {
739
/* run this after the checks above so we don't set device->removed and delete the device before
740
DARWIN_JoystickUpdate can run to clean up the SDL_Joystick object that owns this device */
741
while (CFRunLoopRunInMode(SDL_JOYSTICK_RUNLOOP_MODE, 0, TRUE) == kCFRunLoopRunHandledSource) {
742
// no-op. Pending callbacks will fire in CFRunLoopRunInMode().
743
}
744
}
745
}
746
747
static bool DARWIN_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
748
{
749
// We don't override any other drivers
750
return false;
751
}
752
753
static const char *DARWIN_JoystickGetDeviceName(int device_index)
754
{
755
recDevice *device = GetDeviceForIndex(device_index);
756
return device ? device->product : "UNKNOWN";
757
}
758
759
static const char *DARWIN_JoystickGetDevicePath(int device_index)
760
{
761
return NULL;
762
}
763
764
static int DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
765
{
766
recDevice *device = GetDeviceForIndex(device_index);
767
return device ? device->steam_virtual_gamepad_slot : -1;
768
}
769
770
static int DARWIN_JoystickGetDevicePlayerIndex(int device_index)
771
{
772
return -1;
773
}
774
775
static void DARWIN_JoystickSetDevicePlayerIndex(int device_index, int player_index)
776
{
777
}
778
779
static SDL_GUID DARWIN_JoystickGetDeviceGUID(int device_index)
780
{
781
recDevice *device = GetDeviceForIndex(device_index);
782
SDL_GUID guid;
783
if (device) {
784
guid = device->guid;
785
} else {
786
SDL_zero(guid);
787
}
788
return guid;
789
}
790
791
static SDL_JoystickID DARWIN_JoystickGetDeviceInstanceID(int device_index)
792
{
793
recDevice *device = GetDeviceForIndex(device_index);
794
return device ? device->instance_id : 0;
795
}
796
797
static bool DARWIN_JoystickOpen(SDL_Joystick *joystick, int device_index)
798
{
799
recDevice *device = GetDeviceForIndex(device_index);
800
801
joystick->hwdata = device;
802
device->joystick = joystick;
803
joystick->name = device->product;
804
805
joystick->naxes = device->axes;
806
joystick->nhats = device->hats;
807
joystick->nbuttons = device->buttons;
808
809
if (device->ffservice) {
810
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
811
}
812
813
return true;
814
}
815
816
/*
817
* Like strerror but for force feedback errors.
818
*/
819
static const char *FFStrError(unsigned int err)
820
{
821
switch (err) {
822
case FFERR_DEVICEFULL:
823
return "device full";
824
// This should be valid, but for some reason isn't defined...
825
/* case FFERR_DEVICENOTREG:
826
return "device not registered"; */
827
case FFERR_DEVICEPAUSED:
828
return "device paused";
829
case FFERR_DEVICERELEASED:
830
return "device released";
831
case FFERR_EFFECTPLAYING:
832
return "effect playing";
833
case FFERR_EFFECTTYPEMISMATCH:
834
return "effect type mismatch";
835
case FFERR_EFFECTTYPENOTSUPPORTED:
836
return "effect type not supported";
837
case FFERR_GENERIC:
838
return "undetermined error";
839
case FFERR_HASEFFECTS:
840
return "device has effects";
841
case FFERR_INCOMPLETEEFFECT:
842
return "incomplete effect";
843
case FFERR_INTERNAL:
844
return "internal fault";
845
case FFERR_INVALIDDOWNLOADID:
846
return "invalid download id";
847
case FFERR_INVALIDPARAM:
848
return "invalid parameter";
849
case FFERR_MOREDATA:
850
return "more data";
851
case FFERR_NOINTERFACE:
852
return "interface not supported";
853
case FFERR_NOTDOWNLOADED:
854
return "effect is not downloaded";
855
case FFERR_NOTINITIALIZED:
856
return "object has not been initialized";
857
case FFERR_OUTOFMEMORY:
858
return "out of memory";
859
case FFERR_UNPLUGGED:
860
return "device is unplugged";
861
case FFERR_UNSUPPORTED:
862
return "function call unsupported";
863
case FFERR_UNSUPPORTEDAXIS:
864
return "axis unsupported";
865
866
default:
867
return "unknown error";
868
}
869
}
870
871
static bool DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)
872
{
873
HRESULT result;
874
875
if (!device->ffdevice) {
876
result = FFCreateDevice(device->ffservice, &device->ffdevice);
877
if (result != FF_OK) {
878
return SDL_SetError("Unable to create force feedback device from service: %s", FFStrError(result));
879
}
880
}
881
882
// Reset and then enable actuators
883
result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_RESET);
884
if (result != FF_OK) {
885
return SDL_SetError("Unable to reset force feedback device: %s", FFStrError(result));
886
}
887
888
result = FFDeviceSendForceFeedbackCommand(device->ffdevice, FFSFFC_SETACTUATORSON);
889
if (result != FF_OK) {
890
return SDL_SetError("Unable to enable force feedback actuators: %s", FFStrError(result));
891
}
892
893
// Create the effect
894
device->ffeffect = CreateRumbleEffectData(magnitude);
895
if (!device->ffeffect) {
896
return false;
897
}
898
899
result = FFDeviceCreateEffect(device->ffdevice, kFFEffectType_Sine_ID,
900
device->ffeffect, &device->ffeffect_ref);
901
if (result != FF_OK) {
902
return SDL_SetError("Haptic: Unable to create effect: %s", FFStrError(result));
903
}
904
return true;
905
}
906
907
static bool DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
908
{
909
HRESULT result;
910
recDevice *device = joystick->hwdata;
911
912
// Scale and average the two rumble strengths
913
Sint16 magnitude = (Sint16)(((low_frequency_rumble / 2) + (high_frequency_rumble / 2)) / 2);
914
915
if (!device) {
916
return SDL_SetError("Rumble failed, device disconnected");
917
}
918
919
if (!device->ffservice) {
920
return SDL_Unsupported();
921
}
922
923
if (device->ff_initialized) {
924
FFPERIODIC *periodic = ((FFPERIODIC *)device->ffeffect->lpvTypeSpecificParams);
925
periodic->dwMagnitude = CONVERT_MAGNITUDE(magnitude);
926
927
result = FFEffectSetParameters(device->ffeffect_ref, device->ffeffect,
928
(FFEP_DURATION | FFEP_TYPESPECIFICPARAMS));
929
if (result != FF_OK) {
930
return SDL_SetError("Unable to update rumble effect: %s", FFStrError(result));
931
}
932
} else {
933
if (!DARWIN_JoystickInitRumble(device, magnitude)) {
934
return false;
935
}
936
device->ff_initialized = true;
937
}
938
939
result = FFEffectStart(device->ffeffect_ref, 1, 0);
940
if (result != FF_OK) {
941
return SDL_SetError("Unable to run the rumble effect: %s", FFStrError(result));
942
}
943
return true;
944
}
945
946
static bool DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
947
{
948
return SDL_Unsupported();
949
}
950
951
static bool DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
952
{
953
return SDL_Unsupported();
954
}
955
956
static bool DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
957
{
958
return SDL_Unsupported();
959
}
960
961
static bool DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
962
{
963
return SDL_Unsupported();
964
}
965
966
static void DARWIN_JoystickUpdate(SDL_Joystick *joystick)
967
{
968
recDevice *device = joystick->hwdata;
969
recElement *element;
970
SInt32 value, range;
971
int i, goodRead = false;
972
Uint64 timestamp = SDL_GetTicksNS();
973
974
if (!device) {
975
return;
976
}
977
978
if (device->removed) { // device was unplugged; ignore it.
979
if (joystick->hwdata) {
980
joystick->hwdata = NULL;
981
}
982
return;
983
}
984
985
element = device->firstAxis;
986
i = 0;
987
988
while (element) {
989
if (device->nacon_revolution_x_unlimited) {
990
goodRead = GetHIDScaledCalibratedState_NACON_Revolution_X_Unlimited(device, element, -32768, 32767, &value);
991
} else {
992
goodRead = GetHIDScaledCalibratedState(device, element, -32768, 32767, &value);
993
}
994
if (goodRead) {
995
SDL_SendJoystickAxis(timestamp, joystick, i, value);
996
}
997
998
element = element->pNext;
999
++i;
1000
}
1001
1002
element = device->firstButton;
1003
i = 0;
1004
while (element) {
1005
goodRead = GetHIDElementState(device, element, &value);
1006
if (goodRead) {
1007
SDL_SendJoystickButton(timestamp, joystick, i, (value != 0));
1008
}
1009
1010
element = element->pNext;
1011
++i;
1012
}
1013
1014
element = device->firstHat;
1015
i = 0;
1016
1017
while (element) {
1018
Uint8 pos = 0;
1019
1020
range = (element->max - element->min + 1);
1021
goodRead = GetHIDElementState(device, element, &value);
1022
if (goodRead) {
1023
value -= element->min;
1024
if (range == 4) { // 4 position hatswitch - scale up value
1025
value *= 2;
1026
} else if (range != 8) { // Neither a 4 nor 8 positions - fall back to default position (centered)
1027
value = -1;
1028
}
1029
switch (value) {
1030
case 0:
1031
pos = SDL_HAT_UP;
1032
break;
1033
case 1:
1034
pos = SDL_HAT_RIGHTUP;
1035
break;
1036
case 2:
1037
pos = SDL_HAT_RIGHT;
1038
break;
1039
case 3:
1040
pos = SDL_HAT_RIGHTDOWN;
1041
break;
1042
case 4:
1043
pos = SDL_HAT_DOWN;
1044
break;
1045
case 5:
1046
pos = SDL_HAT_LEFTDOWN;
1047
break;
1048
case 6:
1049
pos = SDL_HAT_LEFT;
1050
break;
1051
case 7:
1052
pos = SDL_HAT_LEFTUP;
1053
break;
1054
default:
1055
/* Every other value is mapped to center. We do that because some
1056
* joysticks use 8 and some 15 for this value, and apparently
1057
* there are even more variants out there - so we try to be generous.
1058
*/
1059
pos = SDL_HAT_CENTERED;
1060
break;
1061
}
1062
1063
SDL_SendJoystickHat(timestamp, joystick, i, pos);
1064
}
1065
1066
element = element->pNext;
1067
++i;
1068
}
1069
}
1070
1071
static void DARWIN_JoystickClose(SDL_Joystick *joystick)
1072
{
1073
recDevice *device = joystick->hwdata;
1074
if (device) {
1075
device->joystick = NULL;
1076
}
1077
}
1078
1079
static void DARWIN_JoystickQuit(void)
1080
{
1081
while (FreeDevice(gpDeviceList)) {
1082
// spin
1083
}
1084
1085
if (hidman) {
1086
IOHIDManagerUnscheduleFromRunLoop(hidman, CFRunLoopGetCurrent(), SDL_JOYSTICK_RUNLOOP_MODE);
1087
IOHIDManagerClose(hidman, kIOHIDOptionsTypeNone);
1088
CFRelease(hidman);
1089
hidman = NULL;
1090
}
1091
}
1092
1093
static bool DARWIN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1094
{
1095
return false;
1096
}
1097
1098
SDL_JoystickDriver SDL_DARWIN_JoystickDriver = {
1099
DARWIN_JoystickInit,
1100
DARWIN_JoystickGetCount,
1101
DARWIN_JoystickDetect,
1102
DARWIN_JoystickIsDevicePresent,
1103
DARWIN_JoystickGetDeviceName,
1104
DARWIN_JoystickGetDevicePath,
1105
DARWIN_JoystickGetDeviceSteamVirtualGamepadSlot,
1106
DARWIN_JoystickGetDevicePlayerIndex,
1107
DARWIN_JoystickSetDevicePlayerIndex,
1108
DARWIN_JoystickGetDeviceGUID,
1109
DARWIN_JoystickGetDeviceInstanceID,
1110
DARWIN_JoystickOpen,
1111
DARWIN_JoystickRumble,
1112
DARWIN_JoystickRumbleTriggers,
1113
DARWIN_JoystickSetLED,
1114
DARWIN_JoystickSendEffect,
1115
DARWIN_JoystickSetSensorsEnabled,
1116
DARWIN_JoystickUpdate,
1117
DARWIN_JoystickClose,
1118
DARWIN_JoystickQuit,
1119
DARWIN_JoystickGetGamepadMapping
1120
};
1121
1122
#endif // SDL_JOYSTICK_IOKIT
1123
1124