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