Atlas - SDL_sysjoystick.m
Home / ext / SDL2 / src / joystick / iphoneos Lines: 1 | Size: 22753 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2018 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_c.h" 25 26/* needed for SDL_IPHONE_MAX_GFORCE macro */ 27#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#endif /* SDL_JOYSTICK_MFI */ 52 53#if !TARGET_OS_TV 54static const char *accelerometerName = "iOS Accelerometer"; 55static CMMotionManager *motionManager = nil; 56#endif /* !TARGET_OS_TV */ 57 58static SDL_JoystickDeviceItem *deviceList = NULL; 59 60static int numjoysticks = 0; 61int SDL_AppleTVRemoteOpenedAsJoystick = 0; 62 63static SDL_JoystickDeviceItem * 64GetDeviceForIndex(int device_index) 65{ 66 SDL_JoystickDeviceItem *device = deviceList; 67 int i = 0; 68 69 while (i < device_index) { 70 if (device == NULL) { 71 return NULL; 72 } 73 device = device->next; 74 i++; 75 } 76 77 return device; 78} 79 80static void 81IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller) 82{ 83#ifdef SDL_JOYSTICK_MFI 84 const Uint16 VENDOR_APPLE = 0x05AC; 85 Uint16 *guid16 = (Uint16 *)device->guid.data; 86 Uint16 vendor = 0; 87 Uint16 product = 0; 88 Uint16 version = 0; 89 Uint8 subtype = 0; 90 91 const char *name = NULL; 92 /* Explicitly retain the controller because SDL_JoystickDeviceItem is a 93 * struct, and ARC doesn't work with structs. */ 94 device->controller = (__bridge GCController *) CFBridgingRetain(controller); 95 96 if (controller.vendorName) { 97 name = controller.vendorName.UTF8String; 98 } 99 100 if (!name) { 101 name = "MFi Gamepad"; 102 } 103 104 device->name = SDL_strdup(name); 105 106 if (controller.extendedGamepad) { 107 vendor = VENDOR_APPLE; 108 product = 1; 109 subtype = 1; 110 device->naxes = 6; /* 2 thumbsticks and 2 triggers */ 111 device->nhats = 1; /* d-pad */ 112 device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */ 113 } else if (controller.gamepad) { 114 vendor = VENDOR_APPLE; 115 product = 2; 116 subtype = 2; 117 device->naxes = 0; /* no traditional analog inputs */ 118 device->nhats = 1; /* d-pad */ 119 device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */ 120 } 121#if TARGET_OS_TV 122 else if (controller.microGamepad) { 123 vendor = VENDOR_APPLE; 124 product = 3; 125 subtype = 3; 126 device->naxes = 2; /* treat the touch surface as two axes */ 127 device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */ 128 device->nbuttons = 3; /* AX, pause button */ 129 130 controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE); 131 } 132#endif /* TARGET_OS_TV */ 133 134 /* We only need 16 bits for each of these; space them out to fill 128. */ 135 /* Byteswap so devices get same GUID on little/big endian platforms. */ 136 *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH); 137 *guid16++ = 0; 138 *guid16++ = SDL_SwapLE16(vendor); 139 *guid16++ = 0; 140 *guid16++ = SDL_SwapLE16(product); 141 *guid16++ = 0; 142 *guid16++ = SDL_SwapLE16(version); 143 *guid16++ = 0; 144 145 /* Note that this is an MFI controller and what subtype it is */ 146 device->guid.data[14] = 'm'; 147 device->guid.data[15] = subtype; 148 149 /* This will be set when the first button press of the controller is 150 * detected. */ 151 controller.playerIndex = -1; 152 153#endif /* SDL_JOYSTICK_MFI */ 154} 155 156static void 157IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer) 158{ 159 SDL_JoystickDeviceItem *device = deviceList; 160 161#if TARGET_OS_TV 162 if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) { 163 /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */ 164 if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) { 165 return; 166 } 167 } 168#endif 169 170 while (device != NULL) { 171 if (device->controller == controller) { 172 return; 173 } 174 device = device->next; 175 } 176 177 device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem)); 178 if (device == NULL) { 179 return; 180 } 181 182 device->accelerometer = accelerometer; 183 device->instance_id = SDL_GetNextJoystickInstanceID(); 184 185 if (accelerometer) { 186#if TARGET_OS_TV 187 SDL_free(device); 188 return; 189#else 190 device->name = SDL_strdup(accelerometerName); 191 device->naxes = 3; /* Device acceleration in the x, y, and z axes. */ 192 device->nhats = 0; 193 device->nbuttons = 0; 194 195 /* Use the accelerometer name as a GUID. */ 196 SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name))); 197#endif /* TARGET_OS_TV */ 198 } else if (controller) { 199 IOS_AddMFIJoystickDevice(device, controller); 200 } 201 202 if (deviceList == NULL) { 203 deviceList = device; 204 } else { 205 SDL_JoystickDeviceItem *lastdevice = deviceList; 206 while (lastdevice->next != NULL) { 207 lastdevice = lastdevice->next; 208 } 209 lastdevice->next = device; 210 } 211 212 ++numjoysticks; 213 214 SDL_PrivateJoystickAdded(device->instance_id); 215} 216 217static SDL_JoystickDeviceItem * 218IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device) 219{ 220 SDL_JoystickDeviceItem *prev = NULL; 221 SDL_JoystickDeviceItem *next = NULL; 222 SDL_JoystickDeviceItem *item = deviceList; 223 224 if (device == NULL) { 225 return NULL; 226 } 227 228 next = device->next; 229 230 while (item != NULL) { 231 if (item == device) { 232 break; 233 } 234 prev = item; 235 item = item->next; 236 } 237 238 /* Unlink the device item from the device list. */ 239 if (prev) { 240 prev->next = device->next; 241 } else if (device == deviceList) { 242 deviceList = device->next; 243 } 244 245 if (device->joystick) { 246 device->joystick->hwdata = NULL; 247 } 248 249#ifdef SDL_JOYSTICK_MFI 250 @autoreleasepool { 251 if (device->controller) { 252 /* The controller was explicitly retained in the struct, so it 253 * should be explicitly released before freeing the struct. */ 254 GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller)); 255 controller.controllerPausedHandler = nil; 256 device->controller = nil; 257 } 258 } 259#endif /* SDL_JOYSTICK_MFI */ 260 261 --numjoysticks; 262 263 SDL_PrivateJoystickRemoved(device->instance_id); 264 265 SDL_free(device->name); 266 SDL_free(device); 267 268 return next; 269} 270 271#if TARGET_OS_TV 272static void SDLCALL 273SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue) 274{ 275 BOOL allowRotation = newValue != NULL && *newValue != '0'; 276 277 @autoreleasepool { 278 for (GCController *controller in [GCController controllers]) { 279 if (controller.microGamepad) { 280 controller.microGamepad.allowsRotation = allowRotation; 281 } 282 } 283 } 284} 285#endif /* TARGET_OS_TV */ 286 287static int 288IOS_JoystickInit(void) 289{ 290 @autoreleasepool { 291 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 292 293#if !TARGET_OS_TV 294 if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) { 295 /* Default behavior, accelerometer as joystick */ 296 IOS_AddJoystickDevice(nil, SDL_TRUE); 297 } 298#endif /* !TARGET_OS_TV */ 299 300#ifdef SDL_JOYSTICK_MFI 301 /* GameController.framework was added in iOS 7. */ 302 if (![GCController class]) { 303 return 0; 304 } 305 306 for (GCController *controller in [GCController controllers]) { 307 IOS_AddJoystickDevice(controller, SDL_FALSE); 308 } 309 310#if TARGET_OS_TV 311 SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 312 SDL_AppleTVRemoteRotationHintChanged, NULL); 313#endif /* TARGET_OS_TV */ 314 315 connectObserver = [center addObserverForName:GCControllerDidConnectNotification 316 object:nil 317 queue:nil 318 usingBlock:^(NSNotification *note) { 319 GCController *controller = note.object; 320 IOS_AddJoystickDevice(controller, SDL_FALSE); 321 }]; 322 323 disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification 324 object:nil 325 queue:nil 326 usingBlock:^(NSNotification *note) { 327 GCController *controller = note.object; 328 SDL_JoystickDeviceItem *device = deviceList; 329 while (device != NULL) { 330 if (device->controller == controller) { 331 IOS_RemoveJoystickDevice(device); 332 break; 333 } 334 device = device->next; 335 } 336 }]; 337#endif /* SDL_JOYSTICK_MFI */ 338 } 339 340 return 0; 341} 342 343static int 344IOS_JoystickGetCount(void) 345{ 346 return numjoysticks; 347} 348 349static void 350IOS_JoystickDetect(void) 351{ 352} 353 354static const char * 355IOS_JoystickGetDeviceName(int device_index) 356{ 357 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 358 return device ? device->name : "Unknown"; 359} 360 361static SDL_JoystickGUID 362IOS_JoystickGetDeviceGUID( int device_index ) 363{ 364 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 365 SDL_JoystickGUID guid; 366 if (device) { 367 guid = device->guid; 368 } else { 369 SDL_zero(guid); 370 } 371 return guid; 372} 373 374static SDL_JoystickID 375IOS_JoystickGetDeviceInstanceID(int device_index) 376{ 377 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 378 return device ? device->instance_id : -1; 379} 380 381static int 382IOS_JoystickOpen(SDL_Joystick * joystick, int device_index) 383{ 384 SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index); 385 if (device == NULL) { 386 return SDL_SetError("Could not open Joystick: no hardware device for the specified index"); 387 } 388 389 joystick->hwdata = device; 390 joystick->instance_id = device->instance_id; 391 392 joystick->naxes = device->naxes; 393 joystick->nhats = device->nhats; 394 joystick->nbuttons = device->nbuttons; 395 joystick->nballs = 0; 396 397 device->joystick = joystick; 398 399 @autoreleasepool { 400 if (device->accelerometer) { 401#if !TARGET_OS_TV 402 if (motionManager == nil) { 403 motionManager = [[CMMotionManager alloc] init]; 404 } 405 406 /* Shorter times between updates can significantly increase CPU usage. */ 407 motionManager.accelerometerUpdateInterval = 0.1; 408 [motionManager startAccelerometerUpdates]; 409#endif /* !TARGET_OS_TV */ 410 } else { 411#ifdef SDL_JOYSTICK_MFI 412 GCController *controller = device->controller; 413 controller.controllerPausedHandler = ^(GCController *c) { 414 if (joystick->hwdata) { 415 ++joystick->hwdata->num_pause_presses; 416 } 417 }; 418#endif /* SDL_JOYSTICK_MFI */ 419 } 420 } 421 if (device->remote) { 422 ++SDL_AppleTVRemoteOpenedAsJoystick; 423 } 424 425 return 0; 426} 427 428static void 429IOS_AccelerometerUpdate(SDL_Joystick * joystick) 430{ 431#if !TARGET_OS_TV 432 const float maxgforce = SDL_IPHONE_MAX_GFORCE; 433 const SInt16 maxsint16 = 0x7FFF; 434 CMAcceleration accel; 435 436 @autoreleasepool { 437 if (!motionManager.isAccelerometerActive) { 438 return; 439 } 440 441 accel = motionManager.accelerometerData.acceleration; 442 } 443 444 /* 445 Convert accelerometer data from floating point to Sint16, which is what 446 the joystick system expects. 447 448 To do the conversion, the data is first clamped onto the interval 449 [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied 450 by MAX_SINT16 so that it is mapped to the full range of an Sint16. 451 452 You can customize the clamped range of this function by modifying the 453 SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h. 454 455 Once converted to Sint16, the accelerometer data no longer has coherent 456 units. You can convert the data back to units of g-force by multiplying 457 it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF. 458 */ 459 460 /* clamp the data */ 461 accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce); 462 accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce); 463 accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce); 464 465 /* pass in data mapped to range of SInt16 */ 466 SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16); 467 SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16); 468 SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16); 469#endif /* !TARGET_OS_TV */ 470} 471 472#ifdef SDL_JOYSTICK_MFI 473static Uint8 474IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad) 475{ 476 Uint8 hat = 0; 477 478 if (dpad.up.isPressed) { 479 hat |= SDL_HAT_UP; 480 } else if (dpad.down.isPressed) { 481 hat |= SDL_HAT_DOWN; 482 } 483 484 if (dpad.left.isPressed) { 485 hat |= SDL_HAT_LEFT; 486 } else if (dpad.right.isPressed) { 487 hat |= SDL_HAT_RIGHT; 488 } 489 490 if (hat == 0) { 491 return SDL_HAT_CENTERED; 492 } 493 494 return hat; 495} 496#endif 497 498static void 499IOS_MFIJoystickUpdate(SDL_Joystick * joystick) 500{ 501#if SDL_JOYSTICK_MFI 502 @autoreleasepool { 503 GCController *controller = joystick->hwdata->controller; 504 Uint8 hatstate = SDL_HAT_CENTERED; 505 int i; 506 int updateplayerindex = 0; 507 508 if (controller.extendedGamepad) { 509 GCExtendedGamepad *gamepad = controller.extendedGamepad; 510 511 /* Axis order matches the XInput Windows mappings. */ 512 Sint16 axes[] = { 513 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767), 514 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767), 515 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768), 516 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767), 517 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767), 518 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768), 519 }; 520 521 /* Button order matches the XInput Windows mappings. */ 522 Uint8 buttons[] = { 523 gamepad.buttonA.isPressed, gamepad.buttonB.isPressed, 524 gamepad.buttonX.isPressed, gamepad.buttonY.isPressed, 525 gamepad.leftShoulder.isPressed, 526 gamepad.rightShoulder.isPressed, 527 }; 528 529 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 530 531 for (i = 0; i < SDL_arraysize(axes); i++) { 532 /* The triggers (axes 2 and 5) are resting at -32768 but SDL 533 * initializes its values to 0. We only want to make sure the 534 * player index is up to date if the user actually moves an axis. */ 535 if ((i != 2 && i != 5) || axes[i] != -32768) { 536 updateplayerindex |= (joystick->axes[i].value != axes[i]); 537 } 538 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 539 } 540 541 for (i = 0; i < SDL_arraysize(buttons); i++) { 542 updateplayerindex |= (joystick->buttons[i] != buttons[i]); 543 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 544 } 545 } else if (controller.gamepad) { 546 GCGamepad *gamepad = controller.gamepad; 547 548 /* Button order matches the XInput Windows mappings. */ 549 Uint8 buttons[] = { 550 gamepad.buttonA.isPressed, gamepad.buttonB.isPressed, 551 gamepad.buttonX.isPressed, gamepad.buttonY.isPressed, 552 gamepad.leftShoulder.isPressed, 553 gamepad.rightShoulder.isPressed, 554 }; 555 556 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad); 557 558 for (i = 0; i < SDL_arraysize(buttons); i++) { 559 updateplayerindex |= (joystick->buttons[i] != buttons[i]); 560 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 561 } 562 } 563#if TARGET_OS_TV 564 else if (controller.microGamepad) { 565 GCMicroGamepad *gamepad = controller.microGamepad; 566 567 Sint16 axes[] = { 568 (Sint16) (gamepad.dpad.xAxis.value * 32767), 569 (Sint16) (gamepad.dpad.yAxis.value * -32767), 570 }; 571 572 for (i = 0; i < SDL_arraysize(axes); i++) { 573 updateplayerindex |= (joystick->axes[i].value != axes[i]); 574 SDL_PrivateJoystickAxis(joystick, i, axes[i]); 575 } 576 577 Uint8 buttons[] = { 578 gamepad.buttonA.isPressed, 579 gamepad.buttonX.isPressed, 580 }; 581 582 for (i = 0; i < SDL_arraysize(buttons); i++) { 583 updateplayerindex |= (joystick->buttons[i] != buttons[i]); 584 SDL_PrivateJoystickButton(joystick, i, buttons[i]); 585 } 586 } 587#endif /* TARGET_OS_TV */ 588 589 if (joystick->nhats > 0) { 590 updateplayerindex |= (joystick->hats[0] != hatstate); 591 SDL_PrivateJoystickHat(joystick, 0, hatstate); 592 } 593 594 for (i = 0; i < joystick->hwdata->num_pause_presses; i++) { 595 const Uint8 pausebutton = joystick->nbuttons - 1; /* The pause button is always last. */ 596 SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED); 597 SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED); 598 updateplayerindex = YES; 599 } 600 joystick->hwdata->num_pause_presses = 0; 601 602 if (updateplayerindex && controller.playerIndex == -1) { 603 BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO}; 604 605 /* Find the player index of all other connected controllers. */ 606 for (GCController *c in [GCController controllers]) { 607 if (c != controller && c.playerIndex >= 0) { 608 usedPlayerIndexSlots[c.playerIndex] = YES; 609 } 610 } 611 612 /* Set this controller's player index to the first unused index. 613 * FIXME: This logic isn't great... but SDL doesn't expose this 614 * concept in its external API, so we don't have much to go on. */ 615 for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) { 616 if (!usedPlayerIndexSlots[i]) { 617 controller.playerIndex = i; 618 break; 619 } 620 } 621 } 622 } 623#endif /* SDL_JOYSTICK_MFI */ 624} 625 626static int 627IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms) 628{ 629 return SDL_Unsupported(); 630} 631 632static void 633IOS_JoystickUpdate(SDL_Joystick * joystick) 634{ 635 SDL_JoystickDeviceItem *device = joystick->hwdata; 636 637 if (device == NULL) { 638 return; 639 } 640 641 if (device->accelerometer) { 642 IOS_AccelerometerUpdate(joystick); 643 } else if (device->controller) { 644 IOS_MFIJoystickUpdate(joystick); 645 } 646} 647 648static void 649IOS_JoystickClose(SDL_Joystick * joystick) 650{ 651 SDL_JoystickDeviceItem *device = joystick->hwdata; 652 653 if (device == NULL) { 654 return; 655 } 656 657 device->joystick = NULL; 658 659 @autoreleasepool { 660 if (device->accelerometer) { 661#if !TARGET_OS_TV 662 [motionManager stopAccelerometerUpdates]; 663#endif /* !TARGET_OS_TV */ 664 } else if (device->controller) { 665#ifdef SDL_JOYSTICK_MFI 666 GCController *controller = device->controller; 667 controller.controllerPausedHandler = nil; 668 controller.playerIndex = -1; 669#endif 670 } 671 } 672 if (device->remote) { 673 --SDL_AppleTVRemoteOpenedAsJoystick; 674 } 675} 676 677static void 678IOS_JoystickQuit(void) 679{ 680 @autoreleasepool { 681#ifdef SDL_JOYSTICK_MFI 682 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 683 684 if (connectObserver) { 685 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil]; 686 connectObserver = nil; 687 } 688 689 if (disconnectObserver) { 690 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil]; 691 disconnectObserver = nil; 692 } 693 694#if TARGET_OS_TV 695 SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, 696 SDL_AppleTVRemoteRotationHintChanged, NULL); 697#endif /* TARGET_OS_TV */ 698#endif /* SDL_JOYSTICK_MFI */ 699 700 while (deviceList != NULL) { 701 IOS_RemoveJoystickDevice(deviceList); 702 } 703 704#if !TARGET_OS_TV 705 motionManager = nil; 706#endif /* !TARGET_OS_TV */ 707 } 708 709 numjoysticks = 0; 710} 711 712SDL_JoystickDriver SDL_IOS_JoystickDriver = 713{ 714 IOS_JoystickInit, 715 IOS_JoystickGetCount, 716 IOS_JoystickDetect, 717 IOS_JoystickGetDeviceName, 718 IOS_JoystickGetDeviceGUID, 719 IOS_JoystickGetDeviceInstanceID, 720 IOS_JoystickOpen, 721 IOS_JoystickRumble, 722 IOS_JoystickUpdate, 723 IOS_JoystickClose, 724 IOS_JoystickQuit, 725}; 726 727/* vi: set ts=4 sw=4 expandtab: */ 728[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.