Atlas - SDL_mfijoystick.m

Home / ext / SDL / src / joystick / apple Lines: 13 | Size: 76048 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 bool down; 1064 if (button == device->pause_button_index) { 1065 down = (device->pause_button_pressed > 0); 1066 } else { 1067 down = buttons[key].isPressed; 1068 } 1069 SDL_SendJoystickButton(timestamp, joystick, button++, down); 1070 } 1071 } else if (controller.extendedGamepad) { 1072 bool isstack; 1073 GCExtendedGamepad *gamepad = controller.extendedGamepad; 1074 1075 // Axis order matches the XInput Windows mappings. 1076 Sint16 axes[] = { 1077 (Sint16)(gamepad.leftThumbstick.xAxis.value * 32767), 1078 (Sint16)(gamepad.leftThumbstick.yAxis.value * -32767), 1079 (Sint16)((gamepad.leftTrigger.value * 65535) - 32768), 1080 (Sint16)(gamepad.rightThumbstick.xAxis.value * 32767), 1081 (Sint16)(gamepad.rightThumbstick.yAxis.value * -32767), 1082 (Sint16)((gamepad.rightTrigger.value * 65535) - 32768), 1083 }; 1084 1085 // Button order matches the XInput Windows mappings. 1086 bool *buttons = SDL_small_alloc(bool, joystick->nbuttons, &isstack); 1087 int button_count = 0; 1088 1089 if (buttons == NULL) { 1090 return; 1091 } 1092 1093 // These buttons are part of the original MFi spec 1094 buttons[button_count++] = gamepad.buttonA.isPressed; 1095 buttons[button_count++] = gamepad.buttonB.isPressed; 1096 buttons[button_count++] = gamepad.buttonX.isPressed; 1097 buttons[button_count++] = gamepad.buttonY.isPressed; 1098 buttons[button_count++] = gamepad.leftShoulder.isPressed; 1099 buttons[button_count++] = gamepad.rightShoulder.isPressed; 1100 1101 // These buttons are available on some newer controllers 1102 if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) { 1103 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) { 1104 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed; 1105 } 1106 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) { 1107 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed; 1108 } 1109 } 1110 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { 1111 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) { 1112 buttons[button_count++] = gamepad.buttonOptions.isPressed; 1113 } 1114 } 1115 if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) { 1116 if (device->pause_button_index >= 0) { 1117 // Guaranteed if buttonMenu is not supported on this OS 1118 buttons[button_count++] = (device->pause_button_pressed > 0); 1119 } else { 1120 if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) { 1121 buttons[button_count++] = gamepad.buttonMenu.isPressed; 1122 } 1123 } 1124 } 1125 1126 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 1127 1128 for (i = 0; i < SDL_arraysize(axes); i++) { 1129 SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]); 1130 } 1131 1132 for (i = 0; i < button_count; i++) { 1133 SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]); 1134 } 1135 1136 SDL_small_free(buttons, isstack); 1137 } 1138#ifdef SDL_PLATFORM_TVOS 1139 else if (controller.microGamepad) { 1140 GCMicroGamepad *gamepad = controller.microGamepad; 1141 1142 Sint16 axes[] = { 1143 (Sint16)(gamepad.dpad.xAxis.value * 32767), 1144 (Sint16)(gamepad.dpad.yAxis.value * -32767), 1145 }; 1146 1147 for (i = 0; i < SDL_arraysize(axes); i++) { 1148 SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]); 1149 } 1150 1151 bool buttons[joystick->nbuttons]; 1152 int button_count = 0; 1153 buttons[button_count++] = gamepad.buttonA.isPressed; 1154 buttons[button_count++] = gamepad.buttonX.isPressed; 1155 buttons[button_count++] = (device->pause_button_pressed > 0); 1156 1157 for (i = 0; i < button_count; i++) { 1158 SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]); 1159 } 1160 } 1161#endif // SDL_PLATFORM_TVOS 1162 1163 if (joystick->nhats > 0) { 1164 SDL_SendJoystickHat(timestamp, joystick, 0, hatstate); 1165 } 1166 1167 if (device->pause_button_pressed) { 1168 // The pause callback is instantaneous, so we extend the duration to allow "holding down" by pressing it repeatedly 1169 const int PAUSE_BUTTON_PRESS_DURATION_MS = 250; 1170 if (SDL_GetTicks() >= device->pause_button_pressed + PAUSE_BUTTON_PRESS_DURATION_MS) { 1171 device->pause_button_pressed = 0; 1172 } 1173 } 1174 1175 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1176 if (device->has_dualshock_touchpad) { 1177 GCControllerDirectionPad *dpad; 1178 1179 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]; 1180 if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) { 1181 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); 1182 } else { 1183 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, false, 0.0f, 0.0f, 1.0f); 1184 } 1185 1186 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]; 1187 if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) { 1188 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); 1189 } else { 1190 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, false, 0.0f, 0.0f, 1.0f); 1191 } 1192 } 1193 } 1194 1195 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1196 GCMotion *motion = controller.motion; 1197 if (motion && motion.sensorsActive) { 1198 float data[3]; 1199 1200 if (motion.hasRotationRate) { 1201 GCRotationRate rate = motion.rotationRate; 1202 data[0] = rate.x; 1203 data[1] = rate.z; 1204 data[2] = -rate.y; 1205 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3); 1206 } 1207 if (motion.hasGravityAndUserAcceleration) { 1208 GCAcceleration accel = motion.acceleration; 1209 data[0] = -accel.x * SDL_STANDARD_GRAVITY; 1210 data[1] = -accel.y * SDL_STANDARD_GRAVITY; 1211 data[2] = -accel.z * SDL_STANDARD_GRAVITY; 1212 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3); 1213 } 1214 } 1215 } 1216 1217 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1218 GCDeviceBattery *battery = controller.battery; 1219 if (battery) { 1220 SDL_PowerState state = SDL_POWERSTATE_UNKNOWN; 1221 int percent = (int)SDL_roundf(battery.batteryLevel * 100.0f); 1222 1223 switch (battery.batteryState) { 1224 case GCDeviceBatteryStateDischarging: 1225 state = SDL_POWERSTATE_ON_BATTERY; 1226 break; 1227 case GCDeviceBatteryStateCharging: 1228 state = SDL_POWERSTATE_CHARGING; 1229 break; 1230 case GCDeviceBatteryStateFull: 1231 state = SDL_POWERSTATE_CHARGED; 1232 break; 1233 default: 1234 break; 1235 } 1236 1237 SDL_SendJoystickPowerInfo(joystick, state, percent); 1238 } 1239 } 1240 } 1241#endif // SDL_JOYSTICK_MFI 1242} 1243 1244#ifdef SDL_JOYSTICK_MFI 1245@interface SDL3_RumbleMotor : NSObject 1246@property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); 1247@property(nonatomic, strong) id<CHHapticPatternPlayer> player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); 1248@property bool active; 1249@end 1250 1251@implementation SDL3_RumbleMotor 1252{ 1253} 1254 1255- (void)cleanup 1256{ 1257 @autoreleasepool { 1258 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1259 if (self.player != nil) { 1260 [self.player cancelAndReturnError:nil]; 1261 self.player = nil; 1262 } 1263 if (self.engine != nil) { 1264 [self.engine stopWithCompletionHandler:nil]; 1265 self.engine = nil; 1266 } 1267 } 1268 } 1269} 1270 1271- (bool)setIntensity:(float)intensity 1272{ 1273 @autoreleasepool { 1274 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1275 NSError *error = nil; 1276 CHHapticDynamicParameter *param; 1277 1278 if (self.engine == nil) { 1279 return SDL_SetError("Haptics engine was stopped"); 1280 } 1281 1282 if (intensity == 0.0f) { 1283 if (self.player && self.active) { 1284 [self.player stopAtTime:0 error:&error]; 1285 } 1286 self.active = false; 1287 return true; 1288 } 1289 1290 if (self.player == nil) { 1291 CHHapticEventParameter *event_param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f]; 1292 CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:event_param, nil] relativeTime:0 duration:GCHapticDurationInfinite]; 1293 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error]; 1294 if (error != nil) { 1295 return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]); 1296 } 1297 1298 self.player = [self.engine createPlayerWithPattern:pattern error:&error]; 1299 if (error != nil) { 1300 return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]); 1301 } 1302 self.active = false; 1303 } 1304 1305 param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0]; 1306 [self.player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error]; 1307 if (error != nil) { 1308 return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]); 1309 } 1310 1311 if (!self.active) { 1312 [self.player startAtTime:0 error:&error]; 1313 self.active = true; 1314 } 1315 } 1316 1317 return true; 1318 } 1319} 1320 1321- (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) 1322{ 1323 @autoreleasepool { 1324 NSError *error; 1325 __weak __typeof(self) weakSelf; 1326 self = [super init]; 1327 weakSelf = self; 1328 1329 self.engine = [controller.haptics createEngineWithLocality:locality]; 1330 if (self.engine == nil) { 1331 SDL_SetError("Couldn't create haptics engine"); 1332 return nil; 1333 } 1334 1335 [self.engine startAndReturnError:&error]; 1336 if (error != nil) { 1337 SDL_SetError("Couldn't start haptics engine"); 1338 return nil; 1339 } 1340 1341 self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) { 1342 SDL3_RumbleMotor *_this = weakSelf; 1343 if (_this == nil) { 1344 return; 1345 } 1346 1347 _this.player = nil; 1348 _this.engine = nil; 1349 }; 1350 self.engine.resetHandler = ^{ 1351 SDL3_RumbleMotor *_this = weakSelf; 1352 if (_this == nil) { 1353 return; 1354 } 1355 1356 _this.player = nil; 1357 [_this.engine startAndReturnError:nil]; 1358 }; 1359 1360 return self; 1361 } 1362} 1363 1364@end 1365 1366@interface SDL3_RumbleContext : NSObject 1367@property(nonatomic, strong) SDL3_RumbleMotor *lowFrequencyMotor; 1368@property(nonatomic, strong) SDL3_RumbleMotor *highFrequencyMotor; 1369@property(nonatomic, strong) SDL3_RumbleMotor *leftTriggerMotor; 1370@property(nonatomic, strong) SDL3_RumbleMotor *rightTriggerMotor; 1371@end 1372 1373@implementation SDL3_RumbleContext 1374{ 1375} 1376 1377- (id)initWithLowFrequencyMotor:(SDL3_RumbleMotor *)low_frequency_motor 1378 HighFrequencyMotor:(SDL3_RumbleMotor *)high_frequency_motor 1379 LeftTriggerMotor:(SDL3_RumbleMotor *)left_trigger_motor 1380 RightTriggerMotor:(SDL3_RumbleMotor *)right_trigger_motor 1381{ 1382 self = [super init]; 1383 self.lowFrequencyMotor = low_frequency_motor; 1384 self.highFrequencyMotor = high_frequency_motor; 1385 self.leftTriggerMotor = left_trigger_motor; 1386 self.rightTriggerMotor = right_trigger_motor; 1387 return self; 1388} 1389 1390- (bool)rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble 1391{ 1392 bool result = true; 1393 1394 result &= [self.lowFrequencyMotor setIntensity:((float)low_frequency_rumble / 65535.0f)]; 1395 result &= [self.highFrequencyMotor setIntensity:((float)high_frequency_rumble / 65535.0f)]; 1396 return result; 1397} 1398 1399- (bool)rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble 1400{ 1401 bool result = false; 1402 1403 if (self.leftTriggerMotor && self.rightTriggerMotor) { 1404 result &= [self.leftTriggerMotor setIntensity:((float)left_rumble / 65535.0f)]; 1405 result &= [self.rightTriggerMotor setIntensity:((float)right_rumble / 65535.0f)]; 1406 } else { 1407 result = SDL_Unsupported(); 1408 } 1409 return result; 1410} 1411 1412- (void)cleanup 1413{ 1414 [self.lowFrequencyMotor cleanup]; 1415 [self.highFrequencyMotor cleanup]; 1416} 1417 1418@end 1419 1420static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller) 1421{ 1422 @autoreleasepool { 1423 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1424 SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle]; 1425 SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle]; 1426 SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger]; 1427 SDL3_RumbleMotor *right_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger]; 1428 if (low_frequency_motor && high_frequency_motor) { 1429 return [[SDL3_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor 1430 HighFrequencyMotor:high_frequency_motor 1431 LeftTriggerMotor:left_trigger_motor 1432 RightTriggerMotor:right_trigger_motor]; 1433 } 1434 } 1435 } 1436 return nil; 1437} 1438 1439#endif // SDL_JOYSTICK_MFI 1440 1441static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 1442{ 1443#ifdef SDL_JOYSTICK_MFI 1444 SDL_JoystickDeviceItem *device = joystick->hwdata; 1445 1446 if (device == NULL) { 1447 return SDL_SetError("Controller is no longer connected"); 1448 } 1449 1450 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1451 if (!device->rumble && device->controller && device->controller.haptics) { 1452 SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); 1453 if (rumble) { 1454 device->rumble = (void *)CFBridgingRetain(rumble); 1455 } 1456 } 1457 } 1458 1459 if (device->rumble) { 1460 SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble; 1461 return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble]; 1462 } 1463#endif 1464 return SDL_Unsupported(); 1465} 1466 1467static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 1468{ 1469#ifdef SDL_JOYSTICK_MFI 1470 SDL_JoystickDeviceItem *device = joystick->hwdata; 1471 1472 if (device == NULL) { 1473 return SDL_SetError("Controller is no longer connected"); 1474 } 1475 1476 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1477 if (!device->rumble && device->controller && device->controller.haptics) { 1478 SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); 1479 if (rumble) { 1480 device->rumble = (void *)CFBridgingRetain(rumble); 1481 } 1482 } 1483 } 1484 1485 if (device->rumble) { 1486 SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble; 1487 return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble]; 1488 } 1489#endif 1490 return SDL_Unsupported(); 1491} 1492 1493static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 1494{ 1495 @autoreleasepool { 1496 SDL_JoystickDeviceItem *device = joystick->hwdata; 1497 1498 if (device == NULL) { 1499 return SDL_SetError("Controller is no longer connected"); 1500 } 1501 1502 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1503 GCController *controller = device->controller; 1504 GCDeviceLight *light = controller.light; 1505 if (light) { 1506 light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f 1507 green:(float)green / 255.0f 1508 blue:(float)blue / 255.0f]; 1509 return true; 1510 } 1511 } 1512 } 1513 return SDL_Unsupported(); 1514} 1515 1516static bool IOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size) 1517{ 1518 return SDL_Unsupported(); 1519} 1520 1521static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled) 1522{ 1523 @autoreleasepool { 1524 SDL_JoystickDeviceItem *device = joystick->hwdata; 1525 1526 if (device == NULL) { 1527 return SDL_SetError("Controller is no longer connected"); 1528 } 1529 1530 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1531 GCController *controller = device->controller; 1532 GCMotion *motion = controller.motion; 1533 if (motion) { 1534 motion.sensorsActive = enabled ? YES : NO; 1535 return true; 1536 } 1537 } 1538 } 1539 1540 return SDL_Unsupported(); 1541} 1542 1543static void IOS_JoystickUpdate(SDL_Joystick *joystick) 1544{ 1545 SDL_JoystickDeviceItem *device = joystick->hwdata; 1546 1547 if (device == NULL) { 1548 return; 1549 } 1550 1551 if (device->controller) { 1552 IOS_MFIJoystickUpdate(joystick); 1553 } 1554} 1555 1556static void IOS_JoystickClose(SDL_Joystick *joystick) 1557{ 1558 SDL_JoystickDeviceItem *device = joystick->hwdata; 1559 1560 if (device == NULL) { 1561 return; 1562 } 1563 1564 device->joystick = NULL; 1565 1566#ifdef SDL_JOYSTICK_MFI 1567 @autoreleasepool { 1568 if (device->rumble) { 1569 SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble; 1570 1571 [rumble cleanup]; 1572 CFRelease(device->rumble); 1573 device->rumble = NULL; 1574 } 1575 1576 if (device->controller) { 1577 GCController *controller = device->controller; 1578#pragma clang diagnostic push 1579#pragma clang diagnostic ignored "-Wdeprecated-declarations" 1580 controller.controllerPausedHandler = nil; 1581#pragma clang diagnostic pop 1582 controller.playerIndex = -1; 1583 1584 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1585 for (id key in controller.physicalInputProfile.buttons) { 1586 GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key]; 1587 if ([button isBoundToSystemGesture]) { 1588 button.preferredSystemGestureState = GCSystemGestureStateEnabled; 1589 } 1590 } 1591 } 1592 } 1593 } 1594#endif // SDL_JOYSTICK_MFI 1595 1596 if (device->is_siri_remote) { 1597 --SDL_AppleTVRemoteOpenedAsJoystick; 1598 } 1599} 1600 1601static void IOS_JoystickQuit(void) 1602{ 1603 @autoreleasepool { 1604#ifdef SDL_JOYSTICK_MFI 1605 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 1606 1607 if (connectObserver) { 1608 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil]; 1609 connectObserver = nil; 1610 } 1611 1612 if (disconnectObserver) { 1613 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil]; 1614 disconnectObserver = nil; 1615 } 1616 1617#ifdef SDL_PLATFORM_TVOS 1618 SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 1619 SDL_AppleTVRemoteRotationHintChanged, NULL); 1620#endif // SDL_PLATFORM_TVOS 1621#endif // SDL_JOYSTICK_MFI 1622 1623 while (deviceList != NULL) { 1624 IOS_RemoveJoystickDevice(deviceList); 1625 } 1626 1627#ifdef SDL_VIDEO_DRIVER_UIKIT 1628 UIKit_SetGameControllerInteraction(false); 1629#endif 1630 } 1631 1632 numjoysticks = 0; 1633} 1634 1635static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) 1636{ 1637 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 1638 if (device == NULL) { 1639 return false; 1640 } 1641 1642 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1643 int axis = 0; 1644 for (id key in device->axes) { 1645 if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] || 1646 [(NSString *)key isEqualToString:@"Direction Pad X Axis"]) { 1647 out->leftx.kind = EMappingKind_Axis; 1648 out->leftx.target = axis; 1649 } else if ([(NSString *)key isEqualToString:@"Left Thumbstick Y Axis"] || 1650 [(NSString *)key isEqualToString:@"Direction Pad Y Axis"]) { 1651 out->lefty.kind = EMappingKind_Axis; 1652 out->lefty.target = axis; 1653 out->lefty.axis_reversed = true; 1654 } else if ([(NSString *)key isEqualToString:@"Right Thumbstick X Axis"]) { 1655 out->rightx.kind = EMappingKind_Axis; 1656 out->rightx.target = axis; 1657 } else if ([(NSString *)key isEqualToString:@"Right Thumbstick Y Axis"]) { 1658 out->righty.kind = EMappingKind_Axis; 1659 out->righty.target = axis; 1660 out->righty.axis_reversed = true; 1661 } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) { 1662 out->lefttrigger.kind = EMappingKind_Axis; 1663 out->lefttrigger.target = axis; 1664 out->lefttrigger.half_axis_positive = true; 1665 } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) { 1666 out->righttrigger.kind = EMappingKind_Axis; 1667 out->righttrigger.target = axis; 1668 out->righttrigger.half_axis_positive = true; 1669 } 1670 ++axis; 1671 } 1672 1673 int button = 0; 1674 for (id key in device->buttons) { 1675 SDL_InputMapping *mapping = NULL; 1676 1677 if ([(NSString *)key isEqualToString:GCInputButtonA]) { 1678 if (device->is_siri_remote > 1) { 1679 // GCInputButtonA is triggered for any D-Pad press, ignore it in favor of "Button Center" 1680 } else if (device->has_nintendo_buttons) { 1681 mapping = &out->b; 1682 } else { 1683 mapping = &out->a; 1684 } 1685 } else if ([(NSString *)key isEqualToString:GCInputButtonB]) { 1686 if (device->has_nintendo_buttons) { 1687 mapping = &out->a; 1688 } else if (device->is_switch_joyconL || device->is_switch_joyconR) { 1689 mapping = &out->x; 1690 } else { 1691 mapping = &out->b; 1692 } 1693 } else if ([(NSString *)key isEqualToString:GCInputButtonX]) { 1694 if (device->has_nintendo_buttons) { 1695 mapping = &out->y; 1696 } else if (device->is_switch_joyconL || device->is_switch_joyconR) { 1697 mapping = &out->b; 1698 } else { 1699 mapping = &out->x; 1700 } 1701 } else if ([(NSString *)key isEqualToString:GCInputButtonY]) { 1702 if (device->has_nintendo_buttons) { 1703 mapping = &out->x; 1704 } else { 1705 mapping = &out->y; 1706 } 1707 } else if ([(NSString *)key isEqualToString:@"Direction Pad Left"]) { 1708 mapping = &out->dpleft; 1709 } else if ([(NSString *)key isEqualToString:@"Direction Pad Right"]) { 1710 mapping = &out->dpright; 1711 } else if ([(NSString *)key isEqualToString:@"Direction Pad Up"]) { 1712 mapping = &out->dpup; 1713 } else if ([(NSString *)key isEqualToString:@"Direction Pad Down"]) { 1714 mapping = &out->dpdown; 1715 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Left"]) { 1716 mapping = &out->dpleft; 1717 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Right"]) { 1718 mapping = &out->dpright; 1719 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Up"]) { 1720 mapping = &out->dpup; 1721 } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Down"]) { 1722 mapping = &out->dpdown; 1723 } else if ([(NSString *)key isEqualToString:GCInputLeftShoulder]) { 1724 mapping = &out->leftshoulder; 1725 } else if ([(NSString *)key isEqualToString:GCInputRightShoulder]) { 1726 mapping = &out->rightshoulder; 1727 } else if ([(NSString *)key isEqualToString:GCInputLeftThumbstickButton]) { 1728 mapping = &out->leftstick; 1729 } else if ([(NSString *)key isEqualToString:GCInputRightThumbstickButton]) { 1730 mapping = &out->rightstick; 1731 } else if ([(NSString *)key isEqualToString:@"Button Home"]) { 1732 mapping = &out->guide; 1733 } else if ([(NSString *)key isEqualToString:GCInputButtonMenu]) { 1734 if (device->is_siri_remote) { 1735 mapping = &out->b; 1736 } else { 1737 mapping = &out->start; 1738 } 1739 } else if ([(NSString *)key isEqualToString:GCInputButtonOptions]) { 1740 mapping = &out->back; 1741 } else if ([(NSString *)key isEqualToString:@"Button Share"]) { 1742 mapping = &out->misc1; 1743 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleOne]) { 1744 mapping = &out->right_paddle1; 1745 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleTwo]) { 1746 mapping = &out->right_paddle2; 1747 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleThree]) { 1748 mapping = &out->left_paddle1; 1749 } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleFour]) { 1750 mapping = &out->left_paddle2; 1751 } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) { 1752 mapping = &out->lefttrigger; 1753 } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) { 1754 mapping = &out->righttrigger; 1755 } else if ([(NSString *)key isEqualToString:GCInputDualShockTouchpadButton]) { 1756 mapping = &out->touchpad; 1757 } else if ([(NSString *)key isEqualToString:@"Button Center"]) { 1758 mapping = &out->a; 1759 } 1760 if (mapping && mapping->kind == EMappingKind_None) { 1761 mapping->kind = EMappingKind_Button; 1762 mapping->target = button; 1763 } 1764 ++button; 1765 } 1766 1767 return true; 1768 } 1769 return false; 1770} 1771 1772#if defined(SDL_JOYSTICK_MFI) && defined(SDL_PLATFORM_MACOS) 1773bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) 1774{ 1775 if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) { 1776 return false; 1777 } 1778 1779 if (@available(macOS 11.0, *)) { 1780 const int MAX_ATTEMPTS = 3; 1781 for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { 1782 if ([GCController supportsHIDDevice:device]) { 1783 return true; 1784 } 1785 1786 // The framework may not have seen the device yet 1787 SDL_Delay(10); 1788 } 1789 } 1790 return false; 1791} 1792#endif 1793 1794#ifdef SDL_JOYSTICK_MFI 1795/* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */ 1796static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name) 1797{ 1798 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1799 if (element) { 1800 [element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding]; 1801 } 1802 } 1803} 1804 1805static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller) 1806{ 1807 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1808 return controller.physicalInputProfile.dpads[GCInputDirectionPad]; 1809 } 1810 1811 if (controller.extendedGamepad) { 1812 return controller.extendedGamepad.dpad; 1813 } 1814 1815 if (controller.microGamepad) { 1816 return controller.microGamepad.dpad; 1817 } 1818 1819 return nil; 1820} 1821#endif // SDL_JOYSTICK_MFI 1822 1823const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button) 1824{ 1825 char elementName[256]; 1826 elementName[0] = '\0'; 1827 1828#ifdef SDL_JOYSTICK_MFI 1829 if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { 1830 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1831 GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; 1832 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements; 1833 switch (button) { 1834 case SDL_GAMEPAD_BUTTON_SOUTH: 1835 GetAppleSFSymbolsNameForElement(elements[GCInputButtonA], elementName); 1836 break; 1837 case SDL_GAMEPAD_BUTTON_EAST: 1838 GetAppleSFSymbolsNameForElement(elements[GCInputButtonB], elementName); 1839 break; 1840 case SDL_GAMEPAD_BUTTON_WEST: 1841 GetAppleSFSymbolsNameForElement(elements[GCInputButtonX], elementName); 1842 break; 1843 case SDL_GAMEPAD_BUTTON_NORTH: 1844 GetAppleSFSymbolsNameForElement(elements[GCInputButtonY], elementName); 1845 break; 1846 case SDL_GAMEPAD_BUTTON_BACK: 1847 GetAppleSFSymbolsNameForElement(elements[GCInputButtonOptions], elementName); 1848 break; 1849 case SDL_GAMEPAD_BUTTON_GUIDE: 1850 GetAppleSFSymbolsNameForElement(elements[@"Button Home"], elementName); 1851 break; 1852 case SDL_GAMEPAD_BUTTON_START: 1853 GetAppleSFSymbolsNameForElement(elements[GCInputButtonMenu], elementName); 1854 break; 1855 case SDL_GAMEPAD_BUTTON_LEFT_STICK: 1856 GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstickButton], elementName); 1857 break; 1858 case SDL_GAMEPAD_BUTTON_RIGHT_STICK: 1859 GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstickButton], elementName); 1860 break; 1861 case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: 1862 GetAppleSFSymbolsNameForElement(elements[GCInputLeftShoulder], elementName); 1863 break; 1864 case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: 1865 GetAppleSFSymbolsNameForElement(elements[GCInputRightShoulder], elementName); 1866 break; 1867 case SDL_GAMEPAD_BUTTON_DPAD_UP: 1868 { 1869 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); 1870 if (dpad) { 1871 GetAppleSFSymbolsNameForElement(dpad.up, elementName); 1872 if (SDL_strlen(elementName) == 0) { 1873 SDL_strlcpy(elementName, "dpad.up.fill", sizeof(elementName)); 1874 } 1875 } 1876 break; 1877 } 1878 case SDL_GAMEPAD_BUTTON_DPAD_DOWN: 1879 { 1880 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); 1881 if (dpad) { 1882 GetAppleSFSymbolsNameForElement(dpad.down, elementName); 1883 if (SDL_strlen(elementName) == 0) { 1884 SDL_strlcpy(elementName, "dpad.down.fill", sizeof(elementName)); 1885 } 1886 } 1887 break; 1888 } 1889 case SDL_GAMEPAD_BUTTON_DPAD_LEFT: 1890 { 1891 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); 1892 if (dpad) { 1893 GetAppleSFSymbolsNameForElement(dpad.left, elementName); 1894 if (SDL_strlen(elementName) == 0) { 1895 SDL_strlcpy(elementName, "dpad.left.fill", sizeof(elementName)); 1896 } 1897 } 1898 break; 1899 } 1900 case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: 1901 { 1902 GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller); 1903 if (dpad) { 1904 GetAppleSFSymbolsNameForElement(dpad.right, elementName); 1905 if (SDL_strlen(elementName) == 0) { 1906 SDL_strlcpy(elementName, "dpad.right.fill", sizeof(elementName)); 1907 } 1908 } 1909 break; 1910 } 1911 case SDL_GAMEPAD_BUTTON_MISC1: 1912 GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName); 1913 break; 1914 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: 1915 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleOne], elementName); 1916 break; 1917 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: 1918 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName); 1919 break; 1920 case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: 1921 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName); 1922 break; 1923 case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: 1924 GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleFour], elementName); 1925 break; 1926 case SDL_GAMEPAD_BUTTON_TOUCHPAD: 1927 GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName); 1928 break; 1929 default: 1930 break; 1931 } 1932 } 1933 } 1934#endif // SDL_JOYSTICK_MFI 1935 1936 return *elementName ? SDL_GetPersistentString(elementName) : NULL; 1937} 1938 1939const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis) 1940{ 1941 char elementName[256]; 1942 elementName[0] = '\0'; 1943 1944#ifdef SDL_JOYSTICK_MFI 1945 if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) { 1946 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1947 GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller; 1948 NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements; 1949 switch (axis) { 1950 case SDL_GAMEPAD_AXIS_LEFTX: 1951 GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName); 1952 break; 1953 case SDL_GAMEPAD_AXIS_LEFTY: 1954 GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName); 1955 break; 1956 case SDL_GAMEPAD_AXIS_RIGHTX: 1957 GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName); 1958 break; 1959 case SDL_GAMEPAD_AXIS_RIGHTY: 1960 GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName); 1961 break; 1962 case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: 1963 GetAppleSFSymbolsNameForElement(elements[GCInputLeftTrigger], elementName); 1964 break; 1965 case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: 1966 GetAppleSFSymbolsNameForElement(elements[GCInputRightTrigger], elementName); 1967 break; 1968 default: 1969 break; 1970 } 1971 } 1972 } 1973#endif // SDL_JOYSTICK_MFI 1974 1975 return *elementName ? SDL_GetPersistentString(elementName) : NULL; 1976} 1977 1978SDL_JoystickDriver SDL_IOS_JoystickDriver = { 1979 IOS_JoystickInit, 1980 IOS_JoystickGetCount, 1981 IOS_JoystickDetect, 1982 IOS_JoystickIsDevicePresent, 1983 IOS_JoystickGetDeviceName, 1984 IOS_JoystickGetDevicePath, 1985 IOS_JoystickGetDeviceSteamVirtualGamepadSlot, 1986 IOS_JoystickGetDevicePlayerIndex, 1987 IOS_JoystickSetDevicePlayerIndex, 1988 IOS_JoystickGetDeviceGUID, 1989 IOS_JoystickGetDeviceInstanceID, 1990 IOS_JoystickOpen, 1991 IOS_JoystickRumble, 1992 IOS_JoystickRumbleTriggers, 1993 IOS_JoystickSetLED, 1994 IOS_JoystickSendEffect, 1995 IOS_JoystickSetSensorsEnabled, 1996 IOS_JoystickUpdate, 1997 IOS_JoystickClose, 1998 IOS_JoystickQuit, 1999 IOS_JoystickGetGamepadMapping 2000}; 2001
[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.