src/joystick/iphoneos/SDL_sysjoystick.m
author Alex Szpakowski <slime73@gmail.com>
Mon, 09 Nov 2015 18:13:47 -0400
changeset 9909 b2c000b256ea
parent 9908 b63158b01a7d
child 9960 a20484c998da
permissions -rw-r--r--
iOS: Set the 'player index' of MFi game controllers when they're opened for use.

MFi controllers display their player index via LEDs on the controller.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2015 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_joystick.h"
    30 #include "SDL_hints.h"
    31 #include "SDL_stdinc.h"
    32 #include "../SDL_sysjoystick.h"
    33 #include "../SDL_joystick_c.h"
    34 
    35 #if !SDL_EVENTS_DISABLED
    36 #include "../../events/SDL_events_c.h"
    37 #endif
    38 
    39 #import <CoreMotion/CoreMotion.h>
    40 
    41 #ifdef SDL_JOYSTICK_MFI
    42 #import <GameController/GameController.h>
    43 
    44 static id connectObserver = nil;
    45 static id disconnectObserver = nil;
    46 #endif /* SDL_JOYSTICK_MFI */
    47 
    48 static const char *accelerometerName = "iOS Accelerometer";
    49 static CMMotionManager *motionManager = nil;
    50 
    51 static SDL_JoystickDeviceItem *deviceList = NULL;
    52 
    53 static int numjoysticks = 0;
    54 static SDL_JoystickID instancecounter = 0;
    55 
    56 static SDL_JoystickDeviceItem *
    57 GetDeviceForIndex(int device_index)
    58 {
    59     SDL_JoystickDeviceItem *device = deviceList;
    60     int i = 0;
    61 
    62     while (i < device_index) {
    63         if (device == NULL) {
    64             return NULL;
    65         }
    66         device = device->next;
    67         i++;
    68     }
    69 
    70     return device;
    71 }
    72 
    73 static void
    74 SDL_SYS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
    75 {
    76 #ifdef SDL_JOYSTICK_MFI
    77     const char *name = NULL;
    78     /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
    79      * struct, and ARC doesn't work with structs. */
    80     device->controller = (__bridge GCController *) CFBridgingRetain(controller);
    81 
    82     if (controller.vendorName) {
    83         name = controller.vendorName.UTF8String;
    84     }
    85 
    86     if (!name) {
    87         name = "MFi Gamepad";
    88     }
    89 
    90     device->name = SDL_strdup(name);
    91 
    92     device->guid.data[0] = 'M';
    93     device->guid.data[1] = 'F';
    94     device->guid.data[2] = 'i';
    95     device->guid.data[3] = 'G';
    96     device->guid.data[4] = 'a';
    97     device->guid.data[5] = 'm';
    98     device->guid.data[6] = 'e';
    99     device->guid.data[7] = 'p';
   100     device->guid.data[8] = 'a';
   101     device->guid.data[9] = 'd';
   102 
   103     if (controller.extendedGamepad) {
   104         device->guid.data[10] = 1;
   105     } else if (controller.gamepad) {
   106         device->guid.data[10] = 2;
   107     }
   108 
   109     if (controller.extendedGamepad) {
   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         device->naxes = 0; /* no traditional analog inputs */
   115         device->nhats = 1; /* d-pad */
   116         device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
   117     }
   118     /* TODO: Handle micro profiles on tvOS. */
   119 #endif
   120 }
   121 
   122 static void
   123 SDL_SYS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
   124 {
   125     SDL_JoystickDeviceItem *device = deviceList;
   126 #if !SDL_EVENTS_DISABLED
   127     SDL_Event event;
   128 #endif
   129 
   130     while (device != NULL) {
   131         if (device->controller == controller) {
   132             return;
   133         }
   134         device = device->next;
   135     }
   136 
   137     device = (SDL_JoystickDeviceItem *) SDL_malloc(sizeof(SDL_JoystickDeviceItem));
   138     if (device == NULL) {
   139         return;
   140     }
   141 
   142     SDL_zerop(device);
   143 
   144     device->accelerometer = accelerometer;
   145     device->instance_id = instancecounter++;
   146 
   147     if (accelerometer) {
   148         device->name = SDL_strdup(accelerometerName);
   149         device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
   150         device->nhats = 0;
   151         device->nbuttons = 0;
   152 
   153         /* Use the accelerometer name as a GUID. */
   154         SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
   155     } else if (controller) {
   156         SDL_SYS_AddMFIJoystickDevice(device, controller);
   157     }
   158 
   159     if (deviceList == NULL) {
   160         deviceList = device;
   161     } else {
   162         SDL_JoystickDeviceItem *lastdevice = deviceList;
   163         while (lastdevice->next != NULL) {
   164             lastdevice = lastdevice->next;
   165         }
   166         lastdevice->next = device;
   167     }
   168 
   169     ++numjoysticks;
   170 
   171 #if !SDL_EVENTS_DISABLED
   172     event.type = SDL_JOYDEVICEADDED;
   173 
   174     if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   175         event.jdevice.which = numjoysticks - 1;
   176         if ((SDL_EventOK == NULL) ||
   177             (*SDL_EventOK)(SDL_EventOKParam, &event)) {
   178             SDL_PushEvent(&event);
   179         }
   180     }
   181 #endif /* !SDL_EVENTS_DISABLED */
   182 }
   183 
   184 static SDL_JoystickDeviceItem *
   185 SDL_SYS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
   186 {
   187     SDL_JoystickDeviceItem *prev = NULL;
   188     SDL_JoystickDeviceItem *next = NULL;
   189     SDL_JoystickDeviceItem *item = deviceList;
   190 #if !SDL_EVENTS_DISABLED
   191     SDL_Event event;
   192 #endif
   193 
   194     if (device == NULL) {
   195         return NULL;
   196     }
   197 
   198     next = device->next;
   199 
   200     while (item != NULL) {
   201         if (item == device) {
   202             break;
   203         }
   204         prev = item;
   205         item = item->next;
   206     }
   207 
   208     /* Unlink the device item from the device list. */
   209     if (prev) {
   210         prev->next = device->next;
   211     } else if (device == deviceList) {
   212         deviceList = device->next;
   213     }
   214 
   215     if (device->joystick) {
   216         device->joystick->hwdata = NULL;
   217     }
   218 
   219 #ifdef SDL_JOYSTICK_MFI
   220     @autoreleasepool {
   221         if (device->controller) {
   222             /* The controller was explicitly retained in the struct, so it
   223              * should be explicitly released before freeing the struct. */
   224             GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
   225             controller.controllerPausedHandler = nil;
   226             device->controller = nil;
   227         }
   228     }
   229 #endif /* SDL_JOYSTICK_MFI */
   230 
   231     --numjoysticks;
   232 
   233 #if !SDL_EVENTS_DISABLED
   234     event.type = SDL_JOYDEVICEREMOVED;
   235 
   236     if (SDL_GetEventState(event.type) == SDL_ENABLE) {
   237         event.jdevice.which = device->instance_id;
   238         if ((SDL_EventOK == NULL) ||
   239             (*SDL_EventOK)(SDL_EventOKParam, &event)) {
   240             SDL_PushEvent(&event);
   241         }
   242     }
   243 #endif /* !SDL_EVENTS_DISABLED */
   244 
   245     SDL_free(device->name);
   246     SDL_free(device);
   247 
   248     return next;
   249 }
   250 
   251 /* Function to scan the system for joysticks.
   252  * Joystick 0 should be the system default joystick.
   253  * It should return 0, or -1 on an unrecoverable fatal error.
   254  */
   255 int
   256 SDL_SYS_JoystickInit(void)
   257 {
   258     @autoreleasepool {
   259         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   260         const char *hint = SDL_GetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK);
   261 
   262         if (!hint || SDL_atoi(hint)) {
   263             /* Default behavior, accelerometer as joystick */
   264             SDL_SYS_AddJoystickDevice(nil, SDL_TRUE);
   265         }
   266 
   267 #ifdef SDL_JOYSTICK_MFI
   268         /* GameController.framework was added in iOS 7. */
   269         if (![GCController class]) {
   270             return numjoysticks;
   271         }
   272 
   273         for (GCController *controller in [GCController controllers]) {
   274             SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
   275         }
   276 
   277         connectObserver = [center addObserverForName:GCControllerDidConnectNotification
   278                                               object:nil
   279                                                queue:nil
   280                                           usingBlock:^(NSNotification *note) {
   281                                               GCController *controller = note.object;
   282                                               SDL_SYS_AddJoystickDevice(controller, SDL_FALSE);
   283                                           }];
   284 
   285         disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
   286                                                  object:nil
   287                                                   queue:nil
   288                                              usingBlock:^(NSNotification *note) {
   289                                                  GCController *controller = note.object;
   290                                                  SDL_JoystickDeviceItem *device = deviceList;
   291                                                  while (device != NULL) {
   292                                                      if (device->controller == controller) {
   293                                                          SDL_SYS_RemoveJoystickDevice(device);
   294                                                          break;
   295                                                      }
   296                                                      device = device->next;
   297                                                  }
   298                                              }];
   299 #endif /* SDL_JOYSTICK_MFI */
   300     }
   301 
   302     return numjoysticks;
   303 }
   304 
   305 int SDL_SYS_NumJoysticks()
   306 {
   307     return numjoysticks;
   308 }
   309 
   310 void SDL_SYS_JoystickDetect()
   311 {
   312 }
   313 
   314 /* Function to get the device-dependent name of a joystick */
   315 const char *
   316 SDL_SYS_JoystickNameForDeviceIndex(int device_index)
   317 {
   318     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   319     return device ? device->name : "Unknown";
   320 }
   321 
   322 /* Function to perform the mapping from device index to the instance id for this index */
   323 SDL_JoystickID SDL_SYS_GetInstanceIdOfDeviceIndex(int device_index)
   324 {
   325     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   326     return device ? device->instance_id : 0;
   327 }
   328 
   329 /* Function to open a joystick for use.
   330    The joystick to open is specified by the device index.
   331    This should fill the nbuttons and naxes fields of the joystick structure.
   332    It returns 0, or -1 if there is an error.
   333  */
   334 int
   335 SDL_SYS_JoystickOpen(SDL_Joystick * joystick, int device_index)
   336 {
   337     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   338     if (device == NULL) {
   339         return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
   340     }
   341 
   342     joystick->hwdata = device;
   343     joystick->instance_id = device->instance_id;
   344 
   345     joystick->naxes = device->naxes;
   346     joystick->nhats = device->nhats;
   347     joystick->nbuttons = device->nbuttons;
   348     joystick->nballs = 0;
   349 
   350     device->joystick = joystick;
   351 
   352     @autoreleasepool {
   353         if (device->accelerometer) {
   354             if (motionManager == nil) {
   355                 motionManager = [[CMMotionManager alloc] init];
   356             }
   357 
   358             /* Shorter times between updates can significantly increase CPU usage. */
   359             motionManager.accelerometerUpdateInterval = 0.1;
   360             [motionManager startAccelerometerUpdates];
   361         } else {
   362 #ifdef SDL_JOYSTICK_MFI
   363             GCController *controller = device->controller;
   364             BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
   365 
   366             /* Find the player index of all other connected controllers. */
   367             for (GCController *c in [GCController controllers]) {
   368                 if (c != controller && c.playerIndex >= 0) {
   369                     usedPlayerIndexSlots[c.playerIndex] = YES;
   370                 }
   371             }
   372 
   373             /* Set this controller's player index to the first unused index.
   374              * FIXME: This logic isn't great... but SDL doesn't expose this
   375              * concept in its external API, so we don't have much to go on. */
   376             for (int i = 0; i < 4; i++) {
   377                 if (!usedPlayerIndexSlots[i]) {
   378                     controller.playerIndex = i;
   379                     break;
   380                 }
   381             }
   382 
   383             controller.controllerPausedHandler = ^(GCController *controller) {
   384                 if (joystick->hwdata) {
   385                     ++joystick->hwdata->num_pause_presses;
   386                 }
   387             };
   388 #endif /* SDL_JOYSTICK_MFI */
   389         }
   390     }
   391 
   392     return 0;
   393 }
   394 
   395 /* Function to determine if this joystick is attached to the system right now */
   396 SDL_bool
   397 SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
   398 {
   399     return joystick->hwdata != NULL;
   400 }
   401 
   402 static void
   403 SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
   404 {
   405     const float maxgforce = SDL_IPHONE_MAX_GFORCE;
   406     const SInt16 maxsint16 = 0x7FFF;
   407     CMAcceleration accel;
   408 
   409     @autoreleasepool {
   410         if (!motionManager.isAccelerometerActive) {
   411             return;
   412         }
   413 
   414         accel = motionManager.accelerometerData.acceleration;
   415     }
   416 
   417     /*
   418      Convert accelerometer data from floating point to Sint16, which is what
   419      the joystick system expects.
   420 
   421      To do the conversion, the data is first clamped onto the interval
   422      [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
   423      by MAX_SINT16 so that it is mapped to the full range of an Sint16.
   424 
   425      You can customize the clamped range of this function by modifying the
   426      SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
   427 
   428      Once converted to Sint16, the accelerometer data no longer has coherent
   429      units. You can convert the data back to units of g-force by multiplying
   430      it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
   431      */
   432 
   433     /* clamp the data */
   434     accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
   435     accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
   436     accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
   437 
   438     /* pass in data mapped to range of SInt16 */
   439     SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
   440     SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
   441     SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
   442 }
   443 
   444 #ifdef SDL_JOYSTICK_MFI
   445 static Uint8
   446 SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
   447 {
   448     Uint8 hat = 0;
   449 
   450     if (dpad.up.isPressed) {
   451         hat |= SDL_HAT_UP;
   452     } else if (dpad.down.isPressed) {
   453         hat |= SDL_HAT_DOWN;
   454     }
   455 
   456     if (dpad.left.isPressed) {
   457         hat |= SDL_HAT_LEFT;
   458     } else if (dpad.right.isPressed) {
   459         hat |= SDL_HAT_RIGHT;
   460     }
   461 
   462     if (hat == 0) {
   463         return SDL_HAT_CENTERED;
   464     }
   465 
   466     return hat;
   467 }
   468 #endif
   469 
   470 static void
   471 SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
   472 {
   473 #ifdef SDL_JOYSTICK_MFI
   474     @autoreleasepool {
   475         GCController *controller = joystick->hwdata->controller;
   476         Uint8 hatstate = SDL_HAT_CENTERED;
   477         int i;
   478 
   479         if (controller.extendedGamepad) {
   480             GCExtendedGamepad *gamepad = controller.extendedGamepad;
   481 
   482             /* Axis order matches the XInput Windows mappings. */
   483             SDL_PrivateJoystickAxis(joystick, 0, (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767));
   484             SDL_PrivateJoystickAxis(joystick, 1, (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767));
   485             SDL_PrivateJoystickAxis(joystick, 2, (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768));
   486             SDL_PrivateJoystickAxis(joystick, 3, (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767));
   487             SDL_PrivateJoystickAxis(joystick, 4, (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767));
   488             SDL_PrivateJoystickAxis(joystick, 5, (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768));
   489 
   490             hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
   491 
   492             /* Button order matches the XInput Windows mappings. */
   493             SDL_PrivateJoystickButton(joystick, 0, gamepad.buttonA.isPressed);
   494             SDL_PrivateJoystickButton(joystick, 1, gamepad.buttonB.isPressed);
   495             SDL_PrivateJoystickButton(joystick, 2, gamepad.buttonX.isPressed);
   496             SDL_PrivateJoystickButton(joystick, 3, gamepad.buttonY.isPressed);
   497             SDL_PrivateJoystickButton(joystick, 4, gamepad.leftShoulder.isPressed);
   498             SDL_PrivateJoystickButton(joystick, 5, gamepad.rightShoulder.isPressed);
   499         } else if (controller.gamepad) {
   500             GCGamepad *gamepad = controller.gamepad;
   501 
   502             hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
   503 
   504             /* Button order matches the XInput Windows mappings. */
   505             SDL_PrivateJoystickButton(joystick, 0, gamepad.buttonA.isPressed);
   506             SDL_PrivateJoystickButton(joystick, 1, gamepad.buttonB.isPressed);
   507             SDL_PrivateJoystickButton(joystick, 2, gamepad.buttonX.isPressed);
   508             SDL_PrivateJoystickButton(joystick, 3, gamepad.buttonY.isPressed);
   509             SDL_PrivateJoystickButton(joystick, 4, gamepad.leftShoulder.isPressed);
   510             SDL_PrivateJoystickButton(joystick, 5, gamepad.rightShoulder.isPressed);
   511         }
   512         /* TODO: Handle micro profiles on tvOS. */
   513 
   514         SDL_PrivateJoystickHat(joystick, 0, hatstate);
   515 
   516         for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   517             /* The pause button is always last. */
   518             Uint8 pausebutton = joystick->nbuttons - 1;
   519 
   520             SDL_PrivateJoystickButton(joystick, pausebutton, 1);
   521             SDL_PrivateJoystickButton(joystick, pausebutton, 0);
   522         }
   523 
   524         joystick->hwdata->num_pause_presses = 0;
   525     }
   526 #endif
   527 }
   528 
   529 /* Function to update the state of a joystick - called as a device poll.
   530  * This function shouldn't update the joystick structure directly,
   531  * but instead should call SDL_PrivateJoystick*() to deliver events
   532  * and update joystick device state.
   533  */
   534 void
   535 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   536 {
   537     SDL_JoystickDeviceItem *device = joystick->hwdata;
   538 
   539     if (device == NULL) {
   540         return;
   541     }
   542 
   543     if (device->accelerometer) {
   544         SDL_SYS_AccelerometerUpdate(joystick);
   545     } else if (device->controller) {
   546         SDL_SYS_MFIJoystickUpdate(joystick);
   547     }
   548 }
   549 
   550 /* Function to close a joystick after use */
   551 void
   552 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   553 {
   554     SDL_JoystickDeviceItem *device = joystick->hwdata;
   555 
   556     if (device == NULL) {
   557         return;
   558     }
   559 
   560     device->joystick = NULL;
   561 
   562     @autoreleasepool {
   563         if (device->accelerometer) {
   564             [motionManager stopAccelerometerUpdates];
   565         } else if (device->controller) {
   566 #ifdef SDL_JOYSTICK_MFI
   567             GCController *controller = device->controller;
   568             controller.controllerPausedHandler = nil;
   569 #endif
   570         }
   571     }
   572 }
   573 
   574 /* Function to perform any system-specific joystick related cleanup */
   575 void
   576 SDL_SYS_JoystickQuit(void)
   577 {
   578     @autoreleasepool {
   579 #ifdef SDL_JOYSTICK_MFI
   580         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   581 
   582         if (connectObserver) {
   583             [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
   584             connectObserver = nil;
   585         }
   586 
   587         if (disconnectObserver) {
   588             [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
   589             disconnectObserver = nil;
   590         }
   591 #endif /* SDL_JOYSTICK_MFI */
   592 
   593         while (deviceList != NULL) {
   594             SDL_SYS_RemoveJoystickDevice(deviceList);
   595         }
   596 
   597         motionManager = nil;
   598     }
   599 
   600     numjoysticks = 0;
   601 }
   602 
   603 SDL_JoystickGUID
   604 SDL_SYS_JoystickGetDeviceGUID( int device_index )
   605 {
   606     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   607     SDL_JoystickGUID guid;
   608     if (device) {
   609         guid = device->guid;
   610     } else {
   611         SDL_zero(guid);
   612     }
   613     return guid;
   614 }
   615 
   616 SDL_JoystickGUID
   617 SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
   618 {
   619     SDL_JoystickGUID guid;
   620     if (joystick->hwdata) {
   621         guid = joystick->hwdata->guid;
   622     } else {
   623         SDL_zero(guid);
   624     }
   625     return guid;
   626 }
   627 
   628 /* vi: set ts=4 sw=4 expandtab: */