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