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