SDL_sysjoystick.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "../../SDL_internal.h"
  19. /* This is the iOS implementation of the SDL joystick API */
  20. #include "SDL_sysjoystick_c.h"
  21. /* needed for SDL_IPHONE_MAX_GFORCE macro */
  22. #include "SDL_config_iphoneos.h"
  23. #include "SDL_joystick.h"
  24. #include "SDL_hints.h"
  25. #include "SDL_stdinc.h"
  26. #include "../SDL_sysjoystick.h"
  27. #include "../SDL_joystick_c.h"
  28. #import <CoreMotion/CoreMotion.h>
  29. #ifdef SDL_JOYSTICK_MFI
  30. #import <GameController/GameController.h>
  31. static id connectObserver = nil;
  32. static id disconnectObserver = nil;
  33. #endif /* SDL_JOYSTICK_MFI */
  34. static const char *accelerometerName = "iOS Accelerometer";
  35. static CMMotionManager *motionManager = nil;
  36. static SDL_JoystickDeviceItem *deviceList = NULL;
  37. static int numjoysticks = 0;
  38. static SDL_JoystickID instancecounter = 0;
  39. static SDL_JoystickDeviceItem *
  40. GetDeviceForIndex(int device_index)
  41. {
  42. SDL_JoystickDeviceItem *device = deviceList;
  43. int i = 0;
  44. while (i < device_index) {
  45. if (device == NULL) {
  46. return NULL;
  47. }
  48. device = device->next;
  49. i++;
  50. }
  51. return device;
  52. }
  53. static void
  54. SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
  55. {
  56. #ifdef SDL_JOYSTICK_MFI
  57. const char *name = NULL;
  58. /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
  59. * struct, and ARC doesn't work with structs. */
  60. device->controller = (__bridge GCController *) CFBridgingRetain(controller);
  61. if (controller.vendorName) {
  62. name = controller.vendorName.UTF8String;
  63. }
  64. if (!name) {
  65. name = "MFi Gamepad";
  66. }
  67. device->name = SDL_strdup(name);
  68. device->guid.data[0] = 'M';
  69. device->guid.data[1] = 'F';
  70. device->guid.data[2] = 'i';
  71. device->guid.data[3] = 'G';
  72. device->guid.data[4] = 'a';
  73. device->guid.data[5] = 'm';
  74. device->guid.data[6] = 'e';
  75. device->guid.data[7] = 'p';
  76. device->guid.data[8] = 'a';
  77. device->guid.data[9] = 'd';
  78. if (controller.extendedGamepad) {
  79. device->guid.data[10] = 1;
  80. } else if (controller.gamepad) {
  81. device->guid.data[10] = 2;
  82. }
  83. if (controller.extendedGamepad) {
  84. device->naxes = 6; /* 2 thumbsticks and 2 triggers */
  85. device->nhats = 1; /* d-pad */
  86. device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
  87. } else if (controller.gamepad) {
  88. device->naxes = 0; /* no traditional analog inputs */
  89. device->nhats = 1; /* d-pad */
  90. device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
  91. }
  92. /* TODO: Handle micro profiles on tvOS. */
  93. /* This will be set when the first button press of the controller is
  94. * detected. */
  95. controller.playerIndex = -1;
  96. #endif
  97. }
  98. static void
  99. SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
  100. {
  101. SDL_JoystickDeviceItem *device = deviceList;
  102. while (device != NULL) {
  103. if (device->controller == controller) {
  104. return;
  105. }
  106. device = device->next;
  107. }
  108. device = (SDL_JoystickDeviceItem *) SDL_malloc(sizeof(SDL_JoystickDeviceItem));
  109. if (device == NULL) {
  110. return;
  111. }
  112. SDL_zerop(device);
  113. device->accelerometer = accelerometer;
  114. device->instance_id = instancecounter++;
  115. if (accelerometer) {
  116. device->name = SDL_strdup(accelerometerName);
  117. device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
  118. device->nhats = 0;
  119. device->nbuttons = 0;
  120. /* Use the accelerometer name as a GUID. */
  121. SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
  122. } else if (controller) {
  123. SDL_SYS_AddMFIJoystickDevice(device, controller);
  124. }
  125. if (deviceList == NULL) {
  126. deviceList = device;
  127. } else {
  128. SDL_JoystickDeviceItem *lastdevice = deviceList;
  129. while (lastdevice->next != NULL) {
  130. lastdevice = lastdevice->next;
  131. }
  132. lastdevice->next = device;
  133. }
  134. ++numjoysticks;
  135. SDL_PrivateJoystickAdded(numjoysticks - 1);
  136. }
  137. static SDL_JoystickDeviceItem *
  138. SDL_SYS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
  139. {
  140. SDL_JoystickDeviceItem *prev = NULL;
  141. SDL_JoystickDeviceItem *next = NULL;
  142. SDL_JoystickDeviceItem *item = deviceList;
  143. if (device == NULL) {
  144. return NULL;
  145. }
  146. next = device->next;
  147. while (item != NULL) {
  148. if (item == device) {
  149. break;
  150. }
  151. prev = item;
  152. item = item->next;
  153. }
  154. /* Unlink the device item from the device list. */
  155. if (prev) {
  156. prev->next = device->next;
  157. } else if (device == deviceList) {
  158. deviceList = device->next;
  159. }
  160. if (device->joystick) {
  161. device->joystick->hwdata = NULL;
  162. }
  163. #ifdef SDL_JOYSTICK_MFI
  164. @autoreleasepool {
  165. if (device->controller) {
  166. /* The controller was explicitly retained in the struct, so it
  167. * should be explicitly released before freeing the struct. */
  168. GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
  169. controller.controllerPausedHandler = nil;
  170. device->controller = nil;
  171. }
  172. }
  173. #endif /* SDL_JOYSTICK_MFI */
  174. --numjoysticks;
  175. SDL_PrivateJoystickRemoved(device->instance_id);
  176. SDL_free(device->name);
  177. SDL_free(device);
  178. return next;
  179. }
  180. /* Function to scan the system for joysticks.
  181. * Joystick 0 should be the system default joystick.
  182. * It should return 0, or -1 on an unrecoverable fatal error.
  183. */
  184. int
  185. SDL_SYS_JoystickInit(void)
  186. {
  187. @autoreleasepool {
  188. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  189. const char *hint = SDL_GetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK);
  190. if (!hint || SDL_atoi(hint)) {
  191. /* Default behavior, accelerometer as joystick */
  192. SDL_SYS_AddJoystickDevice(nil, SDL_TRUE);
  193. }
  194. #ifdef SDL_JOYSTICK_MFI
  195. /* GameController.framework was added in iOS 7. */
  196. if (![GCController class]) {
  197. return numjoysticks;
  198. }
  199. for (GCController *controller in [GCController controllers]) {
  200. SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
  201. }
  202. connectObserver = [center addObserverForName:GCControllerDidConnectNotification
  203. object:nil
  204. queue:nil
  205. usingBlock:^(NSNotification *note) {
  206. GCController *controller = note.object;
  207. SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
  208. }];
  209. disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
  210. object:nil
  211. queue:nil
  212. usingBlock:^(NSNotification *note) {
  213. GCController *controller = note.object;
  214. SDL_JoystickDeviceItem *device = deviceList;
  215. while (device != NULL) {
  216. if (device->controller == controller) {
  217. SDL_SYS_RemoveJoystickDevice(device);
  218. break;
  219. }
  220. device = device->next;
  221. }
  222. }];
  223. #endif /* SDL_JOYSTICK_MFI */
  224. }
  225. return numjoysticks;
  226. }
  227. int SDL_SYS_NumJoysticks()
  228. {
  229. return numjoysticks;
  230. }
  231. void SDL_SYS_JoystickDetect()
  232. {
  233. }
  234. /* Function to get the device-dependent name of a joystick */
  235. const char *
  236. SDL_SYS_JoystickNameForDeviceIndex(int device_index)
  237. {
  238. SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
  239. return device ? device->name : "Unknown";
  240. }
  241. /* Function to perform the mapping from device index to the instance id for this index */
  242. SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
  243. {
  244. SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
  245. return device ? device->instance_id : 0;
  246. }
  247. /* Function to open a joystick for use.
  248. The joystick to open is specified by the device index.
  249. This should fill the nbuttons and naxes fields of the joystick structure.
  250. It returns 0, or -1 if there is an error.
  251. */
  252. int
  253. SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
  254. {
  255. SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
  256. if (device == NULL) {
  257. return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
  258. }
  259. joystick->hwdata = device;
  260. joystick->instance_id = device->instance_id;
  261. joystick->naxes = device->naxes;
  262. joystick->nhats = device->nhats;
  263. joystick->nbuttons = device->nbuttons;
  264. joystick->nballs = 0;
  265. device->joystick = joystick;
  266. @autoreleasepool {
  267. if (device->accelerometer) {
  268. if (motionManager == nil) {
  269. motionManager = [[CMMotionManager alloc] init];
  270. }
  271. /* Shorter times between updates can significantly increase CPU usage. */
  272. motionManager.accelerometerUpdateInterval = 0.1;
  273. [motionManager startAccelerometerUpdates];
  274. } else {
  275. #ifdef SDL_JOYSTICK_MFI
  276. GCController *controller = device->controller;
  277. controller.controllerPausedHandler = ^(GCController *c) {
  278. if (joystick->hwdata) {
  279. ++joystick->hwdata->num_pause_presses;
  280. }
  281. };
  282. #endif /* SDL_JOYSTICK_MFI */
  283. }
  284. }
  285. return 0;
  286. }
  287. /* Function to determine if this joystick is attached to the system right now */
  288. SDL_bool
  289. SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
  290. {
  291. return joystick->hwdata != NULL;
  292. }
  293. static void
  294. SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
  295. {
  296. const float maxgforce = SDL_IPHONE_MAX_GFORCE;
  297. const SInt16 maxsint16 = 0x7FFF;
  298. CMAcceleration accel;
  299. @autoreleasepool {
  300. if (!motionManager.isAccelerometerActive) {
  301. return;
  302. }
  303. accel = motionManager.accelerometerData.acceleration;
  304. }
  305. /*
  306. Convert accelerometer data from floating point to Sint16, which is what
  307. the joystick system expects.
  308. To do the conversion, the data is first clamped onto the interval
  309. [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
  310. by MAX_SINT16 so that it is mapped to the full range of an Sint16.
  311. You can customize the clamped range of this function by modifying the
  312. SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
  313. Once converted to Sint16, the accelerometer data no longer has coherent
  314. units. You can convert the data back to units of g-force by multiplying
  315. it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
  316. */
  317. /* clamp the data */
  318. accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
  319. accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
  320. accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
  321. /* pass in data mapped to range of SInt16 */
  322. SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16);
  323. SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
  324. SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16);
  325. }
  326. #ifdef SDL_JOYSTICK_MFI
  327. static Uint8
  328. SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
  329. {
  330. Uint8 hat = 0;
  331. if (dpad.up.isPressed) {
  332. hat |= SDL_HAT_UP;
  333. } else if (dpad.down.isPressed) {
  334. hat |= SDL_HAT_DOWN;
  335. }
  336. if (dpad.left.isPressed) {
  337. hat |= SDL_HAT_LEFT;
  338. } else if (dpad.right.isPressed) {
  339. hat |= SDL_HAT_RIGHT;
  340. }
  341. if (hat == 0) {
  342. return SDL_HAT_CENTERED;
  343. }
  344. return hat;
  345. }
  346. #endif
  347. static void
  348. SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
  349. {
  350. #ifdef SDL_JOYSTICK_MFI
  351. @autoreleasepool {
  352. GCController *controller = joystick->hwdata->controller;
  353. Uint8 hatstate = SDL_HAT_CENTERED;
  354. int i;
  355. int updateplayerindex = 0;
  356. if (controller.extendedGamepad) {
  357. GCExtendedGamepad *gamepad = controller.extendedGamepad;
  358. /* Axis order matches the XInput Windows mappings. */
  359. Sint16 axes[] = {
  360. (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
  361. (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
  362. (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
  363. (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
  364. (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
  365. (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
  366. };
  367. /* Button order matches the XInput Windows mappings. */
  368. Uint8 buttons[] = {
  369. gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
  370. gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
  371. gamepad.leftShoulder.isPressed,
  372. gamepad.rightShoulder.isPressed,
  373. };
  374. hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
  375. for (i = 0; i < SDL_arraysize(axes); i++) {
  376. /* The triggers (axes 2 and 5) are resting at -32768 but SDL
  377. * initializes its values to 0. We only want to make sure the
  378. * player index is up to date if the user actually moves an axis. */
  379. if ((i != 2 && i != 5) || axes[i] != -32768) {
  380. updateplayerindex |= (joystick->axes[i] != axes[i]);
  381. }
  382. SDL_PrivateJoystickAxis(joystick, i, axes[i]);
  383. }
  384. for (i = 0; i < SDL_arraysize(buttons); i++) {
  385. updateplayerindex |= (joystick->buttons[i] != buttons[i]);
  386. SDL_PrivateJoystickButton(joystick, i, buttons[i]);
  387. }
  388. } else if (controller.gamepad) {
  389. GCGamepad *gamepad = controller.gamepad;
  390. /* Button order matches the XInput Windows mappings. */
  391. Uint8 buttons[] = {
  392. gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
  393. gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
  394. gamepad.leftShoulder.isPressed,
  395. gamepad.rightShoulder.isPressed,
  396. };
  397. hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
  398. for (i = 0; i < SDL_arraysize(buttons); i++) {
  399. updateplayerindex |= (joystick->buttons[i] != buttons[i]);
  400. SDL_PrivateJoystickButton(joystick, i, buttons[i]);
  401. }
  402. }
  403. /* TODO: Handle micro profiles on tvOS. */
  404. if (joystick->nhats > 0) {
  405. updateplayerindex |= (joystick->hats[0] != hatstate);
  406. SDL_PrivateJoystickHat(joystick, 0, hatstate);
  407. }
  408. for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
  409. /* The pause button is always last. */
  410. Uint8 pausebutton = joystick->nbuttons - 1;
  411. SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED);
  412. SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED);
  413. updateplayerindex = YES;
  414. }
  415. joystick->hwdata->num_pause_presses = 0;
  416. if (updateplayerindex && controller.playerIndex == -1) {
  417. BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
  418. /* Find the player index of all other connected controllers. */
  419. for (GCController *c in [GCController controllers]) {
  420. if (c != controller && c.playerIndex >= 0) {
  421. usedPlayerIndexSlots[c.playerIndex] = YES;
  422. }
  423. }
  424. /* Set this controller's player index to the first unused index.
  425. * FIXME: This logic isn't great... but SDL doesn't expose this
  426. * concept in its external API, so we don't have much to go on. */
  427. for (i = 0; i < SDL_arraysize(usedPlayerIndexSlots); i++) {
  428. if (!usedPlayerIndexSlots[i]) {
  429. controller.playerIndex = i;
  430. break;
  431. }
  432. }
  433. }
  434. }
  435. #endif
  436. }
  437. /* Function to update the state of a joystick - called as a device poll.
  438. * This function shouldn't update the joystick structure directly,
  439. * but instead should call SDL_PrivateJoystick*() to deliver events
  440. * and update joystick device state.
  441. */
  442. void
  443. SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
  444. {
  445. SDL_JoystickDeviceItem *device = joystick->hwdata;
  446. if (device == NULL) {
  447. return;
  448. }
  449. if (device->accelerometer) {
  450. SDL_SYS_AccelerometerUpdate(joystick);
  451. } else if (device->controller) {
  452. SDL_SYS_MFIJoystickUpdate(joystick);
  453. }
  454. }
  455. /* Function to close a joystick after use */
  456. void
  457. SDL_SYS_JoystickClose(SDL_Joystick * joystick)
  458. {
  459. SDL_JoystickDeviceItem *device = joystick->hwdata;
  460. if (device == NULL) {
  461. return;
  462. }
  463. device->joystick = NULL;
  464. @autoreleasepool {
  465. if (device->accelerometer) {
  466. [motionManager stopAccelerometerUpdates];
  467. } else if (device->controller) {
  468. #ifdef SDL_JOYSTICK_MFI
  469. GCController *controller = device->controller;
  470. controller.controllerPausedHandler = nil;
  471. controller.playerIndex = -1;
  472. #endif
  473. }
  474. }
  475. }
  476. /* Function to perform any system-specific joystick related cleanup */
  477. void
  478. SDL_SYS_JoystickQuit(void)
  479. {
  480. @autoreleasepool {
  481. #ifdef SDL_JOYSTICK_MFI
  482. NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  483. if (connectObserver) {
  484. [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
  485. connectObserver = nil;
  486. }
  487. if (disconnectObserver) {
  488. [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
  489. disconnectObserver = nil;
  490. }
  491. #endif /* SDL_JOYSTICK_MFI */
  492. while (deviceList != NULL) {
  493. SDL_SYS_RemoveJoystickDevice(deviceList);
  494. }
  495. motionManager = nil;
  496. }
  497. numjoysticks = 0;
  498. }
  499. SDL_JoystickGUID
  500. SDL_SYS_JoystickGetDeviceGUID( int device_index )
  501. {
  502. SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
  503. SDL_JoystickGUID guid;
  504. if (device) {
  505. guid = device->guid;
  506. } else {
  507. SDL_zero(guid);
  508. }
  509. return guid;
  510. }
  511. SDL_JoystickGUID
  512. SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
  513. {
  514. SDL_JoystickGUID guid;
  515. if (joystick->hwdata) {
  516. guid = joystick->hwdata->guid;
  517. } else {
  518. SDL_zero(guid);
  519. }
  520. return guid;
  521. }
  522. /* vi: set ts=4 sw=4 expandtab: */