SDL 2.0
SDL_sysjoystick.m
Go to the documentation of this file.
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2019 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 "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
82
83static int numjoysticks = 0;
85
87GetDeviceForIndex(int device_index)
88{
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
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_strdup(name);
129
130 if (controller.extendedGamepad) {
131 GCExtendedGamepad *gamepad = controller.extendedGamepad;
132 int nbuttons = 0;
133
134 /* These buttons are part of the original MFi spec */
135 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
136 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
137 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
138 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
139 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
140 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
141 nbuttons += 6;
142
143 /* These buttons are available on some newer controllers */
144#pragma clang diagnostic push
145#pragma clang diagnostic ignored "-Wunguarded-availability-new"
146 if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
147 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK);
148 ++nbuttons;
149 }
150 if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
151 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK);
152 ++nbuttons;
153 }
154 if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
155 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK);
156 ++nbuttons;
157 }
158 if ([gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu) {
159 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
160 ++nbuttons;
161 } else {
162 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
163 ++nbuttons;
164 device->uses_pause_handler = SDL_TRUE;
165 }
166#pragma clang diagnostic pop
167
168 if ([controller.vendorName containsString: @"Xbox"]) {
169 vendor = VENDOR_MICROSOFT;
170 product = 0x02E0; /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */
171 } else if ([controller.vendorName containsString: @"DUALSHOCK"]) {
172 vendor = VENDOR_SONY;
173 product = 0x09CC; /* Assume DS4 Slim unless/until GCController flows VID/PID */
174 } else {
175 vendor = VENDOR_APPLE;
176 product = 1;
177 subtype = 1;
178 }
179
180 device->naxes = 6; /* 2 thumbsticks and 2 triggers */
181 device->nhats = 1; /* d-pad */
182 device->nbuttons = nbuttons;
183
184 } else if (controller.gamepad) {
185 int nbuttons = 0;
186
187 /* These buttons are part of the original MFi spec */
188 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
189 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
190 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
191 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
192 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
193 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
194 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
195 nbuttons += 7;
196 device->uses_pause_handler = SDL_TRUE;
197
198 vendor = VENDOR_APPLE;
199 product = 2;
200 subtype = 2;
201 device->naxes = 0; /* no traditional analog inputs */
202 device->nhats = 1; /* d-pad */
203 device->nbuttons = nbuttons;
204 }
205#if TARGET_OS_TV
206 else if (controller.microGamepad) {
207 GCMicroGamepad *gamepad = controller.microGamepad;
208 int nbuttons = 0;
209
210 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
211 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */
212 nbuttons += 2;
213
214 if ([gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu) {
215 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
216 ++nbuttons;
217 } else {
218 device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
219 ++nbuttons;
220 device->uses_pause_handler = SDL_TRUE;
221 }
222
223 vendor = VENDOR_APPLE;
224 product = 3;
225 subtype = 3;
226 device->naxes = 2; /* treat the touch surface as two axes */
227 device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
228 device->nbuttons = nbuttons;
229
230 controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
231 }
232#endif /* TARGET_OS_TV */
233
234 /* We only need 16 bits for each of these; space them out to fill 128. */
235 /* Byteswap so devices get same GUID on little/big endian platforms. */
237 *guid16++ = 0;
238 *guid16++ = SDL_SwapLE16(vendor);
239 *guid16++ = 0;
240 *guid16++ = SDL_SwapLE16(product);
241 *guid16++ = 0;
242
243 *guid16++ = SDL_SwapLE16(device->button_mask);
244
245 if (subtype != 0) {
246 /* Note that this is an MFI controller and what subtype it is */
247 device->guid.data[14] = 'm';
248 device->guid.data[15] = subtype;
249 }
250
251 /* This will be set when the first button press of the controller is
252 * detected. */
253 controller.playerIndex = -1;
254
255#endif /* SDL_JOYSTICK_MFI */
256}
257
258static void
259IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
260{
262
263#if TARGET_OS_TV
265 /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */
266 if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) {
267 return;
268 }
269 }
270#endif
271
272 while (device != NULL) {
273 if (device->controller == controller) {
274 return;
275 }
276 device = device->next;
277 }
278
280 if (device == NULL) {
281 return;
282 }
283
284 device->accelerometer = accelerometer;
285 device->instance_id = SDL_GetNextJoystickInstanceID();
286
287 if (accelerometer) {
288#if TARGET_OS_TV
290 return;
291#else
293 device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
294 device->nhats = 0;
295 device->nbuttons = 0;
296
297 /* Use the accelerometer name as a GUID. */
298 SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
299#endif /* TARGET_OS_TV */
300 } else if (controller) {
301 IOS_AddMFIJoystickDevice(device, controller);
302 }
303
304 if (deviceList == NULL) {
306 } else {
308 while (lastdevice->next != NULL) {
309 lastdevice = lastdevice->next;
310 }
311 lastdevice->next = device;
312 }
313
314 ++numjoysticks;
315
316 SDL_PrivateJoystickAdded(device->instance_id);
317}
318
321{
325
326 if (device == NULL) {
327 return NULL;
328 }
329
330 next = device->next;
331
332 while (item != NULL) {
333 if (item == device) {
334 break;
335 }
336 prev = item;
337 item = item->next;
338 }
339
340 /* Unlink the device item from the device list. */
341 if (prev) {
342 prev->next = device->next;
343 } else if (device == deviceList) {
344 deviceList = device->next;
345 }
346
347 if (device->joystick) {
348 device->joystick->hwdata = NULL;
349 }
350
351#ifdef SDL_JOYSTICK_MFI
352 @autoreleasepool {
353 if (device->controller) {
354 /* The controller was explicitly retained in the struct, so it
355 * should be explicitly released before freeing the struct. */
356 GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
357 controller.controllerPausedHandler = nil;
358 device->controller = nil;
359 }
360 }
361#endif /* SDL_JOYSTICK_MFI */
362
363 --numjoysticks;
364
366
367 SDL_free(device->name);
369
370 return next;
371}
372
373#if TARGET_OS_TV
374static void SDLCALL
375SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
376{
377 BOOL allowRotation = newValue != NULL && *newValue != '0';
378
379 @autoreleasepool {
380 for (GCController *controller in [GCController controllers]) {
381 if (controller.microGamepad) {
382 controller.microGamepad.allowsRotation = allowRotation;
383 }
384 }
385 }
386}
387#endif /* TARGET_OS_TV */
388
389static int
391{
392 @autoreleasepool {
393 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
394
395#if !TARGET_OS_TV
397 /* Default behavior, accelerometer as joystick */
399 }
400#endif /* !TARGET_OS_TV */
401
402#ifdef SDL_JOYSTICK_MFI
403 /* GameController.framework was added in iOS 7. */
404 if (![GCController class]) {
405 return 0;
406 }
407
408 for (GCController *controller in [GCController controllers]) {
409 IOS_AddJoystickDevice(controller, SDL_FALSE);
410 }
411
412#if TARGET_OS_TV
414 SDL_AppleTVRemoteRotationHintChanged, NULL);
415#endif /* TARGET_OS_TV */
416
417 connectObserver = [center addObserverForName:GCControllerDidConnectNotification
418 object:nil
419 queue:nil
420 usingBlock:^(NSNotification *note) {
421 GCController *controller = note.object;
422 IOS_AddJoystickDevice(controller, SDL_FALSE);
423 }];
424
425 disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
426 object:nil
427 queue:nil
428 usingBlock:^(NSNotification *note) {
429 GCController *controller = note.object;
430 SDL_JoystickDeviceItem *device = deviceList;
431 while (device != NULL) {
432 if (device->controller == controller) {
433 IOS_RemoveJoystickDevice(device);
434 break;
435 }
436 device = device->next;
437 }
438 }];
439#endif /* SDL_JOYSTICK_MFI */
440 }
441
442 return 0;
443}
444
445static int
447{
448 return numjoysticks;
449}
450
451static void
453{
454}
455
456static const char *
458{
460 return device ? device->name : "Unknown";
461}
462
463static int
465{
467 return device ? (int)device->controller.playerIndex : -1;
468}
469
470static SDL_JoystickGUID
471IOS_JoystickGetDeviceGUID( int device_index )
472{
474 SDL_JoystickGUID guid;
475 if (device) {
476 guid = device->guid;
477 } else {
478 SDL_zero(guid);
479 }
480 return guid;
481}
482
483static SDL_JoystickID
485{
487 return device ? device->instance_id : -1;
488}
489
490static int
491IOS_JoystickOpen(SDL_Joystick * joystick, int device_index)
492{
494 if (device == NULL) {
495 return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
496 }
497
498 joystick->hwdata = device;
499 joystick->instance_id = device->instance_id;
500
501 joystick->naxes = device->naxes;
502 joystick->nhats = device->nhats;
503 joystick->nbuttons = device->nbuttons;
504 joystick->nballs = 0;
505
506 device->joystick = joystick;
507
508 @autoreleasepool {
509 if (device->accelerometer) {
510#if !TARGET_OS_TV
511 if (motionManager == nil) {
512 motionManager = [[CMMotionManager alloc] init];
513 }
514
515 /* Shorter times between updates can significantly increase CPU usage. */
516 motionManager.accelerometerUpdateInterval = 0.1;
517 [motionManager startAccelerometerUpdates];
518#endif /* !TARGET_OS_TV */
519 } else {
520#ifdef SDL_JOYSTICK_MFI
521 if (device->uses_pause_handler) {
522 GCController *controller = device->controller;
523 controller.controllerPausedHandler = ^(GCController *c) {
524 if (joystick->hwdata) {
525 ++joystick->hwdata->num_pause_presses;
526 }
527 };
528 }
529#endif /* SDL_JOYSTICK_MFI */
530 }
531 }
532 if (device->remote) {
534 }
535
536 return 0;
537}
538
539static void
540IOS_AccelerometerUpdate(SDL_Joystick * joystick)
541{
542#if !TARGET_OS_TV
543 const float maxgforce = SDL_IPHONE_MAX_GFORCE;
544 const SInt16 maxsint16 = 0x7FFF;
545 CMAcceleration accel;
546
547 @autoreleasepool {
548 if (!motionManager.isAccelerometerActive) {
549 return;
550 }
551
552 accel = motionManager.accelerometerData.acceleration;
553 }
554
555 /*
556 Convert accelerometer data from floating point to Sint16, which is what
557 the joystick system expects.
558
559 To do the conversion, the data is first clamped onto the interval
560 [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
561 by MAX_SINT16 so that it is mapped to the full range of an Sint16.
562
563 You can customize the clamped range of this function by modifying the
564 SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
565
566 Once converted to Sint16, the accelerometer data no longer has coherent
567 units. You can convert the data back to units of g-force by multiplying
568 it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
569 */
570
571 /* clamp the data */
572 accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
573 accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
574 accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
575
576 /* pass in data mapped to range of SInt16 */
577 SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16);
578 SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
579 SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16);
580#endif /* !TARGET_OS_TV */
581}
582
583#ifdef SDL_JOYSTICK_MFI
584static Uint8
585IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
586{
587 Uint8 hat = 0;
588
589 if (dpad.up.isPressed) {
590 hat |= SDL_HAT_UP;
591 } else if (dpad.down.isPressed) {
592 hat |= SDL_HAT_DOWN;
593 }
594
595 if (dpad.left.isPressed) {
596 hat |= SDL_HAT_LEFT;
597 } else if (dpad.right.isPressed) {
598 hat |= SDL_HAT_RIGHT;
599 }
600
601 if (hat == 0) {
602 return SDL_HAT_CENTERED;
603 }
604
605 return hat;
606}
607#endif
608
609static void
610IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
611{
612#if SDL_JOYSTICK_MFI
613 @autoreleasepool {
614 GCController *controller = joystick->hwdata->controller;
615 Uint8 hatstate = SDL_HAT_CENTERED;
616 int i;
617 int updateplayerindex = 0;
618 int pause_button_index = 0;
619
620 if (controller.extendedGamepad) {
621 GCExtendedGamepad *gamepad = controller.extendedGamepad;
622
623 /* Axis order matches the XInput Windows mappings. */
624 Sint16 axes[] = {
625 (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
626 (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
627 (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
628 (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
629 (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
630 (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
631 };
632
633 /* Button order matches the XInput Windows mappings. */
634 Uint8 buttons[joystick->nbuttons];
635 int button_count = 0;
636
637 /* These buttons are part of the original MFi spec */
638 buttons[button_count++] = gamepad.buttonA.isPressed;
639 buttons[button_count++] = gamepad.buttonB.isPressed;
640 buttons[button_count++] = gamepad.buttonX.isPressed;
641 buttons[button_count++] = gamepad.buttonY.isPressed;
642 buttons[button_count++] = gamepad.leftShoulder.isPressed;
643 buttons[button_count++] = gamepad.rightShoulder.isPressed;
644
645 /* These buttons are available on some newer controllers */
646#pragma clang diagnostic push
647#pragma clang diagnostic ignored "-Wunguarded-availability-new"
648 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) {
649 buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
650 }
651 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) {
652 buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
653 }
654 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) {
655 buttons[button_count++] = gamepad.buttonOptions.isPressed;
656 }
657 /* This must be the last button, so we can optionally handle it with pause_button_index below */
658 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
659 if (joystick->hwdata->uses_pause_handler) {
660 pause_button_index = button_count;
661 buttons[button_count++] = joystick->delayed_guide_button;
662 } else {
663 buttons[button_count++] = gamepad.buttonMenu.isPressed;
664 }
665 }
666#pragma clang diagnostic pop
667
668 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
669
670 for (i = 0; i < SDL_arraysize(axes); i++) {
671 /* The triggers (axes 2 and 5) are resting at -32768 but SDL
672 * initializes its values to 0. We only want to make sure the
673 * player index is up to date if the user actually moves an axis. */
674 if ((i != 2 && i != 5) || axes[i] != -32768) {
675 updateplayerindex |= (joystick->axes[i].value != axes[i]);
676 }
677 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
678 }
679
680 for (i = 0; i < button_count; i++) {
681 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
682 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
683 }
684 } else if (controller.gamepad) {
685 GCGamepad *gamepad = controller.gamepad;
686
687 /* Button order matches the XInput Windows mappings. */
688 Uint8 buttons[joystick->nbuttons];
689 int button_count = 0;
690 buttons[button_count++] = gamepad.buttonA.isPressed;
691 buttons[button_count++] = gamepad.buttonB.isPressed;
692 buttons[button_count++] = gamepad.buttonX.isPressed;
693 buttons[button_count++] = gamepad.buttonY.isPressed;
694 buttons[button_count++] = gamepad.leftShoulder.isPressed;
695 buttons[button_count++] = gamepad.rightShoulder.isPressed;
696 pause_button_index = button_count;
697 buttons[button_count++] = joystick->delayed_guide_button;
698
699 hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
700
701 for (i = 0; i < button_count; i++) {
702 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
703 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
704 }
705 }
706#if TARGET_OS_TV
707 else if (controller.microGamepad) {
708 GCMicroGamepad *gamepad = controller.microGamepad;
709
710 Sint16 axes[] = {
711 (Sint16) (gamepad.dpad.xAxis.value * 32767),
712 (Sint16) (gamepad.dpad.yAxis.value * -32767),
713 };
714
715 for (i = 0; i < SDL_arraysize(axes); i++) {
716 updateplayerindex |= (joystick->axes[i].value != axes[i]);
717 SDL_PrivateJoystickAxis(joystick, i, axes[i]);
718 }
719
720 Uint8 buttons[joystick->nbuttons];
721 int button_count = 0;
722 buttons[button_count++] = gamepad.buttonA.isPressed;
723 buttons[button_count++] = gamepad.buttonX.isPressed;
724#pragma clang diagnostic push
725#pragma clang diagnostic ignored "-Wunguarded-availability-new"
726 /* This must be the last button, so we can optionally handle it with pause_button_index below */
727 if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
728 if (joystick->hwdata->uses_pause_handler) {
729 pause_button_index = button_count;
730 buttons[button_count++] = joystick->delayed_guide_button;
731 } else {
732 buttons[button_count++] = gamepad.buttonMenu.isPressed;
733 }
734 }
735#pragma clang diagnostic pop
736
737 for (i = 0; i < button_count; i++) {
738 updateplayerindex |= (joystick->buttons[i] != buttons[i]);
739 SDL_PrivateJoystickButton(joystick, i, buttons[i]);
740 }
741 }
742#endif /* TARGET_OS_TV */
743
744 if (joystick->nhats > 0) {
745 updateplayerindex |= (joystick->hats[0] != hatstate);
746 SDL_PrivateJoystickHat(joystick, 0, hatstate);
747 }
748
749 if (joystick->hwdata->uses_pause_handler) {
750 for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
751 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED);
752 SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED);
753 updateplayerindex = YES;
754 }
755 joystick->hwdata->num_pause_presses = 0;
756 }
757
758 if (updateplayerindex && controller.playerIndex == -1) {
759 BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
760
761 /* Find the player index of all other connected controllers. */
762 for (GCController *c in [GCController controllers]) {
763 if (c != controller && c.playerIndex >= 0) {
764 usedPlayerIndexSlots[c.playerIndex] = YES;
765 }
766 }
767
768 /* Set this controller's player index to the first unused index.
769 * FIXME: This logic isn't great... but SDL doesn't expose this
770 * concept in its external API, so we don't have much to go on. */
771 for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) {
772 if (!usedPlayerIndexSlots[i]) {
773 controller.playerIndex = i;
774 break;
775 }
776 }
777 }
778 }
779#endif /* SDL_JOYSTICK_MFI */
780}
781
782static int
783IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
784{
785 return SDL_Unsupported();
786}
787
788static void
789IOS_JoystickUpdate(SDL_Joystick * joystick)
790{
791 SDL_JoystickDeviceItem *device = joystick->hwdata;
792
793 if (device == NULL) {
794 return;
795 }
796
797 if (device->accelerometer) {
798 IOS_AccelerometerUpdate(joystick);
799 } else if (device->controller) {
800 IOS_MFIJoystickUpdate(joystick);
801 }
802}
803
804static void
805IOS_JoystickClose(SDL_Joystick * joystick)
806{
807 SDL_JoystickDeviceItem *device = joystick->hwdata;
808
809 if (device == NULL) {
810 return;
811 }
812
813 device->joystick = NULL;
814
815 @autoreleasepool {
816 if (device->accelerometer) {
817#if !TARGET_OS_TV
818 [motionManager stopAccelerometerUpdates];
819#endif /* !TARGET_OS_TV */
820 } else if (device->controller) {
821#ifdef SDL_JOYSTICK_MFI
822 GCController *controller = device->controller;
823 controller.controllerPausedHandler = nil;
824 controller.playerIndex = -1;
825#endif
826 }
827 }
828 if (device->remote) {
830 }
831}
832
833static void
835{
836 @autoreleasepool {
837#ifdef SDL_JOYSTICK_MFI
838 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
839
840 if (connectObserver) {
841 [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
842 connectObserver = nil;
843 }
844
845 if (disconnectObserver) {
846 [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
847 disconnectObserver = nil;
848 }
849
850#if TARGET_OS_TV
852 SDL_AppleTVRemoteRotationHintChanged, NULL);
853#endif /* TARGET_OS_TV */
854#endif /* SDL_JOYSTICK_MFI */
855
856 while (deviceList != NULL) {
858 }
859
860#if !TARGET_OS_TV
861 motionManager = nil;
862#endif /* !TARGET_OS_TV */
863 }
864
865 numjoysticks = 0;
866}
867
869{
882};
883
884/* vi: set ts=4 sw=4 expandtab: */
#define SDL_IPHONE_MAX_GFORCE
#define SDL_SetError
#define SDL_DelHintCallback
#define SDL_strlen
#define SDL_free
#define SDL_strdup
#define SDL_GetHintBoolean
#define SDL_memcpy
#define SDL_AddHintCallback
#define SDL_calloc
#define SDL_SwapLE16(X)
Definition: SDL_endian.h:232
#define SDL_Unsupported()
Definition: SDL_error.h:53
#define SDL_RELEASED
Definition: SDL_events.h:49
#define SDL_PRESSED
Definition: SDL_events.h:50
@ SDL_CONTROLLER_BUTTON_B
@ SDL_CONTROLLER_BUTTON_BACK
@ SDL_CONTROLLER_BUTTON_LEFTSTICK
@ SDL_CONTROLLER_BUTTON_START
@ SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
@ SDL_CONTROLLER_BUTTON_LEFTSHOULDER
@ SDL_CONTROLLER_BUTTON_X
@ SDL_CONTROLLER_BUTTON_RIGHTSTICK
@ SDL_CONTROLLER_BUTTON_Y
@ SDL_CONTROLLER_BUTTON_A
#define SDL_HINT_ACCELEROMETER_AS_JOYSTICK
A variable controlling whether the Android / iOS built-in accelerometer should be listed as a joystic...
Definition: SDL_hints.h:409
#define SDL_HINT_TV_REMOTE_AS_JOYSTICK
A variable controlling whether the Android / tvOS remotes should be listed as joystick devices,...
Definition: SDL_hints.h:419
#define SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION
A variable controlling whether the Apple TV remote's joystick axes will automatically match the rotat...
Definition: SDL_hints.h:388
#define SDLCALL
Definition: SDL_internal.h:49
int SDL_PrivateJoystickHat(SDL_Joystick *joystick, Uint8 hat, Uint8 value)
Definition: SDL_joystick.c:890
void SDL_PrivateJoystickRemoved(SDL_JoystickID device_instance)
Definition: SDL_joystick.c:805
int SDL_PrivateJoystickAxis(SDL_Joystick *joystick, Uint8 axis, Sint16 value)
Definition: SDL_joystick.c:833
void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance)
Definition: SDL_joystick.c:755
SDL_JoystickID SDL_GetNextJoystickInstanceID()
Definition: SDL_joystick.c:163
int SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
Definition: SDL_joystick.c:966
#define SDL_HAT_LEFT
Definition: SDL_joystick.h:333
#define SDL_HAT_RIGHT
Definition: SDL_joystick.h:331
Sint32 SDL_JoystickID
Definition: SDL_joystick.h:81
#define SDL_HAT_DOWN
Definition: SDL_joystick.h:332
#define SDL_HAT_UP
Definition: SDL_joystick.h:330
#define SDL_HAT_CENTERED
Definition: SDL_joystick.h:329
const GLubyte * c
GLuint in
GLuint const GLchar * name
#define SDL_zero(x)
Definition: SDL_stdinc.h:416
SDL_bool
Definition: SDL_stdinc.h:162
@ SDL_TRUE
Definition: SDL_stdinc.h:164
@ SDL_FALSE
Definition: SDL_stdinc.h:163
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:115
uint32_t Uint32
Definition: SDL_stdinc.h:203
int16_t Sint16
Definition: SDL_stdinc.h:185
#define SDL_min(x, y)
Definition: SDL_stdinc.h:406
uint16_t Uint16
Definition: SDL_stdinc.h:191
#define SDL_max(x, y)
Definition: SDL_stdinc.h:407
uint8_t Uint8
Definition: SDL_stdinc.h:179
#define SDL_HARDWARE_BUS_BLUETOOTH
static int IOS_JoystickGetDevicePlayerIndex(int device_index)
static void IOS_JoystickDetect(void)
static SDL_JoystickDeviceItem * deviceList
static void IOS_JoystickClose(SDL_Joystick *joystick)
static void IOS_AccelerometerUpdate(SDL_Joystick *joystick)
static void IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
static SDL_JoystickDeviceItem * GetDeviceForIndex(int device_index)
SDL_JoystickDriver SDL_IOS_JoystickDriver
static SDL_JoystickGUID IOS_JoystickGetDeviceGUID(int device_index)
static void IOS_JoystickUpdate(SDL_Joystick *joystick)
static int IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
static void IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
static const char * IOS_JoystickGetDeviceName(int device_index)
static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
static SDL_JoystickDeviceItem * IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
static int IOS_JoystickGetCount(void)
static const char * accelerometerName
static CMMotionManager * motionManager
static void IOS_JoystickQuit(void)
static int IOS_JoystickInit(void)
static int IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
static SDL_JoystickID IOS_JoystickGetDeviceInstanceID(int device_index)
int SDL_AppleTVRemoteOpenedAsJoystick
static int numjoysticks
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
#define NULL
Definition: begin_code.h:167
static SDL_AudioDeviceID device
Definition: loopwave.c:37
struct joystick_hwdata * next