Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/sdl/joystick/apple/SDL_mfijoystick.m
9906 views
1
/*
2
Simple DirectMedia Layer
3
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
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
// This is the iOS implementation of the SDL joystick API
24
#include "../SDL_sysjoystick.h"
25
#include "../SDL_joystick_c.h"
26
#include "../hidapi/SDL_hidapijoystick_c.h"
27
#include "../usb_ids.h"
28
#include "../../events/SDL_events_c.h"
29
30
#include "SDL_mfijoystick_c.h"
31
32
33
#if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS)
34
#import <CoreMotion/CoreMotion.h>
35
#endif
36
37
#ifdef SDL_PLATFORM_MACOS
38
#include <IOKit/hid/IOHIDManager.h>
39
#include <AppKit/NSApplication.h>
40
#ifndef NSAppKitVersionNumber10_15
41
#define NSAppKitVersionNumber10_15 1894
42
#endif
43
#endif // SDL_PLATFORM_MACOS
44
45
#import <GameController/GameController.h>
46
47
#ifdef SDL_JOYSTICK_MFI
48
static id connectObserver = nil;
49
static id disconnectObserver = nil;
50
51
#include <objc/message.h>
52
53
// Fix build errors when using an older SDK by defining these selectors
54
@interface GCController (SDL)
55
#if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140500) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140500) || (__MAC_OS_X_VERSION_MAX_ALLOWED >= 110300))
56
@property(class, nonatomic, readwrite) BOOL shouldMonitorBackgroundEvents;
57
#endif
58
@end
59
60
#import <CoreHaptics/CoreHaptics.h>
61
62
#endif // SDL_JOYSTICK_MFI
63
64
static SDL_JoystickDeviceItem *deviceList = NULL;
65
66
static int numjoysticks = 0;
67
int SDL_AppleTVRemoteOpenedAsJoystick = 0;
68
69
static SDL_JoystickDeviceItem *GetDeviceForIndex(int device_index)
70
{
71
SDL_JoystickDeviceItem *device = deviceList;
72
int i = 0;
73
74
while (i < device_index) {
75
if (device == NULL) {
76
return NULL;
77
}
78
device = device->next;
79
i++;
80
}
81
82
return device;
83
}
84
85
#ifdef SDL_JOYSTICK_MFI
86
static bool IsControllerPS4(GCController *controller)
87
{
88
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
89
if ([controller.productCategory isEqualToString:@"DualShock 4"]) {
90
return true;
91
}
92
} else {
93
if ([controller.vendorName containsString:@"DUALSHOCK"]) {
94
return true;
95
}
96
}
97
return false;
98
}
99
static bool IsControllerPS5(GCController *controller)
100
{
101
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
102
if ([controller.productCategory isEqualToString:@"DualSense"]) {
103
return true;
104
}
105
} else {
106
if ([controller.vendorName containsString:@"DualSense"]) {
107
return true;
108
}
109
}
110
return false;
111
}
112
static bool IsControllerXbox(GCController *controller)
113
{
114
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
115
if ([controller.productCategory isEqualToString:@"Xbox One"]) {
116
return true;
117
}
118
} else {
119
if ([controller.vendorName containsString:@"Xbox"]) {
120
return true;
121
}
122
}
123
return false;
124
}
125
static bool IsControllerSwitchPro(GCController *controller)
126
{
127
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
128
if ([controller.productCategory isEqualToString:@"Switch Pro Controller"]) {
129
return true;
130
}
131
}
132
return false;
133
}
134
static bool IsControllerSwitchJoyConL(GCController *controller)
135
{
136
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
137
if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L)"]) {
138
return true;
139
}
140
}
141
return false;
142
}
143
static bool IsControllerSwitchJoyConR(GCController *controller)
144
{
145
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
146
if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (R)"]) {
147
return true;
148
}
149
}
150
return false;
151
}
152
static bool IsControllerSwitchJoyConPair(GCController *controller)
153
{
154
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
155
if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) {
156
return true;
157
}
158
}
159
return false;
160
}
161
static bool IsControllerStadia(GCController *controller)
162
{
163
if ([controller.vendorName hasPrefix:@"Stadia"]) {
164
return true;
165
}
166
return false;
167
}
168
static bool IsControllerBackboneOne(GCController *controller)
169
{
170
if ([controller.vendorName hasPrefix:@"Backbone One"]) {
171
return true;
172
}
173
return false;
174
}
175
static void CheckControllerSiriRemote(GCController *controller, int *is_siri_remote)
176
{
177
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
178
if ([controller.productCategory hasPrefix:@"Siri Remote"]) {
179
*is_siri_remote = 1;
180
SDL_sscanf(controller.productCategory.UTF8String, "Siri Remote (%i%*s Generation)", is_siri_remote);
181
return;
182
}
183
}
184
*is_siri_remote = 0;
185
}
186
187
static bool ElementAlreadyHandled(SDL_JoystickDeviceItem *device, NSString *element, NSDictionary<NSString *, GCControllerElement *> *elements)
188
{
189
if ([element isEqualToString:@"Left Thumbstick Left"] ||
190
[element isEqualToString:@"Left Thumbstick Right"]) {
191
if (elements[@"Left Thumbstick X Axis"]) {
192
return true;
193
}
194
}
195
if ([element isEqualToString:@"Left Thumbstick Up"] ||
196
[element isEqualToString:@"Left Thumbstick Down"]) {
197
if (elements[@"Left Thumbstick Y Axis"]) {
198
return true;
199
}
200
}
201
if ([element isEqualToString:@"Right Thumbstick Left"] ||
202
[element isEqualToString:@"Right Thumbstick Right"]) {
203
if (elements[@"Right Thumbstick X Axis"]) {
204
return true;
205
}
206
}
207
if ([element isEqualToString:@"Right Thumbstick Up"] ||
208
[element isEqualToString:@"Right Thumbstick Down"]) {
209
if (elements[@"Right Thumbstick Y Axis"]) {
210
return true;
211
}
212
}
213
if (device->is_siri_remote) {
214
if ([element isEqualToString:@"Direction Pad Left"] ||
215
[element isEqualToString:@"Direction Pad Right"]) {
216
if (elements[@"Direction Pad X Axis"]) {
217
return true;
218
}
219
}
220
if ([element isEqualToString:@"Direction Pad Up"] ||
221
[element isEqualToString:@"Direction Pad Down"]) {
222
if (elements[@"Direction Pad Y Axis"]) {
223
return true;
224
}
225
}
226
} else {
227
if ([element isEqualToString:@"Direction Pad X Axis"]) {
228
if (elements[@"Direction Pad Left"] &&
229
elements[@"Direction Pad Right"]) {
230
return true;
231
}
232
}
233
if ([element isEqualToString:@"Direction Pad Y Axis"]) {
234
if (elements[@"Direction Pad Up"] &&
235
elements[@"Direction Pad Down"]) {
236
return true;
237
}
238
}
239
}
240
if ([element isEqualToString:@"Cardinal Direction Pad X Axis"]) {
241
if (elements[@"Cardinal Direction Pad Left"] &&
242
elements[@"Cardinal Direction Pad Right"]) {
243
return true;
244
}
245
}
246
if ([element isEqualToString:@"Cardinal Direction Pad Y Axis"]) {
247
if (elements[@"Cardinal Direction Pad Up"] &&
248
elements[@"Cardinal Direction Pad Down"]) {
249
return true;
250
}
251
}
252
if ([element isEqualToString:@"Touchpad 1 X Axis"] ||
253
[element isEqualToString:@"Touchpad 1 Y Axis"] ||
254
[element isEqualToString:@"Touchpad 1 Left"] ||
255
[element isEqualToString:@"Touchpad 1 Right"] ||
256
[element isEqualToString:@"Touchpad 1 Up"] ||
257
[element isEqualToString:@"Touchpad 1 Down"] ||
258
[element isEqualToString:@"Touchpad 2 X Axis"] ||
259
[element isEqualToString:@"Touchpad 2 Y Axis"] ||
260
[element isEqualToString:@"Touchpad 2 Left"] ||
261
[element isEqualToString:@"Touchpad 2 Right"] ||
262
[element isEqualToString:@"Touchpad 2 Up"] ||
263
[element isEqualToString:@"Touchpad 2 Down"]) {
264
// The touchpad is handled separately
265
return true;
266
}
267
if ([element isEqualToString:@"Button Home"]) {
268
if (device->is_switch_joycon_pair) {
269
// The Nintendo Switch JoyCon home button doesn't ever show as being held down
270
return true;
271
}
272
#ifdef SDL_PLATFORM_TVOS
273
// The OS uses the home button, it's not available to apps
274
return true;
275
#endif
276
}
277
if ([element isEqualToString:@"Button Share"]) {
278
if (device->is_backbone_one) {
279
// The Backbone app uses share button
280
return true;
281
}
282
}
283
return false;
284
}
285
286
static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
287
{
288
Uint16 vendor = 0;
289
Uint16 product = 0;
290
Uint8 subtype = 0;
291
const char *name = NULL;
292
293
if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) {
294
if (!GCController.shouldMonitorBackgroundEvents) {
295
GCController.shouldMonitorBackgroundEvents = YES;
296
}
297
}
298
299
/* Explicitly retain the controller because SDL_JoystickDeviceItem is a
300
* struct, and ARC doesn't work with structs. */
301
device->controller = (__bridge GCController *)CFBridgingRetain(controller);
302
303
if (controller.vendorName) {
304
name = controller.vendorName.UTF8String;
305
}
306
307
if (!name) {
308
name = "MFi Gamepad";
309
}
310
311
device->name = SDL_CreateJoystickName(0, 0, NULL, name);
312
313
#ifdef DEBUG_CONTROLLER_PROFILE
314
NSLog(@"Product name: %@\n", controller.vendorName);
315
NSLog(@"Product category: %@\n", controller.productCategory);
316
NSLog(@"Elements available:\n");
317
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
318
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
319
for (id key in controller.physicalInputProfile.buttons) {
320
NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
321
}
322
for (id key in controller.physicalInputProfile.axes) {
323
NSLog(@"\tAxis: %@\n", key);
324
}
325
for (id key in controller.physicalInputProfile.dpads) {
326
NSLog(@"\tHat: %@\n", key);
327
}
328
}
329
#endif // DEBUG_CONTROLLER_PROFILE
330
331
device->is_xbox = IsControllerXbox(controller);
332
device->is_ps4 = IsControllerPS4(controller);
333
device->is_ps5 = IsControllerPS5(controller);
334
device->is_switch_pro = IsControllerSwitchPro(controller);
335
device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller);
336
device->is_stadia = IsControllerStadia(controller);
337
device->is_backbone_one = IsControllerBackboneOne(controller);
338
device->is_switch_joyconL = IsControllerSwitchJoyConL(controller);
339
device->is_switch_joyconR = IsControllerSwitchJoyConR(controller);
340
#ifdef SDL_JOYSTICK_HIDAPI
341
if ((device->is_xbox && (HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE) ||
342
HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOX360))) ||
343
(device->is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) ||
344
(device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) ||
345
(device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) ||
346
(device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) ||
347
(device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) ||
348
(device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) ||
349
(device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) {
350
// The HIDAPI driver is taking care of this device
351
return false;
352
}
353
#endif
354
if (device->is_xbox && SDL_strncmp(name, "GamePad-", 8) == 0) {
355
// This is a Steam Virtual Gamepad, which isn't supported by GCController
356
return false;
357
}
358
CheckControllerSiriRemote(controller, &device->is_siri_remote);
359
360
if (device->is_siri_remote && !SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) {
361
// Ignore remotes, they'll be handled as keyboard input
362
return false;
363
}
364
365
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
366
if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
367
device->has_dualshock_touchpad = TRUE;
368
}
369
if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
370
device->has_xbox_paddles = TRUE;
371
}
372
if (controller.physicalInputProfile.buttons[@"Button Share"] != nil) {
373
device->has_xbox_share_button = TRUE;
374
}
375
}
376
377
if (device->is_backbone_one) {
378
vendor = USB_VENDOR_BACKBONE;
379
if (device->is_ps5) {
380
product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5;
381
} else {
382
product = USB_PRODUCT_BACKBONE_ONE_IOS;
383
}
384
} else if (device->is_xbox) {
385
vendor = USB_VENDOR_MICROSOFT;
386
if (device->has_xbox_paddles) {
387
// Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID
388
product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH;
389
} else if (device->has_xbox_share_button) {
390
// Assume Xbox Series X Controller unless/until GCController flows VID/PID
391
product = USB_PRODUCT_XBOX_SERIES_X_BLE;
392
} else {
393
// Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID
394
product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
395
}
396
} else if (device->is_ps4) {
397
// Assume DS4 Slim unless/until GCController flows VID/PID
398
vendor = USB_VENDOR_SONY;
399
product = USB_PRODUCT_SONY_DS4_SLIM;
400
if (device->has_dualshock_touchpad) {
401
subtype = 1;
402
}
403
} else if (device->is_ps5) {
404
vendor = USB_VENDOR_SONY;
405
product = USB_PRODUCT_SONY_DS5;
406
} else if (device->is_switch_pro) {
407
vendor = USB_VENDOR_NINTENDO;
408
product = USB_PRODUCT_NINTENDO_SWITCH_PRO;
409
device->has_nintendo_buttons = TRUE;
410
} else if (device->is_switch_joycon_pair) {
411
vendor = USB_VENDOR_NINTENDO;
412
product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
413
device->has_nintendo_buttons = TRUE;
414
} else if (device->is_switch_joyconL) {
415
vendor = USB_VENDOR_NINTENDO;
416
product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT;
417
} else if (device->is_switch_joyconR) {
418
vendor = USB_VENDOR_NINTENDO;
419
product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT;
420
} else if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
421
vendor = USB_VENDOR_APPLE;
422
product = 4;
423
subtype = 4;
424
} else if (controller.extendedGamepad) {
425
vendor = USB_VENDOR_APPLE;
426
product = 1;
427
subtype = 1;
428
#ifdef SDL_PLATFORM_TVOS
429
} else if (controller.microGamepad) {
430
vendor = USB_VENDOR_APPLE;
431
product = 3;
432
subtype = 3;
433
#endif
434
} else {
435
// We don't know how to get input events from this device
436
return false;
437
}
438
439
if (SDL_ShouldIgnoreJoystick(vendor, product, 0, name)) {
440
return false;
441
}
442
443
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
444
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
445
446
// Provide both axes and analog buttons as SDL axes
447
NSArray *axes = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
448
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
449
if (ElementAlreadyHandled(device, (NSString *)object, elements)) {
450
return false;
451
}
452
453
GCControllerElement *element = elements[object];
454
if (element.analog) {
455
if ([element isKindOfClass:[GCControllerAxisInput class]] ||
456
[element isKindOfClass:[GCControllerButtonInput class]]) {
457
return true;
458
}
459
}
460
return false;
461
}]];
462
NSArray *buttons = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
463
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
464
if (ElementAlreadyHandled(device, (NSString *)object, elements)) {
465
return false;
466
}
467
468
GCControllerElement *element = elements[object];
469
if ([element isKindOfClass:[GCControllerButtonInput class]]) {
470
return true;
471
}
472
return false;
473
}]];
474
/* Explicitly retain the arrays because SDL_JoystickDeviceItem is a
475
* struct, and ARC doesn't work with structs. */
476
device->naxes = (int)axes.count;
477
device->axes = (__bridge NSArray *)CFBridgingRetain(axes);
478
device->nbuttons = (int)buttons.count;
479
device->buttons = (__bridge NSArray *)CFBridgingRetain(buttons);
480
subtype = 4;
481
482
#ifdef DEBUG_CONTROLLER_PROFILE
483
NSLog(@"Elements used:\n", controller.vendorName);
484
for (id key in device->buttons) {
485
NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
486
}
487
for (id key in device->axes) {
488
NSLog(@"\tAxis: %@\n", key);
489
}
490
#endif // DEBUG_CONTROLLER_PROFILE
491
492
#ifdef SDL_PLATFORM_TVOS
493
// tvOS turns the menu button into a system gesture, so we grab it here instead
494
if (elements[GCInputButtonMenu] && !elements[@"Button Home"]) {
495
device->pause_button_index = (int)[device->buttons indexOfObject:GCInputButtonMenu];
496
}
497
#endif
498
} else if (controller.extendedGamepad) {
499
GCExtendedGamepad *gamepad = controller.extendedGamepad;
500
int nbuttons = 0;
501
BOOL has_direct_menu = FALSE;
502
503
// These buttons are part of the original MFi spec
504
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
505
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
506
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST);
507
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_NORTH);
508
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
509
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
510
nbuttons += 6;
511
512
// These buttons are available on some newer controllers
513
if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
514
if (gamepad.leftThumbstickButton) {
515
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK);
516
++nbuttons;
517
}
518
if (gamepad.rightThumbstickButton) {
519
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK);
520
++nbuttons;
521
}
522
}
523
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
524
if (gamepad.buttonOptions) {
525
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_BACK);
526
++nbuttons;
527
}
528
}
529
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START);
530
++nbuttons;
531
532
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
533
if (gamepad.buttonMenu) {
534
has_direct_menu = TRUE;
535
}
536
}
537
#ifdef SDL_PLATFORM_TVOS
538
// The single menu button isn't very reliable, at least as of tvOS 16.1
539
if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) == 0) {
540
has_direct_menu = FALSE;
541
}
542
#endif
543
if (!has_direct_menu) {
544
device->pause_button_index = (nbuttons - 1);
545
}
546
547
device->naxes = 6; // 2 thumbsticks and 2 triggers
548
device->nhats = 1; // d-pad
549
device->nbuttons = nbuttons;
550
}
551
#ifdef SDL_PLATFORM_TVOS
552
else if (controller.microGamepad) {
553
int nbuttons = 0;
554
555
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
556
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); // Button X on microGamepad
557
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
558
nbuttons += 3;
559
device->pause_button_index = (nbuttons - 1);
560
561
device->naxes = 2; // treat the touch surface as two axes
562
device->nhats = 0; // apparently the touch surface-as-dpad is buggy
563
device->nbuttons = nbuttons;
564
565
controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, false);
566
}
567
#endif
568
else {
569
// We don't know how to get input events from this device
570
return false;
571
}
572
573
Uint16 signature;
574
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
575
signature = 0;
576
signature = SDL_crc16(signature, device->name, SDL_strlen(device->name));
577
for (id key in device->axes) {
578
const char *string = ((NSString *)key).UTF8String;
579
signature = SDL_crc16(signature, string, SDL_strlen(string));
580
}
581
for (id key in device->buttons) {
582
const char *string = ((NSString *)key).UTF8String;
583
signature = SDL_crc16(signature, string, SDL_strlen(string));
584
}
585
} else {
586
signature = device->button_mask;
587
}
588
device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, signature, NULL, name, 'm', subtype);
589
590
/* This will be set when the first button press of the controller is
591
* detected. */
592
controller.playerIndex = -1;
593
return true;
594
}
595
#endif // SDL_JOYSTICK_MFI
596
597
#ifdef SDL_JOYSTICK_MFI
598
static void IOS_AddJoystickDevice(GCController *controller)
599
{
600
SDL_JoystickDeviceItem *device = deviceList;
601
602
while (device != NULL) {
603
if (device->controller == controller) {
604
return;
605
}
606
device = device->next;
607
}
608
609
device = (SDL_JoystickDeviceItem *)SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
610
if (device == NULL) {
611
return;
612
}
613
614
device->instance_id = SDL_GetNextObjectID();
615
device->pause_button_index = -1;
616
617
if (controller) {
618
#ifdef SDL_JOYSTICK_MFI
619
if (!IOS_AddMFIJoystickDevice(device, controller)) {
620
SDL_free(device->name);
621
SDL_free(device);
622
return;
623
}
624
#else
625
SDL_free(device);
626
return;
627
#endif // SDL_JOYSTICK_MFI
628
}
629
630
if (deviceList == NULL) {
631
deviceList = device;
632
} else {
633
SDL_JoystickDeviceItem *lastdevice = deviceList;
634
while (lastdevice->next != NULL) {
635
lastdevice = lastdevice->next;
636
}
637
lastdevice->next = device;
638
}
639
640
++numjoysticks;
641
642
SDL_PrivateJoystickAdded(device->instance_id);
643
}
644
#endif // SDL_JOYSTICK_MFI
645
646
static SDL_JoystickDeviceItem *IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
647
{
648
SDL_JoystickDeviceItem *prev = NULL;
649
SDL_JoystickDeviceItem *next = NULL;
650
SDL_JoystickDeviceItem *item = deviceList;
651
652
if (device == NULL) {
653
return NULL;
654
}
655
656
next = device->next;
657
658
while (item != NULL) {
659
if (item == device) {
660
break;
661
}
662
prev = item;
663
item = item->next;
664
}
665
666
// Unlink the device item from the device list.
667
if (prev) {
668
prev->next = device->next;
669
} else if (device == deviceList) {
670
deviceList = device->next;
671
}
672
673
if (device->joystick) {
674
device->joystick->hwdata = NULL;
675
}
676
677
#ifdef SDL_JOYSTICK_MFI
678
@autoreleasepool {
679
// These were explicitly retained in the struct, so they should be explicitly released before freeing the struct.
680
if (device->controller) {
681
GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
682
controller.controllerPausedHandler = nil;
683
device->controller = nil;
684
}
685
if (device->axes) {
686
CFRelease((__bridge CFTypeRef)device->axes);
687
device->axes = nil;
688
}
689
if (device->buttons) {
690
CFRelease((__bridge CFTypeRef)device->buttons);
691
device->buttons = nil;
692
}
693
}
694
#endif // SDL_JOYSTICK_MFI
695
696
--numjoysticks;
697
698
SDL_PrivateJoystickRemoved(device->instance_id);
699
700
SDL_free(device->name);
701
SDL_free(device);
702
703
return next;
704
}
705
706
#ifdef SDL_PLATFORM_TVOS
707
static void SDLCALL SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
708
{
709
BOOL allowRotation = newValue != NULL && *newValue != '0';
710
711
@autoreleasepool {
712
for (GCController *controller in [GCController controllers]) {
713
if (controller.microGamepad) {
714
controller.microGamepad.allowsRotation = allowRotation;
715
}
716
}
717
}
718
}
719
#endif // SDL_PLATFORM_TVOS
720
721
static bool IOS_JoystickInit(void)
722
{
723
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) {
724
return true;
725
}
726
727
#ifdef SDL_PLATFORM_MACOS
728
if (@available(macOS 10.16, *)) {
729
// Continue with initialization on macOS 11+
730
} else {
731
return true;
732
}
733
#endif
734
735
@autoreleasepool {
736
#ifdef SDL_JOYSTICK_MFI
737
NSNotificationCenter *center;
738
#endif
739
740
#ifdef SDL_JOYSTICK_MFI
741
// GameController.framework was added in iOS 7.
742
if (![GCController class]) {
743
return true;
744
}
745
746
/* For whatever reason, this always returns an empty array on
747
macOS 11.0.1 */
748
for (GCController *controller in [GCController controllers]) {
749
IOS_AddJoystickDevice(controller);
750
}
751
752
#ifdef SDL_PLATFORM_TVOS
753
SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
754
SDL_AppleTVRemoteRotationHintChanged, NULL);
755
#endif // SDL_PLATFORM_TVOS
756
757
center = [NSNotificationCenter defaultCenter];
758
759
connectObserver = [center addObserverForName:GCControllerDidConnectNotification
760
object:nil
761
queue:nil
762
usingBlock:^(NSNotification *note) {
763
GCController *controller = note.object;
764
SDL_LockJoysticks();
765
IOS_AddJoystickDevice(controller);
766
SDL_UnlockJoysticks();
767
}];
768
769
disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
770
object:nil
771
queue:nil
772
usingBlock:^(NSNotification *note) {
773
GCController *controller = note.object;
774
SDL_JoystickDeviceItem *device;
775
SDL_LockJoysticks();
776
for (device = deviceList; device != NULL; device = device->next) {
777
if (device->controller == controller) {
778
IOS_RemoveJoystickDevice(device);
779
break;
780
}
781
}
782
SDL_UnlockJoysticks();
783
}];
784
#endif // SDL_JOYSTICK_MFI
785
}
786
787
return true;
788
}
789
790
static int IOS_JoystickGetCount(void)
791
{
792
return numjoysticks;
793
}
794
795
static void IOS_JoystickDetect(void)
796
{
797
}
798
799
static bool IOS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
800
{
801
// We don't override any other drivers through this method
802
return false;
803
}
804
805
static const char *IOS_JoystickGetDeviceName(int device_index)
806
{
807
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
808
return device ? device->name : "Unknown";
809
}
810
811
static const char *IOS_JoystickGetDevicePath(int device_index)
812
{
813
return NULL;
814
}
815
816
static int IOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
817
{
818
return -1;
819
}
820
821
static int IOS_JoystickGetDevicePlayerIndex(int device_index)
822
{
823
#ifdef SDL_JOYSTICK_MFI
824
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
825
if (device && device->controller) {
826
return (int)device->controller.playerIndex;
827
}
828
#endif
829
return -1;
830
}
831
832
static void IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
833
{
834
#ifdef SDL_JOYSTICK_MFI
835
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
836
if (device && device->controller) {
837
device->controller.playerIndex = player_index;
838
}
839
#endif
840
}
841
842
static SDL_GUID IOS_JoystickGetDeviceGUID(int device_index)
843
{
844
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
845
SDL_GUID guid;
846
if (device) {
847
guid = device->guid;
848
} else {
849
SDL_zero(guid);
850
}
851
return guid;
852
}
853
854
static SDL_JoystickID IOS_JoystickGetDeviceInstanceID(int device_index)
855
{
856
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
857
return device ? device->instance_id : 0;
858
}
859
860
static bool IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
861
{
862
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
863
if (device == NULL) {
864
return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
865
}
866
867
joystick->hwdata = device;
868
869
joystick->naxes = device->naxes;
870
joystick->nhats = device->nhats;
871
joystick->nbuttons = device->nbuttons;
872
873
if (device->has_dualshock_touchpad) {
874
SDL_PrivateJoystickAddTouchpad(joystick, 2);
875
}
876
877
device->joystick = joystick;
878
879
@autoreleasepool {
880
#ifdef SDL_JOYSTICK_MFI
881
if (device->pause_button_index >= 0) {
882
GCController *controller = device->controller;
883
controller.controllerPausedHandler = ^(GCController *c) {
884
if (joystick->hwdata) {
885
joystick->hwdata->pause_button_pressed = SDL_GetTicks();
886
}
887
};
888
}
889
890
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
891
GCController *controller = joystick->hwdata->controller;
892
GCMotion *motion = controller.motion;
893
if (motion && motion.hasRotationRate) {
894
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
895
}
896
if (motion && motion.hasGravityAndUserAcceleration) {
897
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
898
}
899
}
900
901
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
902
GCController *controller = joystick->hwdata->controller;
903
for (id key in controller.physicalInputProfile.buttons) {
904
GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
905
if ([button isBoundToSystemGesture]) {
906
button.preferredSystemGestureState = GCSystemGestureStateDisabled;
907
}
908
}
909
}
910
911
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
912
GCController *controller = device->controller;
913
if (controller.light) {
914
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
915
}
916
917
if (controller.haptics) {
918
for (GCHapticsLocality locality in controller.haptics.supportedLocalities) {
919
if ([locality isEqualToString:GCHapticsLocalityHandles]) {
920
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
921
} else if ([locality isEqualToString:GCHapticsLocalityTriggers]) {
922
SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
923
}
924
}
925
}
926
}
927
#endif // SDL_JOYSTICK_MFI
928
}
929
if (device->is_siri_remote) {
930
++SDL_AppleTVRemoteOpenedAsJoystick;
931
}
932
933
return true;
934
}
935
936
#ifdef SDL_JOYSTICK_MFI
937
static Uint8 IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
938
{
939
Uint8 hat = 0;
940
941
if (dpad.up.isPressed) {
942
hat |= SDL_HAT_UP;
943
} else if (dpad.down.isPressed) {
944
hat |= SDL_HAT_DOWN;
945
}
946
947
if (dpad.left.isPressed) {
948
hat |= SDL_HAT_LEFT;
949
} else if (dpad.right.isPressed) {
950
hat |= SDL_HAT_RIGHT;
951
}
952
953
if (hat == 0) {
954
return SDL_HAT_CENTERED;
955
}
956
957
return hat;
958
}
959
#endif
960
961
static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
962
{
963
#ifdef SDL_JOYSTICK_MFI
964
@autoreleasepool {
965
SDL_JoystickDeviceItem *device = joystick->hwdata;
966
GCController *controller = device->controller;
967
Uint8 hatstate = SDL_HAT_CENTERED;
968
int i;
969
Uint64 timestamp = SDL_GetTicksNS();
970
971
#ifdef DEBUG_CONTROLLER_STATE
972
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
973
if (controller.physicalInputProfile) {
974
for (id key in controller.physicalInputProfile.buttons) {
975
GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
976
if (button.isPressed)
977
NSLog(@"Button %@ = %s\n", key, button.isPressed ? "pressed" : "released");
978
}
979
for (id key in controller.physicalInputProfile.axes) {
980
GCControllerAxisInput *axis = controller.physicalInputProfile.axes[key];
981
if (axis.value != 0.0f)
982
NSLog(@"Axis %@ = %g\n", key, axis.value);
983
}
984
for (id key in controller.physicalInputProfile.dpads) {
985
GCControllerDirectionPad *dpad = controller.physicalInputProfile.dpads[key];
986
if (dpad.up.isPressed || dpad.down.isPressed || dpad.left.isPressed || dpad.right.isPressed) {
987
NSLog(@"Hat %@ =%s%s%s%s\n", key,
988
dpad.up.isPressed ? " UP" : "",
989
dpad.down.isPressed ? " DOWN" : "",
990
dpad.left.isPressed ? " LEFT" : "",
991
dpad.right.isPressed ? " RIGHT" : "");
992
}
993
}
994
}
995
}
996
#endif // DEBUG_CONTROLLER_STATE
997
998
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
999
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
1000
NSDictionary<NSString *, GCControllerButtonInput *> *buttons = controller.physicalInputProfile.buttons;
1001
1002
int axis = 0;
1003
for (id key in device->axes) {
1004
Sint16 value;
1005
GCControllerElement *element = elements[key];
1006
if ([element isKindOfClass:[GCControllerAxisInput class]]) {
1007
value = (Sint16)([(GCControllerAxisInput *)element value] * 32767);
1008
} else {
1009
value = (Sint16)([(GCControllerButtonInput *)element value] * 32767);
1010
}
1011
SDL_SendJoystickAxis(timestamp, joystick, axis++, value);
1012
}
1013
1014
int button = 0;
1015
for (id key in device->buttons) {
1016
bool down;
1017
if (button == device->pause_button_index) {
1018
down = (device->pause_button_pressed > 0);
1019
} else {
1020
down = buttons[key].isPressed;
1021
}
1022
SDL_SendJoystickButton(timestamp, joystick, button++, down);
1023
}
1024
} else if (controller.extendedGamepad) {
1025
bool isstack;
1026
GCExtendedGamepad *gamepad = controller.extendedGamepad;
1027
1028
// Axis order matches the XInput Windows mappings.
1029
Sint16 axes[] = {
1030
(Sint16)(gamepad.leftThumbstick.xAxis.value * 32767),
1031
(Sint16)(gamepad.leftThumbstick.yAxis.value * -32767),
1032
(Sint16)((gamepad.leftTrigger.value * 65535) - 32768),
1033
(Sint16)(gamepad.rightThumbstick.xAxis.value * 32767),
1034
(Sint16)(gamepad.rightThumbstick.yAxis.value * -32767),
1035
(Sint16)((gamepad.rightTrigger.value * 65535) - 32768),
1036
};
1037
1038
// Button order matches the XInput Windows mappings.
1039
bool *buttons = SDL_small_alloc(bool, joystick->nbuttons, &isstack);
1040
int button_count = 0;
1041
1042
if (buttons == NULL) {
1043
return;
1044
}
1045
1046
// These buttons are part of the original MFi spec
1047
buttons[button_count++] = gamepad.buttonA.isPressed;
1048
buttons[button_count++] = gamepad.buttonB.isPressed;
1049
buttons[button_count++] = gamepad.buttonX.isPressed;
1050
buttons[button_count++] = gamepad.buttonY.isPressed;
1051
buttons[button_count++] = gamepad.leftShoulder.isPressed;
1052
buttons[button_count++] = gamepad.rightShoulder.isPressed;
1053
1054
// These buttons are available on some newer controllers
1055
if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
1056
if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
1057
buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
1058
}
1059
if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {
1060
buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
1061
}
1062
}
1063
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
1064
if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
1065
buttons[button_count++] = gamepad.buttonOptions.isPressed;
1066
}
1067
}
1068
if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
1069
if (device->pause_button_index >= 0) {
1070
// Guaranteed if buttonMenu is not supported on this OS
1071
buttons[button_count++] = (device->pause_button_pressed > 0);
1072
} else {
1073
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
1074
buttons[button_count++] = gamepad.buttonMenu.isPressed;
1075
}
1076
}
1077
}
1078
1079
hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
1080
1081
for (i = 0; i < SDL_arraysize(axes); i++) {
1082
SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]);
1083
}
1084
1085
for (i = 0; i < button_count; i++) {
1086
SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]);
1087
}
1088
1089
SDL_small_free(buttons, isstack);
1090
}
1091
#ifdef SDL_PLATFORM_TVOS
1092
else if (controller.microGamepad) {
1093
GCMicroGamepad *gamepad = controller.microGamepad;
1094
1095
Sint16 axes[] = {
1096
(Sint16)(gamepad.dpad.xAxis.value * 32767),
1097
(Sint16)(gamepad.dpad.yAxis.value * -32767),
1098
};
1099
1100
for (i = 0; i < SDL_arraysize(axes); i++) {
1101
SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]);
1102
}
1103
1104
bool buttons[joystick->nbuttons];
1105
int button_count = 0;
1106
buttons[button_count++] = gamepad.buttonA.isPressed;
1107
buttons[button_count++] = gamepad.buttonX.isPressed;
1108
buttons[button_count++] = (device->pause_button_pressed > 0);
1109
1110
for (i = 0; i < button_count; i++) {
1111
SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]);
1112
}
1113
}
1114
#endif // SDL_PLATFORM_TVOS
1115
1116
if (joystick->nhats > 0) {
1117
SDL_SendJoystickHat(timestamp, joystick, 0, hatstate);
1118
}
1119
1120
if (device->pause_button_pressed) {
1121
// The pause callback is instantaneous, so we extend the duration to allow "holding down" by pressing it repeatedly
1122
const int PAUSE_BUTTON_PRESS_DURATION_MS = 250;
1123
if (SDL_GetTicks() >= device->pause_button_pressed + PAUSE_BUTTON_PRESS_DURATION_MS) {
1124
device->pause_button_pressed = 0;
1125
}
1126
}
1127
1128
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1129
if (device->has_dualshock_touchpad) {
1130
GCControllerDirectionPad *dpad;
1131
1132
dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne];
1133
if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) {
1134
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
1135
} else {
1136
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, false, 0.0f, 0.0f, 1.0f);
1137
}
1138
1139
dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo];
1140
if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) {
1141
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
1142
} else {
1143
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, false, 0.0f, 0.0f, 1.0f);
1144
}
1145
}
1146
}
1147
1148
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1149
GCMotion *motion = controller.motion;
1150
if (motion && motion.sensorsActive) {
1151
float data[3];
1152
1153
if (motion.hasRotationRate) {
1154
GCRotationRate rate = motion.rotationRate;
1155
data[0] = rate.x;
1156
data[1] = rate.z;
1157
data[2] = -rate.y;
1158
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3);
1159
}
1160
if (motion.hasGravityAndUserAcceleration) {
1161
GCAcceleration accel = motion.acceleration;
1162
data[0] = -accel.x * SDL_STANDARD_GRAVITY;
1163
data[1] = -accel.y * SDL_STANDARD_GRAVITY;
1164
data[2] = -accel.z * SDL_STANDARD_GRAVITY;
1165
SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3);
1166
}
1167
}
1168
}
1169
1170
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1171
GCDeviceBattery *battery = controller.battery;
1172
if (battery) {
1173
SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
1174
int percent = (int)SDL_roundf(battery.batteryLevel * 100.0f);
1175
1176
switch (battery.batteryState) {
1177
case GCDeviceBatteryStateDischarging:
1178
state = SDL_POWERSTATE_ON_BATTERY;
1179
break;
1180
case GCDeviceBatteryStateCharging:
1181
state = SDL_POWERSTATE_CHARGING;
1182
break;
1183
case GCDeviceBatteryStateFull:
1184
state = SDL_POWERSTATE_CHARGED;
1185
break;
1186
default:
1187
break;
1188
}
1189
1190
SDL_SendJoystickPowerInfo(joystick, state, percent);
1191
}
1192
}
1193
}
1194
#endif // SDL_JOYSTICK_MFI
1195
}
1196
1197
#ifdef SDL_JOYSTICK_MFI
1198
@interface SDL3_RumbleMotor : NSObject
1199
@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0));
1200
@property(nonatomic, strong) id<CHHapticPatternPlayer> player API_AVAILABLE(macos(10.16), ios(13.0), tvos(14.0));
1201
@property bool active;
1202
@end
1203
1204
@implementation SDL3_RumbleMotor
1205
{
1206
}
1207
1208
- (void)cleanup
1209
{
1210
@autoreleasepool {
1211
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1212
if (self.player != nil) {
1213
[self.player cancelAndReturnError:nil];
1214
self.player = nil;
1215
}
1216
if (self.engine != nil) {
1217
[self.engine stopWithCompletionHandler:nil];
1218
self.engine = nil;
1219
}
1220
}
1221
}
1222
}
1223
1224
- (bool)setIntensity:(float)intensity
1225
{
1226
@autoreleasepool {
1227
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1228
NSError *error = nil;
1229
CHHapticDynamicParameter *param;
1230
1231
if (self.engine == nil) {
1232
return SDL_SetError("Haptics engine was stopped");
1233
}
1234
1235
if (intensity == 0.0f) {
1236
if (self.player && self.active) {
1237
[self.player stopAtTime:0 error:&error];
1238
}
1239
self.active = false;
1240
return true;
1241
}
1242
1243
if (self.player == nil) {
1244
CHHapticEventParameter *event_param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
1245
CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:event_param, nil] relativeTime:0 duration:GCHapticDurationInfinite];
1246
CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error];
1247
if (error != nil) {
1248
return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]);
1249
}
1250
1251
self.player = [self.engine createPlayerWithPattern:pattern error:&error];
1252
if (error != nil) {
1253
return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]);
1254
}
1255
self.active = false;
1256
}
1257
1258
param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0];
1259
[self.player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error];
1260
if (error != nil) {
1261
return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]);
1262
}
1263
1264
if (!self.active) {
1265
[self.player startAtTime:0 error:&error];
1266
self.active = true;
1267
}
1268
}
1269
1270
return true;
1271
}
1272
}
1273
1274
- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(10.16), ios(14.0), tvos(14.0))
1275
{
1276
@autoreleasepool {
1277
NSError *error;
1278
__weak __typeof(self) weakSelf;
1279
self = [super init];
1280
weakSelf = self;
1281
1282
self.engine = [controller.haptics createEngineWithLocality:locality];
1283
if (self.engine == nil) {
1284
SDL_SetError("Couldn't create haptics engine");
1285
return nil;
1286
}
1287
1288
[self.engine startAndReturnError:&error];
1289
if (error != nil) {
1290
SDL_SetError("Couldn't start haptics engine");
1291
return nil;
1292
}
1293
1294
self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
1295
SDL3_RumbleMotor *_this = weakSelf;
1296
if (_this == nil) {
1297
return;
1298
}
1299
1300
_this.player = nil;
1301
_this.engine = nil;
1302
};
1303
self.engine.resetHandler = ^{
1304
SDL3_RumbleMotor *_this = weakSelf;
1305
if (_this == nil) {
1306
return;
1307
}
1308
1309
_this.player = nil;
1310
[_this.engine startAndReturnError:nil];
1311
};
1312
1313
return self;
1314
}
1315
}
1316
1317
@end
1318
1319
@interface SDL3_RumbleContext : NSObject
1320
@property(nonatomic, strong) SDL3_RumbleMotor *lowFrequencyMotor;
1321
@property(nonatomic, strong) SDL3_RumbleMotor *highFrequencyMotor;
1322
@property(nonatomic, strong) SDL3_RumbleMotor *leftTriggerMotor;
1323
@property(nonatomic, strong) SDL3_RumbleMotor *rightTriggerMotor;
1324
@end
1325
1326
@implementation SDL3_RumbleContext
1327
{
1328
}
1329
1330
- (id)initWithLowFrequencyMotor:(SDL3_RumbleMotor *)low_frequency_motor
1331
HighFrequencyMotor:(SDL3_RumbleMotor *)high_frequency_motor
1332
LeftTriggerMotor:(SDL3_RumbleMotor *)left_trigger_motor
1333
RightTriggerMotor:(SDL3_RumbleMotor *)right_trigger_motor
1334
{
1335
self = [super init];
1336
self.lowFrequencyMotor = low_frequency_motor;
1337
self.highFrequencyMotor = high_frequency_motor;
1338
self.leftTriggerMotor = left_trigger_motor;
1339
self.rightTriggerMotor = right_trigger_motor;
1340
return self;
1341
}
1342
1343
- (bool)rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble
1344
{
1345
bool result = true;
1346
1347
result &= [self.lowFrequencyMotor setIntensity:((float)low_frequency_rumble / 65535.0f)];
1348
result &= [self.highFrequencyMotor setIntensity:((float)high_frequency_rumble / 65535.0f)];
1349
return result;
1350
}
1351
1352
- (bool)rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble
1353
{
1354
bool result = false;
1355
1356
if (self.leftTriggerMotor && self.rightTriggerMotor) {
1357
result &= [self.leftTriggerMotor setIntensity:((float)left_rumble / 65535.0f)];
1358
result &= [self.rightTriggerMotor setIntensity:((float)right_rumble / 65535.0f)];
1359
} else {
1360
result = SDL_Unsupported();
1361
}
1362
return result;
1363
}
1364
1365
- (void)cleanup
1366
{
1367
[self.lowFrequencyMotor cleanup];
1368
[self.highFrequencyMotor cleanup];
1369
}
1370
1371
@end
1372
1373
static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller)
1374
{
1375
@autoreleasepool {
1376
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1377
SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
1378
SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
1379
SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger];
1380
SDL3_RumbleMotor *right_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger];
1381
if (low_frequency_motor && high_frequency_motor) {
1382
return [[SDL3_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor
1383
HighFrequencyMotor:high_frequency_motor
1384
LeftTriggerMotor:left_trigger_motor
1385
RightTriggerMotor:right_trigger_motor];
1386
}
1387
}
1388
}
1389
return nil;
1390
}
1391
1392
#endif // SDL_JOYSTICK_MFI
1393
1394
static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
1395
{
1396
#ifdef SDL_JOYSTICK_MFI
1397
SDL_JoystickDeviceItem *device = joystick->hwdata;
1398
1399
if (device == NULL) {
1400
return SDL_SetError("Controller is no longer connected");
1401
}
1402
1403
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1404
if (!device->rumble && device->controller && device->controller.haptics) {
1405
SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
1406
if (rumble) {
1407
device->rumble = (void *)CFBridgingRetain(rumble);
1408
}
1409
}
1410
}
1411
1412
if (device->rumble) {
1413
SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
1414
return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble];
1415
}
1416
#endif
1417
return SDL_Unsupported();
1418
}
1419
1420
static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
1421
{
1422
#ifdef SDL_JOYSTICK_MFI
1423
SDL_JoystickDeviceItem *device = joystick->hwdata;
1424
1425
if (device == NULL) {
1426
return SDL_SetError("Controller is no longer connected");
1427
}
1428
1429
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1430
if (!device->rumble && device->controller && device->controller.haptics) {
1431
SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
1432
if (rumble) {
1433
device->rumble = (void *)CFBridgingRetain(rumble);
1434
}
1435
}
1436
}
1437
1438
if (device->rumble) {
1439
SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
1440
return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble];
1441
}
1442
#endif
1443
return SDL_Unsupported();
1444
}
1445
1446
static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1447
{
1448
@autoreleasepool {
1449
SDL_JoystickDeviceItem *device = joystick->hwdata;
1450
1451
if (device == NULL) {
1452
return SDL_SetError("Controller is no longer connected");
1453
}
1454
1455
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1456
GCController *controller = device->controller;
1457
GCDeviceLight *light = controller.light;
1458
if (light) {
1459
light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f
1460
green:(float)green / 255.0f
1461
blue:(float)blue / 255.0f];
1462
return true;
1463
}
1464
}
1465
}
1466
return SDL_Unsupported();
1467
}
1468
1469
static bool IOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
1470
{
1471
return SDL_Unsupported();
1472
}
1473
1474
static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
1475
{
1476
@autoreleasepool {
1477
SDL_JoystickDeviceItem *device = joystick->hwdata;
1478
1479
if (device == NULL) {
1480
return SDL_SetError("Controller is no longer connected");
1481
}
1482
1483
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1484
GCController *controller = device->controller;
1485
GCMotion *motion = controller.motion;
1486
if (motion) {
1487
motion.sensorsActive = enabled ? YES : NO;
1488
return true;
1489
}
1490
}
1491
}
1492
1493
return SDL_Unsupported();
1494
}
1495
1496
static void IOS_JoystickUpdate(SDL_Joystick *joystick)
1497
{
1498
SDL_JoystickDeviceItem *device = joystick->hwdata;
1499
1500
if (device == NULL) {
1501
return;
1502
}
1503
1504
if (device->controller) {
1505
IOS_MFIJoystickUpdate(joystick);
1506
}
1507
}
1508
1509
static void IOS_JoystickClose(SDL_Joystick *joystick)
1510
{
1511
SDL_JoystickDeviceItem *device = joystick->hwdata;
1512
1513
if (device == NULL) {
1514
return;
1515
}
1516
1517
device->joystick = NULL;
1518
1519
#ifdef SDL_JOYSTICK_MFI
1520
@autoreleasepool {
1521
if (device->rumble) {
1522
SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
1523
1524
[rumble cleanup];
1525
CFRelease(device->rumble);
1526
device->rumble = NULL;
1527
}
1528
1529
if (device->controller) {
1530
GCController *controller = device->controller;
1531
controller.controllerPausedHandler = nil;
1532
controller.playerIndex = -1;
1533
1534
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1535
for (id key in controller.physicalInputProfile.buttons) {
1536
GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
1537
if ([button isBoundToSystemGesture]) {
1538
button.preferredSystemGestureState = GCSystemGestureStateEnabled;
1539
}
1540
}
1541
}
1542
}
1543
}
1544
#endif // SDL_JOYSTICK_MFI
1545
1546
if (device->is_siri_remote) {
1547
--SDL_AppleTVRemoteOpenedAsJoystick;
1548
}
1549
}
1550
1551
static void IOS_JoystickQuit(void)
1552
{
1553
@autoreleasepool {
1554
#ifdef SDL_JOYSTICK_MFI
1555
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1556
1557
if (connectObserver) {
1558
[center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
1559
connectObserver = nil;
1560
}
1561
1562
if (disconnectObserver) {
1563
[center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
1564
disconnectObserver = nil;
1565
}
1566
1567
#ifdef SDL_PLATFORM_TVOS
1568
SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
1569
SDL_AppleTVRemoteRotationHintChanged, NULL);
1570
#endif // SDL_PLATFORM_TVOS
1571
#endif // SDL_JOYSTICK_MFI
1572
1573
while (deviceList != NULL) {
1574
IOS_RemoveJoystickDevice(deviceList);
1575
}
1576
}
1577
1578
numjoysticks = 0;
1579
}
1580
1581
static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
1582
{
1583
SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
1584
if (device == NULL) {
1585
return false;
1586
}
1587
1588
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1589
int axis = 0;
1590
for (id key in device->axes) {
1591
if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] ||
1592
[(NSString *)key isEqualToString:@"Direction Pad X Axis"]) {
1593
out->leftx.kind = EMappingKind_Axis;
1594
out->leftx.target = axis;
1595
} else if ([(NSString *)key isEqualToString:@"Left Thumbstick Y Axis"] ||
1596
[(NSString *)key isEqualToString:@"Direction Pad Y Axis"]) {
1597
out->lefty.kind = EMappingKind_Axis;
1598
out->lefty.target = axis;
1599
out->lefty.axis_reversed = true;
1600
} else if ([(NSString *)key isEqualToString:@"Right Thumbstick X Axis"]) {
1601
out->rightx.kind = EMappingKind_Axis;
1602
out->rightx.target = axis;
1603
} else if ([(NSString *)key isEqualToString:@"Right Thumbstick Y Axis"]) {
1604
out->righty.kind = EMappingKind_Axis;
1605
out->righty.target = axis;
1606
out->righty.axis_reversed = true;
1607
} else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) {
1608
out->lefttrigger.kind = EMappingKind_Axis;
1609
out->lefttrigger.target = axis;
1610
out->lefttrigger.half_axis_positive = true;
1611
} else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) {
1612
out->righttrigger.kind = EMappingKind_Axis;
1613
out->righttrigger.target = axis;
1614
out->righttrigger.half_axis_positive = true;
1615
}
1616
++axis;
1617
}
1618
1619
int button = 0;
1620
for (id key in device->buttons) {
1621
SDL_InputMapping *mapping = NULL;
1622
1623
if ([(NSString *)key isEqualToString:GCInputButtonA]) {
1624
if (device->is_siri_remote > 1) {
1625
// GCInputButtonA is triggered for any D-Pad press, ignore it in favor of "Button Center"
1626
} else if (device->has_nintendo_buttons) {
1627
mapping = &out->b;
1628
} else {
1629
mapping = &out->a;
1630
}
1631
} else if ([(NSString *)key isEqualToString:GCInputButtonB]) {
1632
if (device->has_nintendo_buttons) {
1633
mapping = &out->a;
1634
} else if (device->is_switch_joyconL || device->is_switch_joyconR) {
1635
mapping = &out->x;
1636
} else {
1637
mapping = &out->b;
1638
}
1639
} else if ([(NSString *)key isEqualToString:GCInputButtonX]) {
1640
if (device->has_nintendo_buttons) {
1641
mapping = &out->y;
1642
} else if (device->is_switch_joyconL || device->is_switch_joyconR) {
1643
mapping = &out->b;
1644
} else {
1645
mapping = &out->x;
1646
}
1647
} else if ([(NSString *)key isEqualToString:GCInputButtonY]) {
1648
if (device->has_nintendo_buttons) {
1649
mapping = &out->x;
1650
} else {
1651
mapping = &out->y;
1652
}
1653
} else if ([(NSString *)key isEqualToString:@"Direction Pad Left"]) {
1654
mapping = &out->dpleft;
1655
} else if ([(NSString *)key isEqualToString:@"Direction Pad Right"]) {
1656
mapping = &out->dpright;
1657
} else if ([(NSString *)key isEqualToString:@"Direction Pad Up"]) {
1658
mapping = &out->dpup;
1659
} else if ([(NSString *)key isEqualToString:@"Direction Pad Down"]) {
1660
mapping = &out->dpdown;
1661
} else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Left"]) {
1662
mapping = &out->dpleft;
1663
} else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Right"]) {
1664
mapping = &out->dpright;
1665
} else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Up"]) {
1666
mapping = &out->dpup;
1667
} else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Down"]) {
1668
mapping = &out->dpdown;
1669
} else if ([(NSString *)key isEqualToString:GCInputLeftShoulder]) {
1670
mapping = &out->leftshoulder;
1671
} else if ([(NSString *)key isEqualToString:GCInputRightShoulder]) {
1672
mapping = &out->rightshoulder;
1673
} else if ([(NSString *)key isEqualToString:GCInputLeftThumbstickButton]) {
1674
mapping = &out->leftstick;
1675
} else if ([(NSString *)key isEqualToString:GCInputRightThumbstickButton]) {
1676
mapping = &out->rightstick;
1677
} else if ([(NSString *)key isEqualToString:@"Button Home"]) {
1678
mapping = &out->guide;
1679
} else if ([(NSString *)key isEqualToString:GCInputButtonMenu]) {
1680
if (device->is_siri_remote) {
1681
mapping = &out->b;
1682
} else {
1683
mapping = &out->start;
1684
}
1685
} else if ([(NSString *)key isEqualToString:GCInputButtonOptions]) {
1686
mapping = &out->back;
1687
} else if ([(NSString *)key isEqualToString:@"Button Share"]) {
1688
mapping = &out->misc1;
1689
} else if ([(NSString *)key isEqualToString:GCInputXboxPaddleOne]) {
1690
mapping = &out->right_paddle1;
1691
} else if ([(NSString *)key isEqualToString:GCInputXboxPaddleTwo]) {
1692
mapping = &out->right_paddle2;
1693
} else if ([(NSString *)key isEqualToString:GCInputXboxPaddleThree]) {
1694
mapping = &out->left_paddle1;
1695
} else if ([(NSString *)key isEqualToString:GCInputXboxPaddleFour]) {
1696
mapping = &out->left_paddle2;
1697
} else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) {
1698
mapping = &out->lefttrigger;
1699
} else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) {
1700
mapping = &out->righttrigger;
1701
} else if ([(NSString *)key isEqualToString:GCInputDualShockTouchpadButton]) {
1702
mapping = &out->touchpad;
1703
} else if ([(NSString *)key isEqualToString:@"Button Center"]) {
1704
mapping = &out->a;
1705
}
1706
if (mapping && mapping->kind == EMappingKind_None) {
1707
mapping->kind = EMappingKind_Button;
1708
mapping->target = button;
1709
}
1710
++button;
1711
}
1712
1713
return true;
1714
}
1715
return false;
1716
}
1717
1718
#if defined(SDL_JOYSTICK_MFI) && defined(SDL_PLATFORM_MACOS)
1719
bool IOS_SupportedHIDDevice(IOHIDDeviceRef device)
1720
{
1721
if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) {
1722
return false;
1723
}
1724
1725
if (@available(macOS 10.16, *)) {
1726
const int MAX_ATTEMPTS = 3;
1727
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
1728
if ([GCController supportsHIDDevice:device]) {
1729
return true;
1730
}
1731
1732
// The framework may not have seen the device yet
1733
SDL_Delay(10);
1734
}
1735
}
1736
return false;
1737
}
1738
#endif
1739
1740
#ifdef SDL_JOYSTICK_MFI
1741
/* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */
1742
static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name)
1743
{
1744
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1745
if (element) {
1746
[element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding];
1747
}
1748
}
1749
}
1750
1751
static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller)
1752
{
1753
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1754
return controller.physicalInputProfile.dpads[GCInputDirectionPad];
1755
}
1756
1757
if (controller.extendedGamepad) {
1758
return controller.extendedGamepad.dpad;
1759
}
1760
1761
if (controller.microGamepad) {
1762
return controller.microGamepad.dpad;
1763
}
1764
1765
return nil;
1766
}
1767
#endif // SDL_JOYSTICK_MFI
1768
1769
const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
1770
{
1771
char elementName[256];
1772
elementName[0] = '\0';
1773
1774
#ifdef SDL_JOYSTICK_MFI
1775
if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
1776
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1777
GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
1778
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
1779
switch (button) {
1780
case SDL_GAMEPAD_BUTTON_SOUTH:
1781
GetAppleSFSymbolsNameForElement(elements[GCInputButtonA], elementName);
1782
break;
1783
case SDL_GAMEPAD_BUTTON_EAST:
1784
GetAppleSFSymbolsNameForElement(elements[GCInputButtonB], elementName);
1785
break;
1786
case SDL_GAMEPAD_BUTTON_WEST:
1787
GetAppleSFSymbolsNameForElement(elements[GCInputButtonX], elementName);
1788
break;
1789
case SDL_GAMEPAD_BUTTON_NORTH:
1790
GetAppleSFSymbolsNameForElement(elements[GCInputButtonY], elementName);
1791
break;
1792
case SDL_GAMEPAD_BUTTON_BACK:
1793
GetAppleSFSymbolsNameForElement(elements[GCInputButtonOptions], elementName);
1794
break;
1795
case SDL_GAMEPAD_BUTTON_GUIDE:
1796
GetAppleSFSymbolsNameForElement(elements[@"Button Home"], elementName);
1797
break;
1798
case SDL_GAMEPAD_BUTTON_START:
1799
GetAppleSFSymbolsNameForElement(elements[GCInputButtonMenu], elementName);
1800
break;
1801
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
1802
GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstickButton], elementName);
1803
break;
1804
case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
1805
GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstickButton], elementName);
1806
break;
1807
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
1808
GetAppleSFSymbolsNameForElement(elements[GCInputLeftShoulder], elementName);
1809
break;
1810
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
1811
GetAppleSFSymbolsNameForElement(elements[GCInputRightShoulder], elementName);
1812
break;
1813
case SDL_GAMEPAD_BUTTON_DPAD_UP:
1814
{
1815
GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1816
if (dpad) {
1817
GetAppleSFSymbolsNameForElement(dpad.up, elementName);
1818
if (SDL_strlen(elementName) == 0) {
1819
SDL_strlcpy(elementName, "dpad.up.fill", sizeof(elementName));
1820
}
1821
}
1822
break;
1823
}
1824
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
1825
{
1826
GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1827
if (dpad) {
1828
GetAppleSFSymbolsNameForElement(dpad.down, elementName);
1829
if (SDL_strlen(elementName) == 0) {
1830
SDL_strlcpy(elementName, "dpad.down.fill", sizeof(elementName));
1831
}
1832
}
1833
break;
1834
}
1835
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
1836
{
1837
GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1838
if (dpad) {
1839
GetAppleSFSymbolsNameForElement(dpad.left, elementName);
1840
if (SDL_strlen(elementName) == 0) {
1841
SDL_strlcpy(elementName, "dpad.left.fill", sizeof(elementName));
1842
}
1843
}
1844
break;
1845
}
1846
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
1847
{
1848
GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
1849
if (dpad) {
1850
GetAppleSFSymbolsNameForElement(dpad.right, elementName);
1851
if (SDL_strlen(elementName) == 0) {
1852
SDL_strlcpy(elementName, "dpad.right.fill", sizeof(elementName));
1853
}
1854
}
1855
break;
1856
}
1857
case SDL_GAMEPAD_BUTTON_MISC1:
1858
GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName);
1859
break;
1860
case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1:
1861
GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleOne], elementName);
1862
break;
1863
case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1:
1864
GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName);
1865
break;
1866
case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2:
1867
GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName);
1868
break;
1869
case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2:
1870
GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleFour], elementName);
1871
break;
1872
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
1873
GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName);
1874
break;
1875
default:
1876
break;
1877
}
1878
}
1879
}
1880
#endif // SDL_JOYSTICK_MFI
1881
1882
return *elementName ? SDL_GetPersistentString(elementName) : NULL;
1883
}
1884
1885
const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)
1886
{
1887
char elementName[256];
1888
elementName[0] = '\0';
1889
1890
#ifdef SDL_JOYSTICK_MFI
1891
if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
1892
if (@available(macOS 10.16, iOS 14.0, tvOS 14.0, *)) {
1893
GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
1894
NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
1895
switch (axis) {
1896
case SDL_GAMEPAD_AXIS_LEFTX:
1897
GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName);
1898
break;
1899
case SDL_GAMEPAD_AXIS_LEFTY:
1900
GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName);
1901
break;
1902
case SDL_GAMEPAD_AXIS_RIGHTX:
1903
GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName);
1904
break;
1905
case SDL_GAMEPAD_AXIS_RIGHTY:
1906
GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName);
1907
break;
1908
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
1909
GetAppleSFSymbolsNameForElement(elements[GCInputLeftTrigger], elementName);
1910
break;
1911
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
1912
GetAppleSFSymbolsNameForElement(elements[GCInputRightTrigger], elementName);
1913
break;
1914
default:
1915
break;
1916
}
1917
}
1918
}
1919
#endif // SDL_JOYSTICK_MFI
1920
1921
return *elementName ? SDL_GetPersistentString(elementName) : NULL;
1922
}
1923
1924
SDL_JoystickDriver SDL_IOS_JoystickDriver = {
1925
IOS_JoystickInit,
1926
IOS_JoystickGetCount,
1927
IOS_JoystickDetect,
1928
IOS_JoystickIsDevicePresent,
1929
IOS_JoystickGetDeviceName,
1930
IOS_JoystickGetDevicePath,
1931
IOS_JoystickGetDeviceSteamVirtualGamepadSlot,
1932
IOS_JoystickGetDevicePlayerIndex,
1933
IOS_JoystickSetDevicePlayerIndex,
1934
IOS_JoystickGetDeviceGUID,
1935
IOS_JoystickGetDeviceInstanceID,
1936
IOS_JoystickOpen,
1937
IOS_JoystickRumble,
1938
IOS_JoystickRumbleTriggers,
1939
IOS_JoystickSetLED,
1940
IOS_JoystickSendEffect,
1941
IOS_JoystickSetSensorsEnabled,
1942
IOS_JoystickUpdate,
1943
IOS_JoystickClose,
1944
IOS_JoystickQuit,
1945
IOS_JoystickGetGamepadMapping
1946
};
1947
1948