SDL_mfijoystick.m (49703B)
1 /* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> 4 5 This software is provided 'as-is', without any express or implied 6 warranty. In no event will the authors be held liable for any damages 7 arising from the use of this software. 8 9 Permission is granted to anyone to use this software for any purpose, 10 including commercial applications, and to alter it and redistribute it 11 freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not 14 claim that you wrote the original software. If you use this software 15 in a product, an acknowledgment in the product documentation would be 16 appreciated but is not required. 17 2. Altered source versions must be plainly marked as such, and must not be 18 misrepresented as being the original software. 19 3. This notice may not be removed or altered from any source distribution. 20 */ 21 #include "../../SDL_internal.h" 22 23 /* This is the iOS implementation of the SDL joystick API */ 24 #include "SDL_events.h" 25 #include "SDL_joystick.h" 26 #include "SDL_hints.h" 27 #include "SDL_stdinc.h" 28 #include "../SDL_sysjoystick.h" 29 #include "../SDL_joystick_c.h" 30 #include "../usb_ids.h" 31 32 #include "SDL_mfijoystick_c.h" 33 34 #if !SDL_EVENTS_DISABLED 35 #include "../../events/SDL_events_c.h" 36 #endif 37 38 #if TARGET_OS_IOS 39 #define SDL_JOYSTICK_iOS_ACCELEROMETER 40 #import <CoreMotion/CoreMotion.h> 41 #endif 42 43 #if defined(__MACOSX__) 44 #include <IOKit/hid/IOHIDManager.h> 45 #include <AppKit/NSApplication.h> 46 #ifndef NSAppKitVersionNumber10_15 47 #define NSAppKitVersionNumber10_15 1894 48 #endif 49 #endif /* __MACOSX__ */ 50 51 #ifdef SDL_JOYSTICK_MFI 52 #import <GameController/GameController.h> 53 54 static id connectObserver = nil; 55 static id disconnectObserver = nil; 56 57 #include <Availability.h> 58 #include <objc/message.h> 59 60 /* remove compilation warnings for strict builds by defining these selectors, even though 61 * they are only ever used indirectly through objc_msgSend 62 */ 63 @interface GCController (SDL) 64 #if defined(__MACOSX__) && (__MAC_OS_X_VERSION_MAX_ALLOWED <= 101600) 65 + (BOOL)supportsHIDDevice:(IOHIDDeviceRef)device; 66 #endif 67 @end 68 @interface GCExtendedGamepad (SDL) 69 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 121000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 121000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1401000)) 70 @property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton; 71 @property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton; 72 #endif 73 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 130000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1500000)) 74 @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu; 75 @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions; 76 #endif 77 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000)) 78 @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonHome; 79 #endif 80 @end 81 @interface GCMicroGamepad (SDL) 82 #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 130000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1500000)) 83 @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu; 84 #endif 85 @end 86 87 #if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000) || (__MAC_OS_X_VERSION_MAX_ALLOWED > 101600) 88 #define ENABLE_MFI_BATTERY 89 #define ENABLE_MFI_RUMBLE 90 #define ENABLE_MFI_LIGHT 91 #define ENABLE_MFI_SENSORS 92 #define ENABLE_PHYSICAL_INPUT_PROFILE 93 #endif 94 95 #ifdef ENABLE_MFI_RUMBLE 96 #import <CoreHaptics/CoreHaptics.h> 97 #endif 98 99 #endif /* SDL_JOYSTICK_MFI */ 100 101 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 102 static const char *accelerometerName = "iOS Accelerometer"; 103 static CMMotionManager *motionManager = nil; 104 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER */ 105 106 static SDL_JoystickDeviceItem *deviceList = NULL; 107 108 static int numjoysticks = 0; 109 int SDL_AppleTVRemoteOpenedAsJoystick = 0; 110 111 static SDL_JoystickDeviceItem * 112 GetDeviceForIndex(int device_index) 113 { 114 SDL_JoystickDeviceItem *device = deviceList; 115 int i = 0; 116 117 while (i < device_index) { 118 if (device == NULL) { 119 return NULL; 120 } 121 device = device->next; 122 i++; 123 } 124 125 return device; 126 } 127 128 #ifdef SDL_JOYSTICK_MFI 129 static void 130 IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller) 131 { 132 Uint16 *guid16 = (Uint16 *)device->guid.data; 133 Uint16 vendor = 0; 134 Uint16 product = 0; 135 Uint8 subtype = 0; 136 137 const char *name = NULL; 138 /* Explicitly retain the controller because SDL_JoystickDeviceItem is a 139 * struct, and ARC doesn't work with structs. */ 140 device->controller = (__bridge GCController *) CFBridgingRetain(controller); 141 142 if (controller.vendorName) { 143 name = controller.vendorName.UTF8String; 144 } 145 146 if (!name) { 147 name = "MFi Gamepad"; 148 } 149 150 device->name = SDL_CreateJoystickName(0, 0, NULL, name); 151 152 if (controller.extendedGamepad) { 153 GCExtendedGamepad *gamepad = controller.extendedGamepad; 154 BOOL is_xbox = [controller.vendorName containsString: @"Xbox"]; 155 BOOL is_ps4 = [controller.vendorName containsString: @"DUALSHOCK"]; 156 #if TARGET_OS_TV 157 BOOL is_MFi = (!is_xbox && !is_ps4); 158 #endif 159 int nbuttons = 0; 160 161 /* These buttons are part of the original MFi spec */ 162 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A); 163 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); 164 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X); 165 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y); 166 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER); 167 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); 168 nbuttons += 6; 169 170 /* These buttons are available on some newer controllers */ 171 #pragma clang diagnostic push 172 #pragma clang diagnostic ignored "-Wunguarded-availability-new" 173 if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) { 174 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK); 175 ++nbuttons; 176 } 177 if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) { 178 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK); 179 ++nbuttons; 180 } 181 if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) { 182 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK); 183 ++nbuttons; 184 } 185 if ([gamepad respondsToSelector:@selector(buttonHome)] && gamepad.buttonHome) { 186 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_GUIDE); 187 ++nbuttons; 188 } 189 BOOL has_direct_menu = [gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu; 190 #if TARGET_OS_TV 191 /* On tvOS MFi controller menu button brings you to the home screen */ 192 if (is_MFi) { 193 has_direct_menu = FALSE; 194 } 195 #endif 196 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START); 197 ++nbuttons; 198 if (!has_direct_menu) { 199 device->uses_pause_handler = SDL_TRUE; 200 } 201 202 #ifdef ENABLE_PHYSICAL_INPUT_PROFILE 203 if ([controller respondsToSelector:@selector(physicalInputProfile)]) { 204 if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) { 205 device->has_dualshock_touchpad = SDL_TRUE; 206 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_MISC1); 207 ++nbuttons; 208 } 209 if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) { 210 device->has_xbox_paddles = SDL_TRUE; 211 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE1); 212 ++nbuttons; 213 } 214 if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) { 215 device->has_xbox_paddles = SDL_TRUE; 216 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE2); 217 ++nbuttons; 218 } 219 if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) { 220 device->has_xbox_paddles = SDL_TRUE; 221 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE3); 222 ++nbuttons; 223 } 224 if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) { 225 device->has_xbox_paddles = SDL_TRUE; 226 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE4); 227 ++nbuttons; 228 } 229 } 230 #endif 231 #pragma clang diagnostic pop 232 233 if (is_xbox) { 234 vendor = USB_VENDOR_MICROSOFT; 235 if (device->has_xbox_paddles) { 236 /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */ 237 product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH; 238 subtype = 1; 239 } else { 240 /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */ 241 product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH; 242 subtype = 0; 243 } 244 } else if (is_ps4) { 245 /* Assume DS4 Slim unless/until GCController flows VID/PID */ 246 vendor = USB_VENDOR_SONY; 247 product = USB_PRODUCT_SONY_DS4_SLIM; 248 if (device->has_dualshock_touchpad) { 249 subtype = 1; 250 } else { 251 subtype = 0; 252 } 253 } else { 254 vendor = USB_VENDOR_APPLE; 255 product = 1; 256 subtype = 1; 257 } 258 259 device->naxes = 6; /* 2 thumbsticks and 2 triggers */ 260 device->nhats = 1; /* d-pad */ 261 device->nbuttons = nbuttons; 262 263 } else if (controller.gamepad) { 264 int nbuttons = 0; 265 266 /* These buttons are part of the original MFi spec */ 267 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A); 268 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); 269 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X); 270 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y); 271 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER); 272 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); 273 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START); 274 nbuttons += 7; 275 device->uses_pause_handler = SDL_TRUE; 276 277 vendor = USB_VENDOR_APPLE; 278 product = 2; 279 subtype = 2; 280 device->naxes = 0; /* no traditional analog inputs */ 281 device->nhats = 1; /* d-pad */ 282 device->nbuttons = nbuttons; 283 } 284 #if TARGET_OS_TV 285 else if (controller.microGamepad) { 286 int nbuttons = 0; 287 288 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A); 289 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */ 290 nbuttons += 2; 291 292 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START); 293 ++nbuttons; 294 device->uses_pause_handler = SDL_TRUE; 295 296 vendor = USB_VENDOR_APPLE; 297 product = 3; 298 subtype = 3; 299 device->naxes = 2; /* treat the touch surface as two axes */ 300 device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */ 301 device->nbuttons = nbuttons; 302 303 controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE); 304 } 305 #endif /* TARGET_OS_TV */ 306 307 /* We only need 16 bits for each of these; space them out to fill 128. */ 308 /* Byteswap so devices get same GUID on little/big endian platforms. */ 309 *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH); 310 *guid16++ = 0; 311 *guid16++ = SDL_SwapLE16(vendor); 312 *guid16++ = 0; 313 *guid16++ = SDL_SwapLE16(product); 314 *guid16++ = 0; 315 316 *guid16++ = SDL_SwapLE16(device->button_mask); 317 318 if (vendor == USB_VENDOR_APPLE) { 319 /* Note that this is an MFI controller and what subtype it is */ 320 device->guid.data[14] = 'm'; 321 device->guid.data[15] = subtype; 322 } else { 323 device->guid.data[15] = subtype; 324 } 325 326 /* This will be set when the first button press of the controller is 327 * detected. */ 328 controller.playerIndex = -1; 329 } 330 #endif /* SDL_JOYSTICK_MFI */ 331 332 #if defined(SDL_JOYSTICK_iOS_ACCELEROMETER) || defined(SDL_JOYSTICK_MFI) 333 static void 334 IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer) 335 { 336 SDL_JoystickDeviceItem *device = deviceList; 337 338 #if TARGET_OS_TV 339 if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) { 340 /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */ 341 if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) { 342 return; 343 } 344 } 345 #endif 346 347 while (device != NULL) { 348 if (device->controller == controller) { 349 return; 350 } 351 device = device->next; 352 } 353 354 device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem)); 355 if (device == NULL) { 356 return; 357 } 358 359 device->accelerometer = accelerometer; 360 device->instance_id = SDL_GetNextJoystickInstanceID(); 361 362 if (accelerometer) { 363 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 364 device->name = SDL_strdup(accelerometerName); 365 device->naxes = 3; /* Device acceleration in the x, y, and z axes. */ 366 device->nhats = 0; 367 device->nbuttons = 0; 368 369 /* Use the accelerometer name as a GUID. */ 370 SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name))); 371 #else 372 SDL_free(device); 373 return; 374 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER */ 375 } else if (controller) { 376 #ifdef SDL_JOYSTICK_MFI 377 IOS_AddMFIJoystickDevice(device, controller); 378 #else 379 SDL_free(device); 380 return; 381 #endif /* SDL_JOYSTICK_MFI */ 382 } 383 384 if (deviceList == NULL) { 385 deviceList = device; 386 } else { 387 SDL_JoystickDeviceItem *lastdevice = deviceList; 388 while (lastdevice->next != NULL) { 389 lastdevice = lastdevice->next; 390 } 391 lastdevice->next = device; 392 } 393 394 ++numjoysticks; 395 396 SDL_PrivateJoystickAdded(device->instance_id); 397 } 398 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER || SDL_JOYSTICK_MFI */ 399 400 static SDL_JoystickDeviceItem * 401 IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device) 402 { 403 SDL_JoystickDeviceItem *prev = NULL; 404 SDL_JoystickDeviceItem *next = NULL; 405 SDL_JoystickDeviceItem *item = deviceList; 406 407 if (device == NULL) { 408 return NULL; 409 } 410 411 next = device->next; 412 413 while (item != NULL) { 414 if (item == device) { 415 break; 416 } 417 prev = item; 418 item = item->next; 419 } 420 421 /* Unlink the device item from the device list. */ 422 if (prev) { 423 prev->next = device->next; 424 } else if (device == deviceList) { 425 deviceList = device->next; 426 } 427 428 if (device->joystick) { 429 device->joystick->hwdata = NULL; 430 } 431 432 #ifdef SDL_JOYSTICK_MFI 433 @autoreleasepool { 434 if (device->controller) { 435 /* The controller was explicitly retained in the struct, so it 436 * should be explicitly released before freeing the struct. */ 437 GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller)); 438 controller.controllerPausedHandler = nil; 439 device->controller = nil; 440 } 441 } 442 #endif /* SDL_JOYSTICK_MFI */ 443 444 --numjoysticks; 445 446 SDL_PrivateJoystickRemoved(device->instance_id); 447 448 SDL_free(device->name); 449 SDL_free(device); 450 451 return next; 452 } 453 454 #if TARGET_OS_TV 455 static void SDLCALL 456 SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue) 457 { 458 BOOL allowRotation = newValue != NULL && *newValue != '0'; 459 460 @autoreleasepool { 461 for (GCController *controller in [GCController controllers]) { 462 if (controller.microGamepad) { 463 controller.microGamepad.allowsRotation = allowRotation; 464 } 465 } 466 } 467 } 468 #endif /* TARGET_OS_TV */ 469 470 #if defined(__MACOSX__) 471 static int is_macos11(void) 472 { 473 return (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_15); 474 } 475 #endif 476 477 static int 478 IOS_JoystickInit(void) 479 { 480 #if defined(__MACOSX__) 481 if (!is_macos11()) { 482 return 0; 483 } 484 #endif 485 486 @autoreleasepool { 487 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 488 if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) { 489 /* Default behavior, accelerometer as joystick */ 490 IOS_AddJoystickDevice(nil, SDL_TRUE); 491 } 492 #endif 493 494 #ifdef SDL_JOYSTICK_MFI 495 /* GameController.framework was added in iOS 7. */ 496 if (![GCController class]) { 497 return 0; 498 } 499 500 /* For whatever reason, this always returns an empty array on 501 macOS 11.0.1 */ 502 for (GCController *controller in [GCController controllers]) { 503 IOS_AddJoystickDevice(controller, SDL_FALSE); 504 } 505 506 #if TARGET_OS_TV 507 SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 508 SDL_AppleTVRemoteRotationHintChanged, NULL); 509 #endif /* TARGET_OS_TV */ 510 511 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 512 513 connectObserver = [center addObserverForName:GCControllerDidConnectNotification 514 object:nil 515 queue:nil 516 usingBlock:^(NSNotification *note) { 517 GCController *controller = note.object; 518 IOS_AddJoystickDevice(controller, SDL_FALSE); 519 }]; 520 521 disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification 522 object:nil 523 queue:nil 524 usingBlock:^(NSNotification *note) { 525 GCController *controller = note.object; 526 SDL_JoystickDeviceItem *device = deviceList; 527 while (device != NULL) { 528 if (device->controller == controller) { 529 IOS_RemoveJoystickDevice(device); 530 break; 531 } 532 device = device->next; 533 } 534 }]; 535 #endif /* SDL_JOYSTICK_MFI */ 536 } 537 538 return 0; 539 } 540 541 static int 542 IOS_JoystickGetCount(void) 543 { 544 return numjoysticks; 545 } 546 547 static void 548 IOS_JoystickDetect(void) 549 { 550 } 551 552 static const char * 553 IOS_JoystickGetDeviceName(int device_index) 554 { 555 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 556 return device ? device->name : "Unknown"; 557 } 558 559 static int 560 IOS_JoystickGetDevicePlayerIndex(int device_index) 561 { 562 #ifdef SDL_JOYSTICK_MFI 563 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 564 if (device && device->controller) { 565 return (int)device->controller.playerIndex; 566 } 567 #endif 568 return -1; 569 } 570 571 static void 572 IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index) 573 { 574 #ifdef SDL_JOYSTICK_MFI 575 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 576 if (device && device->controller) { 577 device->controller.playerIndex = player_index; 578 } 579 #endif 580 } 581 582 static SDL_JoystickGUID 583 IOS_JoystickGetDeviceGUID( int device_index ) 584 { 585 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 586 SDL_JoystickGUID guid; 587 if (device) { 588 guid = device->guid; 589 } else { 590 SDL_zero(guid); 591 } 592 return guid; 593 } 594 595 static SDL_JoystickID 596 IOS_JoystickGetDeviceInstanceID(int device_index) 597 { 598 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 599 return device ? device->instance_id : -1; 600 } 601 602 static int 603 IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) 604 { 605 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 606 if (device == NULL) { 607 return SDL_SetError("Could not open Joystick: no hardware device for the specified index"); 608 } 609 610 joystick->hwdata = device; 611 joystick->instance_id = device->instance_id; 612 613 joystick->naxes = device->naxes; 614 joystick->nhats = device->nhats; 615 joystick->nbuttons = device->nbuttons; 616 joystick->nballs = 0; 617 618 if (device->has_dualshock_touchpad) { 619 SDL_PrivateJoystickAddTouchpad(joystick, 2); 620 } 621 622 device->joystick = joystick; 623 624 @autoreleasepool { 625 if (device->accelerometer) { 626 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 627 if (motionManager == nil) { 628 motionManager = [[CMMotionManager alloc] init]; 629 } 630 631 /* Shorter times between updates can significantly increase CPU usage. */ 632 motionManager.accelerometerUpdateInterval = 0.1; 633 [motionManager startAccelerometerUpdates]; 634 #endif 635 } else { 636 #ifdef SDL_JOYSTICK_MFI 637 if (device->uses_pause_handler) { 638 GCController *controller = device->controller; 639 controller.controllerPausedHandler = ^(GCController *c) { 640 if (joystick->hwdata) { 641 ++joystick->hwdata->num_pause_presses; 642 } 643 }; 644 } 645 646 #ifdef ENABLE_MFI_SENSORS 647 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 648 GCController *controller = joystick->hwdata->controller; 649 GCMotion *motion = controller.motion; 650 if (motion && motion.hasRotationRate) { 651 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO); 652 } 653 if (motion && motion.hasGravityAndUserAcceleration) { 654 SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL); 655 } 656 } 657 #endif /* ENABLE_MFI_SENSORS */ 658 659 #endif /* SDL_JOYSTICK_MFI */ 660 } 661 } 662 if (device->remote) { 663 ++SDL_AppleTVRemoteOpenedAsJoystick; 664 } 665 666 return 0; 667 } 668 669 static void 670 IOS_AccelerometerUpdate(SDL_Joystick *joystick) 671 { 672 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 673 const float maxgforce = SDL_IPHONE_MAX_GFORCE; 674 const SInt16 maxsint16 = 0x7FFF; 675 CMAcceleration accel; 676 677 @autoreleasepool { 678 if (!motionManager.isAccelerometerActive) { 679 return; 680 } 681 682 accel = motionManager.accelerometerData.acceleration; 683 } 684 685 /* 686 Convert accelerometer data from floating point to Sint16, which is what 687 the joystick system expects. 688 689 To do the conversion, the data is first clamped onto the interval 690 [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied 691 by MAX_SINT16 so that it is mapped to the full range of an Sint16. 692 693 You can customize the clamped range of this function by modifying the 694 SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h. 695 696 Once converted to Sint16, the accelerometer data no longer has coherent 697 units. You can convert the data back to units of g-force by multiplying 698 it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF. 699 */ 700 701 /* clamp the data */ 702 accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce); 703 accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce); 704 accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce); 705 706 /* pass in data mapped to range of SInt16 */ 707 SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16); 708 SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16); 709 SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16); 710 #endif /* SDL_JOYSTICK_iOS_ACCELEROMETER */ 711 } 712 713 #ifdef SDL_JOYSTICK_MFI 714 static Uint8 715 IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) 716 { 717 Uint8 hat = 0; 718 719 if (dpad.up.isPressed) { 720 hat |= SDL_HAT_UP; 721 } else if (dpad.down.isPressed) { 722 hat |= SDL_HAT_DOWN; 723 } 724 725 if (dpad.left.isPressed) { 726 hat |= SDL_HAT_LEFT; 727 } else if (dpad.right.isPressed) { 728 hat |= SDL_HAT_RIGHT; 729 } 730 731 if (hat == 0) { 732 return SDL_HAT_CENTERED; 733 } 734 735 return hat; 736 } 737 #endif 738 739 static void 740 IOS_MFIJoystickUpdate(SDL_Joystick *joystick) 741 { 742 #if SDL_JOYSTICK_MFI 743 @autoreleasepool { 744 GCController *controller = joystick->hwdata->controller; 745 Uint8 hatstate = SDL_HAT_CENTERED; 746 int i; 747 int pause_button_index = 0; 748 749 if (controller.extendedGamepad) { 750 GCExtendedGamepad *gamepad = controller.extendedGamepad; 751 752 /* Axis order matches the XInput Windows mappings. */ 753 Sint16 axes[] = { 754 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767), 755 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767), 756 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768), 757 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767), 758 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767), 759 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768), 760 }; 761 762 /* Button order matches the XInput Windows mappings. */ 763 Uint8 buttons[joystick->nbuttons]; 764 int button_count = 0; 765 766 /* These buttons are part of the original MFi spec */ 767 buttons[button_count++] = gamepad.buttonA.isPressed; 768 buttons[button_count++] = gamepad.buttonB.isPressed; 769 buttons[button_count++] = gamepad.buttonX.isPressed; 770 buttons[button_count++] = gamepad.buttonY.isPressed; 771 buttons[button_count++] = gamepad.leftShoulder.isPressed; 772 buttons[button_count++] = gamepad.rightShoulder.isPressed; 773 774 /* These buttons are available on some newer controllers */ 775 #pragma clang diagnostic push 776 #pragma clang diagnostic ignored "-Wunguarded-availability-new" 777 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) { 778 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed; 779 } 780 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) { 781 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed; 782 } 783 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) { 784 buttons[button_count++] = gamepad.buttonOptions.isPressed; 785 } 786 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_GUIDE)) { 787 buttons[button_count++] = gamepad.buttonHome.isPressed; 788 } 789 /* This must be the last button, so we can optionally handle it with pause_button_index below */ 790 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { 791 if (joystick->hwdata->uses_pause_handler) { 792 pause_button_index = button_count; 793 buttons[button_count++] = joystick->delayed_guide_button; 794 } else { 795 buttons[button_count++] = gamepad.buttonMenu.isPressed; 796 } 797 } 798 799 #ifdef ENABLE_PHYSICAL_INPUT_PROFILE 800 if (joystick->hwdata->has_dualshock_touchpad) { 801 buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed; 802 803 GCControllerDirectionPad *dpad; 804 805 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne]; 806 if (dpad.xAxis.value || dpad.yAxis.value) { 807 SDL_PrivateJoystickTouchpad(joystick, 0, 0, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); 808 } else { 809 SDL_PrivateJoystickTouchpad(joystick, 0, 0, SDL_RELEASED, 0.0f, 0.0f, 1.0f); 810 } 811 812 dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo]; 813 if (dpad.xAxis.value || dpad.yAxis.value) { 814 SDL_PrivateJoystickTouchpad(joystick, 0, 1, SDL_PRESSED, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f); 815 } else { 816 SDL_PrivateJoystickTouchpad(joystick, 0, 1, SDL_RELEASED, 0.0f, 0.0f, 1.0f); 817 } 818 } 819 820 if (joystick->hwdata->has_xbox_paddles) { 821 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE1)) { 822 buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed; 823 } 824 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE2)) { 825 buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed; 826 } 827 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE3)) { 828 buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed; 829 } 830 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE4)) { 831 buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed; 832 } 833 834 /* 835 SDL_Log("Paddles: [%d,%d,%d,%d]", 836 controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed, 837 controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed, 838 controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed, 839 controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed); 840 */ 841 } 842 #endif 843 #pragma clang diagnostic pop 844 845 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 846 847 for (i = 0; i < SDL_arraysize(axes); i++) { 848 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 849 } 850 851 for (i = 0; i < button_count; i++) { 852 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 853 } 854 855 #ifdef ENABLE_MFI_SENSORS 856 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 857 GCMotion *motion = controller.motion; 858 if (motion && motion.sensorsActive) { 859 float data[3]; 860 861 if (motion.hasRotationRate) { 862 GCRotationRate rate = motion.rotationRate; 863 data[0] = rate.x; 864 data[1] = rate.z; 865 data[2] = -rate.y; 866 SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, data, 3); 867 } 868 if (motion.hasGravityAndUserAcceleration) { 869 GCAcceleration accel = motion.acceleration; 870 data[0] = -accel.x * SDL_STANDARD_GRAVITY; 871 data[1] = -accel.y * SDL_STANDARD_GRAVITY; 872 data[2] = -accel.z * SDL_STANDARD_GRAVITY; 873 SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, data, 3); 874 } 875 } 876 } 877 #endif /* ENABLE_MFI_SENSORS */ 878 879 } else if (controller.gamepad) { 880 GCGamepad *gamepad = controller.gamepad; 881 882 /* Button order matches the XInput Windows mappings. */ 883 Uint8 buttons[joystick->nbuttons]; 884 int button_count = 0; 885 buttons[button_count++] = gamepad.buttonA.isPressed; 886 buttons[button_count++] = gamepad.buttonB.isPressed; 887 buttons[button_count++] = gamepad.buttonX.isPressed; 888 buttons[button_count++] = gamepad.buttonY.isPressed; 889 buttons[button_count++] = gamepad.leftShoulder.isPressed; 890 buttons[button_count++] = gamepad.rightShoulder.isPressed; 891 pause_button_index = button_count; 892 buttons[button_count++] = joystick->delayed_guide_button; 893 894 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 895 896 for (i = 0; i < button_count; i++) { 897 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 898 } 899 } 900 #if TARGET_OS_TV 901 else if (controller.microGamepad) { 902 GCMicroGamepad *gamepad = controller.microGamepad; 903 904 Sint16 axes[] = { 905 (Sint16) (gamepad.dpad.xAxis.value * 32767), 906 (Sint16) (gamepad.dpad.yAxis.value * -32767), 907 }; 908 909 for (i = 0; i < SDL_arraysize(axes); i++) { 910 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 911 } 912 913 Uint8 buttons[joystick->nbuttons]; 914 int button_count = 0; 915 buttons[button_count++] = gamepad.buttonA.isPressed; 916 buttons[button_count++] = gamepad.buttonX.isPressed; 917 #pragma clang diagnostic push 918 #pragma clang diagnostic ignored "-Wunguarded-availability-new" 919 /* This must be the last button, so we can optionally handle it with pause_button_index below */ 920 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { 921 if (joystick->hwdata->uses_pause_handler) { 922 pause_button_index = button_count; 923 buttons[button_count++] = joystick->delayed_guide_button; 924 } else { 925 buttons[button_count++] = gamepad.buttonMenu.isPressed; 926 } 927 } 928 #pragma clang diagnostic pop 929 930 for (i = 0; i < button_count; i++) { 931 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 932 } 933 } 934 #endif /* TARGET_OS_TV */ 935 936 if (joystick->nhats > 0) { 937 SDL_PrivateJoystickHat(joystick, 0, hatstate); 938 } 939 940 if (joystick->hwdata->uses_pause_handler) { 941 for (i = 0; i < joystick->hwdata->num_pause_presses; i++) { 942 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED); 943 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED); 944 } 945 joystick->hwdata->num_pause_presses = 0; 946 } 947 948 #ifdef ENABLE_MFI_BATTERY 949 if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { 950 GCDeviceBattery *battery = controller.battery; 951 if (battery) { 952 SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN; 953 954 switch (battery.batteryState) { 955 case GCDeviceBatteryStateDischarging: 956 { 957 float power_level = battery.batteryLevel; 958 if (power_level <= 0.05f) { 959 ePowerLevel = SDL_JOYSTICK_POWER_EMPTY; 960 } else if (power_level <= 0.20f) { 961 ePowerLevel = SDL_JOYSTICK_POWER_LOW; 962 } else if (power_level <= 0.70f) { 963 ePowerLevel = SDL_JOYSTICK_POWER_MEDIUM; 964 } else { 965 ePowerLevel = SDL_JOYSTICK_POWER_FULL; 966 } 967 } 968 break; 969 case GCDeviceBatteryStateCharging: 970 ePowerLevel = SDL_JOYSTICK_POWER_WIRED; 971 break; 972 case GCDeviceBatteryStateFull: 973 ePowerLevel = SDL_JOYSTICK_POWER_FULL; 974 break; 975 default: 976 break; 977 } 978 979 SDL_PrivateJoystickBatteryLevel(joystick, ePowerLevel); 980 } 981 } 982 #endif /* ENABLE_MFI_BATTERY */ 983 } 984 #endif /* SDL_JOYSTICK_MFI */ 985 } 986 987 #ifdef ENABLE_MFI_RUMBLE 988 989 @interface SDL_RumbleMotor : NSObject 990 @end 991 992 @implementation SDL_RumbleMotor { 993 CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); 994 id<CHHapticPatternPlayer> player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); 995 bool active; 996 } 997 998 -(void)cleanup 999 { 1000 if (self->player != nil) { 1001 [self->player cancelAndReturnError:nil]; 1002 self->player = nil; 1003 } 1004 if (self->engine != nil) { 1005 [self->engine stopWithCompletionHandler:nil]; 1006 self->engine = nil; 1007 } 1008 } 1009 1010 -(int)setIntensity:(float)intensity 1011 { 1012 @autoreleasepool { 1013 if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { 1014 NSError *error; 1015 1016 if (self->engine == nil) { 1017 return SDL_SetError("Haptics engine was stopped"); 1018 } 1019 1020 if (intensity == 0.0f) { 1021 if (self->player && self->active) { 1022 [self->player stopAtTime:0 error:&error]; 1023 } 1024 self->active = false; 1025 return 0; 1026 } 1027 1028 if (self->player == nil) { 1029 CHHapticEventParameter *param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f]; 1030 CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:param, nil] relativeTime:0 duration:GCHapticDurationInfinite]; 1031 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error]; 1032 if (error != nil) { 1033 return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]); 1034 } 1035 1036 self->player = [self->engine createPlayerWithPattern:pattern error:&error]; 1037 if (error != nil) { 1038 return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]); 1039 } 1040 self->active = false; 1041 } 1042 1043 CHHapticDynamicParameter *param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0]; 1044 [self->player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error]; 1045 if (error != nil) { 1046 return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]); 1047 } 1048 1049 if (!self->active) { 1050 [self->player startAtTime:0 error:&error]; 1051 self->active = true; 1052 } 1053 } 1054 1055 return 0; 1056 } 1057 } 1058 1059 -(id) initWithController:(GCController*)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) 1060 { 1061 @autoreleasepool { 1062 self = [super init]; 1063 NSError *error; 1064 1065 self->engine = [controller.haptics createEngineWithLocality:locality]; 1066 if (self->engine == nil) { 1067 SDL_SetError("Couldn't create haptics engine"); 1068 return nil; 1069 } 1070 1071 [self->engine startAndReturnError:&error]; 1072 if (error != nil) { 1073 SDL_SetError("Couldn't start haptics engine"); 1074 return nil; 1075 } 1076 1077 __weak typeof(self) weakSelf = self; 1078 self->engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) { 1079 SDL_RumbleMotor *_this = weakSelf; 1080 if (_this == nil) { 1081 return; 1082 } 1083 1084 _this->player = nil; 1085 _this->engine = nil; 1086 }; 1087 self->engine.resetHandler = ^{ 1088 SDL_RumbleMotor *_this = weakSelf; 1089 if (_this == nil) { 1090 return; 1091 } 1092 1093 _this->player = nil; 1094 [_this->engine startAndReturnError:nil]; 1095 }; 1096 1097 return self; 1098 } 1099 } 1100 1101 @end 1102 1103 @interface SDL_RumbleContext : NSObject 1104 @end 1105 1106 @implementation SDL_RumbleContext { 1107 SDL_RumbleMotor *m_low_frequency_motor; 1108 SDL_RumbleMotor *m_high_frequency_motor; 1109 SDL_RumbleMotor *m_left_trigger_motor; 1110 SDL_RumbleMotor *m_right_trigger_motor; 1111 } 1112 1113 -(id) initWithLowFrequencyMotor:(SDL_RumbleMotor*)low_frequency_motor 1114 HighFrequencyMotor:(SDL_RumbleMotor*)high_frequency_motor 1115 LeftTriggerMotor:(SDL_RumbleMotor*)left_trigger_motor 1116 RightTriggerMotor:(SDL_RumbleMotor*)right_trigger_motor 1117 { 1118 self = [super init]; 1119 self->m_low_frequency_motor = low_frequency_motor; 1120 self->m_high_frequency_motor = high_frequency_motor; 1121 self->m_left_trigger_motor = left_trigger_motor; 1122 self->m_right_trigger_motor = right_trigger_motor; 1123 return self; 1124 } 1125 1126 -(int) rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble 1127 { 1128 int result = 0; 1129 1130 result += [self->m_low_frequency_motor setIntensity:((float)low_frequency_rumble / 65535.0f)]; 1131 result += [self->m_high_frequency_motor setIntensity:((float)high_frequency_rumble / 65535.0f)]; 1132 return ((result < 0) ? -1 : 0); 1133 } 1134 1135 -(int) rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble 1136 { 1137 int result = 0; 1138 1139 if (self->m_left_trigger_motor && self->m_right_trigger_motor) { 1140 result += [self->m_left_trigger_motor setIntensity:((float)left_rumble / 65535.0f)]; 1141 result += [self->m_right_trigger_motor setIntensity:((float)right_rumble / 65535.0f)]; 1142 } else { 1143 result = SDL_Unsupported(); 1144 } 1145 return ((result < 0) ? -1 : 0); 1146 } 1147 1148 -(void)cleanup 1149 { 1150 [self->m_low_frequency_motor cleanup]; 1151 [self->m_high_frequency_motor cleanup]; 1152 } 1153 1154 @end 1155 1156 static SDL_RumbleContext *IOS_JoystickInitRumble(GCController *controller) 1157 { 1158 @autoreleasepool { 1159 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1160 SDL_RumbleMotor *low_frequency_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle]; 1161 SDL_RumbleMotor *high_frequency_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle]; 1162 SDL_RumbleMotor *left_trigger_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger]; 1163 SDL_RumbleMotor *right_trigger_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger]; 1164 if (low_frequency_motor && high_frequency_motor) { 1165 return [[SDL_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor 1166 HighFrequencyMotor:high_frequency_motor 1167 LeftTriggerMotor:left_trigger_motor 1168 RightTriggerMotor:right_trigger_motor]; 1169 } 1170 } 1171 } 1172 return nil; 1173 } 1174 1175 #endif /* ENABLE_MFI_RUMBLE */ 1176 1177 static int 1178 IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 1179 { 1180 #ifdef ENABLE_MFI_RUMBLE 1181 SDL_JoystickDeviceItem *device = joystick->hwdata; 1182 1183 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1184 if (!device->rumble && device->controller && device->controller.haptics) { 1185 SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); 1186 if (rumble) { 1187 device->rumble = (void *)CFBridgingRetain(rumble); 1188 } 1189 } 1190 } 1191 1192 if (device->rumble) { 1193 SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble; 1194 return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble]; 1195 } else { 1196 return SDL_Unsupported(); 1197 } 1198 #else 1199 return SDL_Unsupported(); 1200 #endif 1201 } 1202 1203 static int 1204 IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) 1205 { 1206 #ifdef ENABLE_MFI_RUMBLE 1207 SDL_JoystickDeviceItem *device = joystick->hwdata; 1208 1209 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1210 if (!device->rumble && device->controller && device->controller.haptics) { 1211 SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); 1212 if (rumble) { 1213 device->rumble = (void *)CFBridgingRetain(rumble); 1214 } 1215 } 1216 } 1217 1218 if (device->rumble) { 1219 SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble; 1220 return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble]; 1221 } else { 1222 return SDL_Unsupported(); 1223 } 1224 #else 1225 return SDL_Unsupported(); 1226 #endif 1227 } 1228 1229 static SDL_bool 1230 IOS_JoystickHasLED(SDL_Joystick *joystick) 1231 { 1232 #ifdef ENABLE_MFI_LIGHT 1233 @autoreleasepool { 1234 if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { 1235 GCController *controller = joystick->hwdata->controller; 1236 GCDeviceLight *light = controller.light; 1237 if (light) { 1238 return SDL_TRUE; 1239 } 1240 } 1241 } 1242 #endif /* ENABLE_MFI_LIGHT */ 1243 1244 return SDL_FALSE; 1245 } 1246 1247 static int 1248 IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) 1249 { 1250 #ifdef ENABLE_MFI_LIGHT 1251 @autoreleasepool { 1252 if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { 1253 GCController *controller = joystick->hwdata->controller; 1254 GCDeviceLight *light = controller.light; 1255 if (light) { 1256 light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f 1257 green:(float)green / 255.0f 1258 blue:(float)blue / 255.0f]; 1259 return 0; 1260 } 1261 } 1262 } 1263 #endif /* ENABLE_MFI_LIGHT */ 1264 1265 return SDL_Unsupported(); 1266 } 1267 1268 static int 1269 IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled) 1270 { 1271 #ifdef ENABLE_MFI_SENSORS 1272 @autoreleasepool { 1273 if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { 1274 GCController *controller = joystick->hwdata->controller; 1275 GCMotion *motion = controller.motion; 1276 if (motion) { 1277 motion.sensorsActive = enabled ? YES : NO; 1278 return 0; 1279 } 1280 } 1281 } 1282 #endif /* ENABLE_MFI_SENSORS */ 1283 1284 return SDL_Unsupported(); 1285 } 1286 1287 static void 1288 IOS_JoystickUpdate(SDL_Joystick *joystick) 1289 { 1290 SDL_JoystickDeviceItem *device = joystick->hwdata; 1291 1292 if (device == NULL) { 1293 return; 1294 } 1295 1296 if (device->accelerometer) { 1297 IOS_AccelerometerUpdate(joystick); 1298 } else if (device->controller) { 1299 IOS_MFIJoystickUpdate(joystick); 1300 } 1301 } 1302 1303 static void 1304 IOS_JoystickClose(SDL_Joystick *joystick) 1305 { 1306 SDL_JoystickDeviceItem *device = joystick->hwdata; 1307 1308 if (device == NULL) { 1309 return; 1310 } 1311 1312 device->joystick = NULL; 1313 1314 @autoreleasepool { 1315 #ifdef ENABLE_MFI_RUMBLE 1316 if (device->rumble) { 1317 SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble; 1318 1319 [rumble cleanup]; 1320 CFRelease(device->rumble); 1321 device->rumble = NULL; 1322 } 1323 #endif /* ENABLE_MFI_RUMBLE */ 1324 1325 if (device->accelerometer) { 1326 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 1327 [motionManager stopAccelerometerUpdates]; 1328 #endif 1329 } else if (device->controller) { 1330 #ifdef SDL_JOYSTICK_MFI 1331 GCController *controller = device->controller; 1332 controller.controllerPausedHandler = nil; 1333 controller.playerIndex = -1; 1334 #endif 1335 } 1336 } 1337 if (device->remote) { 1338 --SDL_AppleTVRemoteOpenedAsJoystick; 1339 } 1340 } 1341 1342 static void 1343 IOS_JoystickQuit(void) 1344 { 1345 @autoreleasepool { 1346 #ifdef SDL_JOYSTICK_MFI 1347 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 1348 1349 if (connectObserver) { 1350 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil]; 1351 connectObserver = nil; 1352 } 1353 1354 if (disconnectObserver) { 1355 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil]; 1356 disconnectObserver = nil; 1357 } 1358 1359 #if TARGET_OS_TV 1360 SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 1361 SDL_AppleTVRemoteRotationHintChanged, NULL); 1362 #endif /* TARGET_OS_TV */ 1363 #endif /* SDL_JOYSTICK_MFI */ 1364 1365 while (deviceList != NULL) { 1366 IOS_RemoveJoystickDevice(deviceList); 1367 } 1368 1369 #ifdef SDL_JOYSTICK_iOS_ACCELEROMETER 1370 motionManager = nil; 1371 #endif 1372 } 1373 1374 numjoysticks = 0; 1375 } 1376 1377 static SDL_bool 1378 IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) 1379 { 1380 return SDL_FALSE; 1381 } 1382 1383 #if defined(SDL_JOYSTICK_MFI) && defined(__MACOSX__) 1384 SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) 1385 { 1386 if (is_macos11()) { 1387 return [GCController supportsHIDDevice:device] ? SDL_TRUE : SDL_FALSE; 1388 } 1389 return SDL_FALSE; 1390 } 1391 #endif 1392 1393 SDL_JoystickDriver SDL_IOS_JoystickDriver = 1394 { 1395 IOS_JoystickInit, 1396 IOS_JoystickGetCount, 1397 IOS_JoystickDetect, 1398 IOS_JoystickGetDeviceName, 1399 IOS_JoystickGetDevicePlayerIndex, 1400 IOS_JoystickSetDevicePlayerIndex, 1401 IOS_JoystickGetDeviceGUID, 1402 IOS_JoystickGetDeviceInstanceID, 1403 IOS_JoystickOpen, 1404 IOS_JoystickRumble, 1405 IOS_JoystickRumbleTriggers, 1406 IOS_JoystickHasLED, 1407 IOS_JoystickSetLED, 1408 IOS_JoystickSetSensorsEnabled, 1409 IOS_JoystickUpdate, 1410 IOS_JoystickClose, 1411 IOS_JoystickQuit, 1412 IOS_JoystickGetGamepadMapping 1413 }; 1414 1415 /* vi: set ts=4 sw=4 expandtab: */