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 
49 static id connectObserver = nil;
50 static 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
77 static const char *accelerometerName = "iOS Accelerometer";
78 static CMMotionManager *motionManager = nil;
79 #endif /* !TARGET_OS_TV */
80 
82 
83 static int numjoysticks = 0;
85 
87 GetDeviceForIndex(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 
103 static 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 
258 static void
259 IOS_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
289  SDL_free(device);
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) {
305  deviceList = device;
306  } else {
307  SDL_JoystickDeviceItem *lastdevice = deviceList;
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 
319 static SDL_JoystickDeviceItem *
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 
365  SDL_PrivateJoystickRemoved(device->instance_id);
366 
367  SDL_free(device->name);
368  SDL_free(device);
369 
370  return next;
371 }
372 
373 #if TARGET_OS_TV
374 static void SDLCALL
375 SDL_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 
389 static 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 
445 static int
447 {
448  return numjoysticks;
449 }
450 
451 static void
453 {
454 }
455 
456 static const char *
457 IOS_JoystickGetDeviceName(int device_index)
458 {
460  return device ? device->name : "Unknown";
461 }
462 
463 static int
465 {
467  return device ? (int)device->controller.playerIndex : -1;
468 }
469 
470 static SDL_JoystickGUID
471 IOS_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 
483 static SDL_JoystickID
485 {
487  return device ? device->instance_id : -1;
488 }
489 
490 static int
491 IOS_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 
539 static void
540 IOS_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
584 static Uint8
585 IOS_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 
609 static void
610 IOS_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 
782 static int
783 IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
784 {
785  return SDL_Unsupported();
786 }
787 
788 static void
789 IOS_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 
804 static void
805 IOS_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 
833 static 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: */
SDL_zero
#define SDL_zero(x)
Definition: SDL_stdinc.h:416
IOS_JoystickGetDeviceInstanceID
static SDL_JoystickID IOS_JoystickGetDeviceInstanceID(int device_index)
Definition: SDL_sysjoystick.m:484
SDL_events.h
IOS_AddMFIJoystickDevice
static void IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
Definition: SDL_sysjoystick.m:104
SDL_HINT_TV_REMOTE_AS_JOYSTICK
#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
IOS_JoystickRumble
static int IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
Definition: SDL_sysjoystick.m:783
numjoysticks
static int numjoysticks
Definition: SDL_sysjoystick.m:83
SDL_sysjoystick_c.h
SDL_HAT_CENTERED
#define SDL_HAT_CENTERED
Definition: SDL_joystick.h:329
NULL
#define NULL
Definition: begin_code.h:167
SDL_HAT_DOWN
#define SDL_HAT_DOWN
Definition: SDL_joystick.h:332
SDL_joystick.h
GetDeviceForIndex
static SDL_JoystickDeviceItem * GetDeviceForIndex(int device_index)
Definition: SDL_sysjoystick.m:87
IOS_JoystickGetDeviceGUID
static SDL_JoystickGUID IOS_JoystickGetDeviceGUID(int device_index)
Definition: SDL_sysjoystick.m:471
IOS_MFIJoystickUpdate
static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
Definition: SDL_sysjoystick.m:610
IOS_AccelerometerUpdate
static void IOS_AccelerometerUpdate(SDL_Joystick *joystick)
Definition: SDL_sysjoystick.m:540
SDLCALL
#define SDLCALL
Definition: SDL_internal.h:49
SDL_PrivateJoystickAdded
void SDL_PrivateJoystickAdded(SDL_JoystickID device_instance)
Definition: SDL_joystick.c:755
IOS_JoystickDetect
static void IOS_JoystickDetect(void)
Definition: SDL_sysjoystick.m:452
SDL_CONTROLLER_BUTTON_RIGHTSTICK
Definition: SDL_gamecontroller.h:327
deviceList
static SDL_JoystickDeviceItem * deviceList
Definition: SDL_sysjoystick.m:81
SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION
#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
SDL_GetNextJoystickInstanceID
SDL_JoystickID SDL_GetNextJoystickInstanceID()
Definition: SDL_joystick.c:163
IOS_JoystickClose
static void IOS_JoystickClose(SDL_Joystick *joystick)
Definition: SDL_sysjoystick.m:805
SDL_RELEASED
#define SDL_RELEASED
Definition: SDL_events.h:49
c
const GLubyte * c
Definition: SDL_opengl_glext.h:11093
SDL_SwapLE16
#define SDL_SwapLE16(X)
Definition: SDL_endian.h:232
IOS_JoystickOpen
static int IOS_JoystickOpen(SDL_Joystick *joystick, int device_index)
Definition: SDL_sysjoystick.m:491
SDL_CONTROLLER_BUTTON_B
Definition: SDL_gamecontroller.h:320
SDL_PrivateJoystickRemoved
void SDL_PrivateJoystickRemoved(SDL_JoystickID device_instance)
Definition: SDL_joystick.c:805
SDL_PrivateJoystickAxis
int SDL_PrivateJoystickAxis(SDL_Joystick *joystick, Uint8 axis, Sint16 value)
Definition: SDL_joystick.c:833
SDL_CONTROLLER_BUTTON_BACK
Definition: SDL_gamecontroller.h:323
SDL_CONTROLLER_BUTTON_LEFTSHOULDER
Definition: SDL_gamecontroller.h:328
SDL_PRESSED
#define SDL_PRESSED
Definition: SDL_events.h:50
Sint16
int16_t Sint16
Definition: SDL_stdinc.h:185
joystick_hwdata::next
struct joystick_hwdata * next
Definition: SDL_sysjoystick_c.h:51
SDL_HINT_ACCELEROMETER_AS_JOYSTICK
#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
SDL_memcpy
#define SDL_memcpy
Definition: SDL_dynapi_overrides.h:387
Uint8
uint8_t Uint8
Definition: SDL_stdinc.h:179
SDL_GetHintBoolean
#define SDL_GetHintBoolean
Definition: SDL_dynapi_overrides.h:608
SDL_config_iphoneos.h
SDL_free
#define SDL_free
Definition: SDL_dynapi_overrides.h:377
IOS_JoystickGetDeviceName
static const char * IOS_JoystickGetDeviceName(int device_index)
Definition: SDL_sysjoystick.m:457
SDL_max
#define SDL_max(x, y)
Definition: SDL_stdinc.h:407
in
GLuint in
Definition: SDL_opengl_glext.h:7940
IOS_JoystickQuit
static void IOS_JoystickQuit(void)
Definition: SDL_sysjoystick.m:834
SDL_FALSE
Definition: SDL_stdinc.h:163
name
GLuint const GLchar * name
Definition: SDL_opengl_glext.h:660
SDL_IPHONE_MAX_GFORCE
#define SDL_IPHONE_MAX_GFORCE
Definition: SDL_config_iphoneos.h:196
SDL_PrivateJoystickButton
int SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
Definition: SDL_joystick.c:966
SDL_HAT_LEFT
#define SDL_HAT_LEFT
Definition: SDL_joystick.h:333
SDL_assert.h
SDL_min
#define SDL_min(x, y)
Definition: SDL_stdinc.h:406
SDL_AppleTVRemoteOpenedAsJoystick
int SDL_AppleTVRemoteOpenedAsJoystick
Definition: SDL_sysjoystick.m:84
SDL_CONTROLLER_BUTTON_START
Definition: SDL_gamecontroller.h:325
IOS_JoystickInit
static int IOS_JoystickInit(void)
Definition: SDL_sysjoystick.m:390
SDL_JoystickDriver
Definition: SDL_sysjoystick.h:92
SDL_PrivateJoystickHat
int SDL_PrivateJoystickHat(SDL_Joystick *joystick, Uint8 hat, Uint8 value)
Definition: SDL_joystick.c:890
SDL_arraysize
#define SDL_arraysize(array)
Definition: SDL_stdinc.h:115
SDL_calloc
#define SDL_calloc
Definition: SDL_dynapi_overrides.h:375
SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
Definition: SDL_gamecontroller.h:329
SDL_bool
SDL_bool
Definition: SDL_stdinc.h:161
SDL_CONTROLLER_BUTTON_Y
Definition: SDL_gamecontroller.h:322
sort_controllers.controllers
list controllers
Definition: sort_controllers.py:12
motionManager
static CMMotionManager * motionManager
Definition: SDL_sysjoystick.m:78
IOS_JoystickGetCount
static int IOS_JoystickGetCount(void)
Definition: SDL_sysjoystick.m:446
SDL_AddHintCallback
#define SDL_AddHintCallback
Definition: SDL_dynapi_overrides.h:192
SDL_HAT_RIGHT
#define SDL_HAT_RIGHT
Definition: SDL_joystick.h:331
SDL_SetError
#define SDL_SetError
Definition: SDL_dynapi_overrides.h:30
IOS_JoystickGetDevicePlayerIndex
static int IOS_JoystickGetDevicePlayerIndex(int device_index)
Definition: SDL_sysjoystick.m:464
SDL_TRUE
Definition: SDL_stdinc.h:164
SDL_stdinc.h
SDL_hints.h
IOS_JoystickUpdate
static void IOS_JoystickUpdate(SDL_Joystick *joystick)
Definition: SDL_sysjoystick.m:789
SDL_CONTROLLER_BUTTON_LEFTSTICK
Definition: SDL_gamecontroller.h:326
SDL_strdup
#define SDL_strdup
Definition: SDL_dynapi_overrides.h:397
SDL_strlen
#define SDL_strlen
Definition: SDL_dynapi_overrides.h:393
Uint32
uint32_t Uint32
Definition: SDL_stdinc.h:203
SDL_HAT_UP
#define SDL_HAT_UP
Definition: SDL_joystick.h:330
SDL_HARDWARE_BUS_BLUETOOTH
#define SDL_HARDWARE_BUS_BLUETOOTH
Definition: SDL_sysjoystick.h:87
IOS_RemoveJoystickDevice
static SDL_JoystickDeviceItem * IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
Definition: SDL_sysjoystick.m:320
SDL_IOS_JoystickDriver
SDL_JoystickDriver SDL_IOS_JoystickDriver
Definition: SDL_sysjoystick.m:868
SDL_DelHintCallback
#define SDL_DelHintCallback
Definition: SDL_dynapi_overrides.h:193
SDL_Unsupported
#define SDL_Unsupported()
Definition: SDL_error.h:53
SDL_CONTROLLER_BUTTON_X
Definition: SDL_gamecontroller.h:321
device
static SDL_AudioDeviceID device
Definition: loopwave.c:37
SDL_JoystickGUID
Definition: SDL_joystick.h:70
SDL_CONTROLLER_BUTTON_A
Definition: SDL_gamecontroller.h:319
i
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
accelerometerName
static const char * accelerometerName
Definition: SDL_sysjoystick.m:77
IOS_AddJoystickDevice
static void IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
Definition: SDL_sysjoystick.m:259
Uint16
uint16_t Uint16
Definition: SDL_stdinc.h:191
joystick_hwdata
Definition: SDL_sysjoystick_c.h:46
SDL_JoystickID
Sint32 SDL_JoystickID
Definition: SDL_joystick.h:81