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_sysjoystick_c.h" 25 26/* needed for SDL_IPHONE_MAX_GFORCE macro */ 27#include "../../../include/SDL_config_iphoneos.h" 28 29#include "SDL_assert.h" 30#include "SDL_events.h" 31#include "SDL_joystick.h" 32#include "SDL_hints.h" 33#include "SDL_stdinc.h" 34#include "../SDL_sysjoystick.h" 35#include "../SDL_joystick_c.h" 36 37 38#if !SDL_EVENTS_DISABLED 39#include "../../events/SDL_events_c.h" 40#endif 41 42#if !TARGET_OS_TV 43#import <CoreMotion/CoreMotion.h> 44#endif 45 46#ifdef SDL_JOYSTICK_MFI 47#import <GameController/GameController.h> 48 49static id connectObserver = nil; 50static id disconnectObserver = nil; 51 52#include <Availability.h> 53#include <objc/message.h> 54 55/* remove compilation warnings for strict builds by defining these selectors, even though 56 * they are only ever used indirectly through objc_msgSend 57 */ 58@interface GCExtendedGamepad (SDL) 59#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 121000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 121000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1401000) 60@property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton; 61@property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton; 62#endif 63#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000) 64@property (nonatomic, readonly) GCControllerButtonInput *buttonMenu; 65@property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions; 66#endif 67@end 68@interface GCMicroGamepad (SDL) 69#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000) 70@property (nonatomic, readonly) GCControllerButtonInput *buttonMenu; 71#endif 72@end 73 74#endif /* SDL_JOYSTICK_MFI */ 75 76#if !TARGET_OS_TV 77static const char *accelerometerName = "iOS Accelerometer"; 78static CMMotionManager *motionManager = nil; 79#endif /* !TARGET_OS_TV */ 80 81static SDL_JoystickDeviceItem *deviceList = NULL; 82 83static int numjoysticks = 0; 84int SDL_AppleTVRemoteOpenedAsJoystick = 0; 85 86static SDL_JoystickDeviceItem * 87GetDeviceForIndex(int device_index) 88{ 89 SDL_JoystickDeviceItem *device = deviceList; 90 int i = 0; 91 92 while (i < device_index) { 93 if (device == NULL) { 94 return NULL; 95 } 96 device = device->next; 97 i++; 98 } 99 100 return device; 101} 102 103static void 104IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller) 105{ 106#ifdef SDL_JOYSTICK_MFI 107 const Uint16 VENDOR_APPLE = 0x05AC; 108 const Uint16 VENDOR_MICROSOFT = 0x045e; 109 const Uint16 VENDOR_SONY = 0x054C; 110 Uint16 *guid16 = (Uint16 *)device->guid.data; 111 Uint16 vendor = 0; 112 Uint16 product = 0; 113 Uint8 subtype = 0; 114 115 const char *name = NULL; 116 /* Explicitly retain the controller because SDL_JoystickDeviceItem is a 117 * struct, and ARC doesn't work with structs. */ 118 device->controller = (__bridge GCController *) CFBridgingRetain(controller); 119 120 if (controller.vendorName) { 121 name = controller.vendorName.UTF8String; 122 } 123 124 if (!name) { 125 name = "MFi Gamepad"; 126 } 127 128 device->name = SDL_CreateJoystickName(0, 0, NULL, name); 129 130 if (controller.extendedGamepad) { 131 GCExtendedGamepad *gamepad = controller.extendedGamepad; 132 BOOL is_xbox = [controller.vendorName containsString: @"Xbox"]; 133 BOOL is_ps4 = [controller.vendorName containsString: @"DUALSHOCK"]; 134#if TARGET_OS_TV 135 BOOL is_MFi = (!is_xbox && !is_ps4); 136#endif 137 int nbuttons = 0; 138 139 /* These buttons are part of the original MFi spec */ 140 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A); 141 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); 142 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X); 143 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y); 144 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER); 145 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); 146 nbuttons += 6; 147 148 /* These buttons are available on some newer controllers */ 149#pragma clang diagnostic push 150#pragma clang diagnostic ignored "-Wunguarded-availability-new" 151 if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) { 152 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK); 153 ++nbuttons; 154 } 155 if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) { 156 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK); 157 ++nbuttons; 158 } 159 if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) { 160 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK); 161 ++nbuttons; 162 } 163 BOOL has_direct_menu = [gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu; 164#if TARGET_OS_TV 165 /* On tvOS MFi controller menu button brings you to the home screen */ 166 if (is_MFi) { 167 has_direct_menu = FALSE; 168 } 169#endif 170 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START); 171 ++nbuttons; 172 if (!has_direct_menu) { 173 device->uses_pause_handler = SDL_TRUE; 174 } 175#pragma clang diagnostic pop 176 177 if (is_xbox) { 178 vendor = VENDOR_MICROSOFT; 179 product = 0x02E0; /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */ 180 } else if (is_ps4) { 181 vendor = VENDOR_SONY; 182 product = 0x09CC; /* Assume DS4 Slim unless/until GCController flows VID/PID */ 183 } else { 184 vendor = VENDOR_APPLE; 185 product = 1; 186 subtype = 1; 187 } 188 189 device->naxes = 6; /* 2 thumbsticks and 2 triggers */ 190 device->nhats = 1; /* d-pad */ 191 device->nbuttons = nbuttons; 192 193 } else if (controller.gamepad) { 194 int nbuttons = 0; 195 196 /* These buttons are part of the original MFi spec */ 197 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A); 198 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); 199 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X); 200 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y); 201 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER); 202 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); 203 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START); 204 nbuttons += 7; 205 device->uses_pause_handler = SDL_TRUE; 206 207 vendor = VENDOR_APPLE; 208 product = 2; 209 subtype = 2; 210 device->naxes = 0; /* no traditional analog inputs */ 211 device->nhats = 1; /* d-pad */ 212 device->nbuttons = nbuttons; 213 } 214#if TARGET_OS_TV 215 else if (controller.microGamepad) { 216 int nbuttons = 0; 217 218 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A); 219 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */ 220 nbuttons += 2; 221 222 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START); 223 ++nbuttons; 224 device->uses_pause_handler = SDL_TRUE; 225 226 vendor = VENDOR_APPLE; 227 product = 3; 228 subtype = 3; 229 device->naxes = 2; /* treat the touch surface as two axes */ 230 device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */ 231 device->nbuttons = nbuttons; 232 233 controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE); 234 } 235#endif /* TARGET_OS_TV */ 236 237 /* We only need 16 bits for each of these; space them out to fill 128. */ 238 /* Byteswap so devices get same GUID on little/big endian platforms. */ 239 *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH); 240 *guid16++ = 0; 241 *guid16++ = SDL_SwapLE16(vendor); 242 *guid16++ = 0; 243 *guid16++ = SDL_SwapLE16(product); 244 *guid16++ = 0; 245 246 *guid16++ = SDL_SwapLE16(device->button_mask); 247 248 if (subtype != 0) { 249 /* Note that this is an MFI controller and what subtype it is */ 250 device->guid.data[14] = 'm'; 251 device->guid.data[15] = subtype; 252 } 253 254 /* This will be set when the first button press of the controller is 255 * detected. */ 256 controller.playerIndex = -1; 257 258#endif /* SDL_JOYSTICK_MFI */ 259} 260 261static void 262IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer) 263{ 264 SDL_JoystickDeviceItem *device = deviceList; 265 266#if TARGET_OS_TV 267 if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) { 268 /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */ 269 if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) { 270 return; 271 } 272 } 273#endif 274 275 while (device != NULL) { 276 if (device->controller == controller) { 277 return; 278 } 279 device = device->next; 280 } 281 282 device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem)); 283 if (device == NULL) { 284 return; 285 } 286 287 device->accelerometer = accelerometer; 288 device->instance_id = SDL_GetNextJoystickInstanceID(); 289 290 if (accelerometer) { 291#if TARGET_OS_TV 292 SDL_free(device); 293 return; 294#else 295 device->name = SDL_strdup(accelerometerName); 296 device->naxes = 3; /* Device acceleration in the x, y, and z axes. */ 297 device->nhats = 0; 298 device->nbuttons = 0; 299 300 /* Use the accelerometer name as a GUID. */ 301 SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name))); 302#endif /* TARGET_OS_TV */ 303 } else if (controller) { 304 IOS_AddMFIJoystickDevice(device, controller); 305 } 306 307 if (deviceList == NULL) { 308 deviceList = device; 309 } else { 310 SDL_JoystickDeviceItem *lastdevice = deviceList; 311 while (lastdevice->next != NULL) { 312 lastdevice = lastdevice->next; 313 } 314 lastdevice->next = device; 315 } 316 317 ++numjoysticks; 318 319 SDL_PrivateJoystickAdded(device->instance_id); 320} 321 322static SDL_JoystickDeviceItem * 323IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device) 324{ 325 SDL_JoystickDeviceItem *prev = NULL; 326 SDL_JoystickDeviceItem *next = NULL; 327 SDL_JoystickDeviceItem *item = deviceList; 328 329 if (device == NULL) { 330 return NULL; 331 } 332 333 next = device->next; 334 335 while (item != NULL) { 336 if (item == device) { 337 break; 338 } 339 prev = item; 340 item = item->next; 341 } 342 343 /* Unlink the device item from the device list. */ 344 if (prev) { 345 prev->next = device->next; 346 } else if (device == deviceList) { 347 deviceList = device->next; 348 } 349 350 if (device->joystick) { 351 device->joystick->hwdata = NULL; 352 } 353 354#ifdef SDL_JOYSTICK_MFI 355 @autoreleasepool { 356 if (device->controller) { 357 /* The controller was explicitly retained in the struct, so it 358 * should be explicitly released before freeing the struct. */ 359 GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller)); 360 controller.controllerPausedHandler = nil; 361 device->controller = nil; 362 } 363 } 364#endif /* SDL_JOYSTICK_MFI */ 365 366 --numjoysticks; 367 368 SDL_PrivateJoystickRemoved(device->instance_id); 369 370 SDL_free(device->name); 371 SDL_free(device); 372 373 return next; 374} 375 376#if TARGET_OS_TV 377static void SDLCALL 378SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue) 379{ 380 BOOL allowRotation = newValue != NULL && *newValue != '0'; 381 382 @autoreleasepool { 383 for (GCController *controller in [GCController controllers]) { 384 if (controller.microGamepad) { 385 controller.microGamepad.allowsRotation = allowRotation; 386 } 387 } 388 } 389} 390#endif /* TARGET_OS_TV */ 391 392static int 393IOS_JoystickInit(void) 394{ 395 @autoreleasepool { 396#if !TARGET_OS_TV 397 if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) { 398 /* Default behavior, accelerometer as joystick */ 399 IOS_AddJoystickDevice(nil, SDL_TRUE); 400 } 401#endif /* !TARGET_OS_TV */ 402 403#ifdef SDL_JOYSTICK_MFI 404 /* GameController.framework was added in iOS 7. */ 405 if (![GCController class]) { 406 return 0; 407 } 408 409 for (GCController *controller in [GCController controllers]) { 410 IOS_AddJoystickDevice(controller, SDL_FALSE); 411 } 412 413#if TARGET_OS_TV 414 SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 415 SDL_AppleTVRemoteRotationHintChanged, NULL); 416#endif /* TARGET_OS_TV */ 417 418 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 419 420 connectObserver = [center addObserverForName:GCControllerDidConnectNotification 421 object:nil 422 queue:nil 423 usingBlock:^(NSNotification *note) { 424 GCController *controller = note.object; 425 IOS_AddJoystickDevice(controller, SDL_FALSE); 426 }]; 427 428 disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification 429 object:nil 430 queue:nil 431 usingBlock:^(NSNotification *note) { 432 GCController *controller = note.object; 433 SDL_JoystickDeviceItem *device = deviceList; 434 while (device != NULL) { 435 if (device->controller == controller) { 436 IOS_RemoveJoystickDevice(device); 437 break; 438 } 439 device = device->next; 440 } 441 }]; 442#endif /* SDL_JOYSTICK_MFI */ 443 } 444 445 return 0; 446} 447 448static int 449IOS_JoystickGetCount(void) 450{ 451 return numjoysticks; 452} 453 454static void 455IOS_JoystickDetect(void) 456{ 457} 458 459static const char * 460IOS_JoystickGetDeviceName(int device_index) 461{ 462 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 463 return device ? device->name : "Unknown"; 464} 465 466static int 467IOS_JoystickGetDevicePlayerIndex(int device_index) 468{ 469#ifdef SDL_JOYSTICK_MFI 470 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 471 if (device && device->controller) { 472 return (int)device->controller.playerIndex; 473 } 474#endif 475 return -1; 476} 477 478static void 479IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index) 480{ 481#ifdef SDL_JOYSTICK_MFI 482 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 483 if (device && device->controller) { 484 device->controller.playerIndex = player_index; 485 } 486#endif 487} 488 489static SDL_JoystickGUID 490IOS_JoystickGetDeviceGUID( int device_index ) 491{ 492 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 493 SDL_JoystickGUID guid; 494 if (device) { 495 guid = device->guid; 496 } else { 497 SDL_zero(guid); 498 } 499 return guid; 500} 501 502static SDL_JoystickID 503IOS_JoystickGetDeviceInstanceID(int device_index) 504{ 505 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 506 return device ? device->instance_id : -1; 507} 508 509static int 510IOS_JoystickOpen(SDL_Joystick * joystick, int device_index) 511{ 512 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 513 if (device == NULL) { 514 return SDL_SetError("Could not open Joystick: no hardware device for the specified index"); 515 } 516 517 joystick->hwdata = device; 518 joystick->instance_id = device->instance_id; 519 520 joystick->naxes = device->naxes; 521 joystick->nhats = device->nhats; 522 joystick->nbuttons = device->nbuttons; 523 joystick->nballs = 0; 524 525 device->joystick = joystick; 526 527 @autoreleasepool { 528 if (device->accelerometer) { 529#if !TARGET_OS_TV 530 if (motionManager == nil) { 531 motionManager = [[CMMotionManager alloc] init]; 532 } 533 534 /* Shorter times between updates can significantly increase CPU usage. */ 535 motionManager.accelerometerUpdateInterval = 0.1; 536 [motionManager startAccelerometerUpdates]; 537#endif /* !TARGET_OS_TV */ 538 } else { 539#ifdef SDL_JOYSTICK_MFI 540 if (device->uses_pause_handler) { 541 GCController *controller = device->controller; 542 controller.controllerPausedHandler = ^(GCController *c) { 543 if (joystick->hwdata) { 544 ++joystick->hwdata->num_pause_presses; 545 } 546 }; 547 } 548#endif /* SDL_JOYSTICK_MFI */ 549 } 550 } 551 if (device->remote) { 552 ++SDL_AppleTVRemoteOpenedAsJoystick; 553 } 554 555 return 0; 556} 557 558static void 559IOS_AccelerometerUpdate(SDL_Joystick * joystick) 560{ 561#if !TARGET_OS_TV 562 const float maxgforce = SDL_IPHONE_MAX_GFORCE; 563 const SInt16 maxsint16 = 0x7FFF; 564 CMAcceleration accel; 565 566 @autoreleasepool { 567 if (!motionManager.isAccelerometerActive) { 568 return; 569 } 570 571 accel = motionManager.accelerometerData.acceleration; 572 } 573 574 /* 575 Convert accelerometer data from floating point to Sint16, which is what 576 the joystick system expects. 577 578 To do the conversion, the data is first clamped onto the interval 579 [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied 580 by MAX_SINT16 so that it is mapped to the full range of an Sint16. 581 582 You can customize the clamped range of this function by modifying the 583 SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h. 584 585 Once converted to Sint16, the accelerometer data no longer has coherent 586 units. You can convert the data back to units of g-force by multiplying 587 it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF. 588 */ 589 590 /* clamp the data */ 591 accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce); 592 accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce); 593 accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce); 594 595 /* pass in data mapped to range of SInt16 */ 596 SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16); 597 SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16); 598 SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16); 599#endif /* !TARGET_OS_TV */ 600} 601 602#ifdef SDL_JOYSTICK_MFI 603static Uint8 604IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) 605{ 606 Uint8 hat = 0; 607 608 if (dpad.up.isPressed) { 609 hat |= SDL_HAT_UP; 610 } else if (dpad.down.isPressed) { 611 hat |= SDL_HAT_DOWN; 612 } 613 614 if (dpad.left.isPressed) { 615 hat |= SDL_HAT_LEFT; 616 } else if (dpad.right.isPressed) { 617 hat |= SDL_HAT_RIGHT; 618 } 619 620 if (hat == 0) { 621 return SDL_HAT_CENTERED; 622 } 623 624 return hat; 625} 626#endif 627 628static void 629IOS_MFIJoystickUpdate(SDL_Joystick * joystick) 630{ 631#if SDL_JOYSTICK_MFI 632 @autoreleasepool { 633 GCController *controller = joystick->hwdata->controller; 634 Uint8 hatstate = SDL_HAT_CENTERED; 635 int i; 636 int pause_button_index = 0; 637 638 if (controller.extendedGamepad) { 639 GCExtendedGamepad *gamepad = controller.extendedGamepad; 640 641 /* Axis order matches the XInput Windows mappings. */ 642 Sint16 axes[] = { 643 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767), 644 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767), 645 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768), 646 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767), 647 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767), 648 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768), 649 }; 650 651 /* Button order matches the XInput Windows mappings. */ 652 Uint8 buttons[joystick->nbuttons]; 653 int button_count = 0; 654 655 /* These buttons are part of the original MFi spec */ 656 buttons[button_count++] = gamepad.buttonA.isPressed; 657 buttons[button_count++] = gamepad.buttonB.isPressed; 658 buttons[button_count++] = gamepad.buttonX.isPressed; 659 buttons[button_count++] = gamepad.buttonY.isPressed; 660 buttons[button_count++] = gamepad.leftShoulder.isPressed; 661 buttons[button_count++] = gamepad.rightShoulder.isPressed; 662 663 /* These buttons are available on some newer controllers */ 664#pragma clang diagnostic push 665#pragma clang diagnostic ignored "-Wunguarded-availability-new" 666 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) { 667 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed; 668 } 669 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) { 670 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed; 671 } 672 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) { 673 buttons[button_count++] = gamepad.buttonOptions.isPressed; 674 } 675 /* This must be the last button, so we can optionally handle it with pause_button_index below */ 676 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { 677 if (joystick->hwdata->uses_pause_handler) { 678 pause_button_index = button_count; 679 buttons[button_count++] = joystick->delayed_guide_button; 680 } else { 681 buttons[button_count++] = gamepad.buttonMenu.isPressed; 682 } 683 } 684#pragma clang diagnostic pop 685 686 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 687 688 for (i = 0; i < SDL_arraysize(axes); i++) { 689 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 690 } 691 692 for (i = 0; i < button_count; i++) { 693 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 694 } 695 } else if (controller.gamepad) { 696 GCGamepad *gamepad = controller.gamepad; 697 698 /* Button order matches the XInput Windows mappings. */ 699 Uint8 buttons[joystick->nbuttons]; 700 int button_count = 0; 701 buttons[button_count++] = gamepad.buttonA.isPressed; 702 buttons[button_count++] = gamepad.buttonB.isPressed; 703 buttons[button_count++] = gamepad.buttonX.isPressed; 704 buttons[button_count++] = gamepad.buttonY.isPressed; 705 buttons[button_count++] = gamepad.leftShoulder.isPressed; 706 buttons[button_count++] = gamepad.rightShoulder.isPressed; 707 pause_button_index = button_count; 708 buttons[button_count++] = joystick->delayed_guide_button; 709 710 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 711 712 for (i = 0; i < button_count; i++) { 713 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 714 } 715 } 716#if TARGET_OS_TV 717 else if (controller.microGamepad) { 718 GCMicroGamepad *gamepad = controller.microGamepad; 719 720 Sint16 axes[] = { 721 (Sint16) (gamepad.dpad.xAxis.value * 32767), 722 (Sint16) (gamepad.dpad.yAxis.value * -32767), 723 }; 724 725 for (i = 0; i < SDL_arraysize(axes); i++) { 726 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 727 } 728 729 Uint8 buttons[joystick->nbuttons]; 730 int button_count = 0; 731 buttons[button_count++] = gamepad.buttonA.isPressed; 732 buttons[button_count++] = gamepad.buttonX.isPressed; 733#pragma clang diagnostic push 734#pragma clang diagnostic ignored "-Wunguarded-availability-new" 735 /* This must be the last button, so we can optionally handle it with pause_button_index below */ 736 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) { 737 if (joystick->hwdata->uses_pause_handler) { 738 pause_button_index = button_count; 739 buttons[button_count++] = joystick->delayed_guide_button; 740 } else { 741 buttons[button_count++] = gamepad.buttonMenu.isPressed; 742 } 743 } 744#pragma clang diagnostic pop 745 746 for (i = 0; i < button_count; i++) { 747 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 748 } 749 } 750#endif /* TARGET_OS_TV */ 751 752 if (joystick->nhats > 0) { 753 SDL_PrivateJoystickHat(joystick, 0, hatstate); 754 } 755 756 if (joystick->hwdata->uses_pause_handler) { 757 for (i = 0; i < joystick->hwdata->num_pause_presses; i++) { 758 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED); 759 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED); 760 } 761 joystick->hwdata->num_pause_presses = 0; 762 } 763 } 764#endif /* SDL_JOYSTICK_MFI */ 765} 766 767static int 768IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) 769{ 770 return SDL_Unsupported(); 771} 772 773static void 774IOS_JoystickUpdate(SDL_Joystick * joystick) 775{ 776 SDL_JoystickDeviceItem *device = joystick->hwdata; 777 778 if (device == NULL) { 779 return; 780 } 781 782 if (device->accelerometer) { 783 IOS_AccelerometerUpdate(joystick); 784 } else if (device->controller) { 785 IOS_MFIJoystickUpdate(joystick); 786 } 787} 788 789static void 790IOS_JoystickClose(SDL_Joystick * joystick) 791{ 792 SDL_JoystickDeviceItem *device = joystick->hwdata; 793 794 if (device == NULL) { 795 return; 796 } 797 798 device->joystick = NULL; 799 800 @autoreleasepool { 801 if (device->accelerometer) { 802#if !TARGET_OS_TV 803 [motionManager stopAccelerometerUpdates]; 804#endif /* !TARGET_OS_TV */ 805 } else if (device->controller) { 806#ifdef SDL_JOYSTICK_MFI 807 GCController *controller = device->controller; 808 controller.controllerPausedHandler = nil; 809 controller.playerIndex = -1; 810#endif 811 } 812 } 813 if (device->remote) { 814 --SDL_AppleTVRemoteOpenedAsJoystick; 815 } 816} 817 818static void 819IOS_JoystickQuit(void) 820{ 821 @autoreleasepool { 822#ifdef SDL_JOYSTICK_MFI 823 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 824 825 if (connectObserver) { 826 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil]; 827 connectObserver = nil; 828 } 829 830 if (disconnectObserver) { 831 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil]; 832 disconnectObserver = nil; 833 } 834 835#if TARGET_OS_TV 836 SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 837 SDL_AppleTVRemoteRotationHintChanged, NULL); 838#endif /* TARGET_OS_TV */ 839#endif /* SDL_JOYSTICK_MFI */ 840 841 while (deviceList != NULL) { 842 IOS_RemoveJoystickDevice(deviceList); 843 } 844 845#if !TARGET_OS_TV 846 motionManager = nil; 847#endif /* !TARGET_OS_TV */ 848 } 849 850 numjoysticks = 0; 851} 852 853static SDL_bool 854IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) 855{ 856 return SDL_FALSE; 857} 858 859SDL_JoystickDriver SDL_IOS_JoystickDriver = 860{ 861 IOS_JoystickInit, 862 IOS_JoystickGetCount, 863 IOS_JoystickDetect, 864 IOS_JoystickGetDeviceName, 865 IOS_JoystickGetDevicePlayerIndex, 866 IOS_JoystickSetDevicePlayerIndex, 867 IOS_JoystickGetDeviceGUID, 868 IOS_JoystickGetDeviceInstanceID, 869 IOS_JoystickOpen, 870 IOS_JoystickRumble, 871 IOS_JoystickUpdate, 872 IOS_JoystickClose, 873 IOS_JoystickQuit, 874 IOS_JoystickGetGamepadMapping 875}; 876 877/* vi: set ts=4 sw=4 expandtab: */ 878