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