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