src/joystick/iphoneos/SDL_sysjoystick.m
author Philipp Wiesemann <philipp.wiesemann@arcor.de>
Mon, 21 Sep 2015 21:19:37 +0200
changeset 9878 63f03a567fe3
parent 9876 1496e502e51d
child 9879 8465a79c9f85
permissions -rw-r--r--
iOS: Fixed pointer dereference after free.
     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 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             controller.controllerPausedHandler = ^(GCController *controller) {
   365                 if (joystick->hwdata) {
   366                     ++joystick->hwdata->num_pause_presses;
   367                 }
   368             };
   369 #endif /* SDL_JOYSTICK_MFI */
   370         }
   371     }
   372 
   373     return 0;
   374 }
   375 
   376 /* Function to determine if this joystick is attached to the system right now */
   377 SDL_bool SDL_SYS_JoystickAttached(SDL_Joystick *joystick)
   378 {
   379     return joystick->hwdata != NULL;
   380 }
   381 
   382 static void SDL_SYS_AccelerometerUpdate(SDL_Joystick * joystick)
   383 {
   384     const float maxgforce = SDL_IPHONE_MAX_GFORCE;
   385     const SInt16 maxsint16 = 0x7FFF;
   386     CMAcceleration accel;
   387 
   388     @autoreleasepool {
   389         if (!motionManager.isAccelerometerActive) {
   390             return;
   391         }
   392 
   393         accel = motionManager.accelerometerData.acceleration;
   394     }
   395 
   396     /*
   397      Convert accelerometer data from floating point to Sint16, which is what
   398      the joystick system expects.
   399 
   400      To do the conversion, the data is first clamped onto the interval
   401      [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
   402      by MAX_SINT16 so that it is mapped to the full range of an Sint16.
   403 
   404      You can customize the clamped range of this function by modifying the
   405      SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
   406 
   407      Once converted to Sint16, the accelerometer data no longer has coherent
   408      units. You can convert the data back to units of g-force by multiplying
   409      it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
   410      */
   411 
   412     /* clamp the data */
   413     accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
   414     accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
   415     accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
   416 
   417     /* pass in data mapped to range of SInt16 */
   418     SDL_PrivateJoystickAxis(joystick, 0,  (accel.x / maxgforce) * maxsint16);
   419     SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
   420     SDL_PrivateJoystickAxis(joystick, 2,  (accel.z / maxgforce) * maxsint16);
   421 }
   422 
   423 #ifdef SDL_JOYSTICK_MFI
   424 static Uint8
   425 SDL_SYS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
   426 {
   427     Uint8 hat = 0;
   428 
   429     if (dpad.up.isPressed) {
   430         hat |= SDL_HAT_UP;
   431     } else if (dpad.down.isPressed) {
   432         hat |= SDL_HAT_DOWN;
   433     }
   434 
   435     if (dpad.left.isPressed) {
   436         hat |= SDL_HAT_LEFT;
   437     } else if (dpad.right.isPressed) {
   438         hat |= SDL_HAT_RIGHT;
   439     }
   440 
   441     if (hat == 0) {
   442         return SDL_HAT_CENTERED;
   443     }
   444 
   445     return hat;
   446 }
   447 #endif
   448 
   449 static void
   450 SDL_SYS_MFIJoystickUpdate(SDL_Joystick * joystick)
   451 {
   452 #ifdef SDL_JOYSTICK_MFI
   453     @autoreleasepool {
   454         GCController *controller = joystick->hwdata->controller;
   455         Uint8 hatstate = SDL_HAT_CENTERED;
   456         int i;
   457 
   458         if (controller.extendedGamepad) {
   459             GCExtendedGamepad *gamepad = controller.extendedGamepad;
   460 
   461             /* Axis order matches the XInput Windows mappings. */
   462             SDL_PrivateJoystickAxis(joystick, 0, (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767));
   463             SDL_PrivateJoystickAxis(joystick, 1, (Sint16) (gamepad.leftThumbstick.yAxis.value * 32767));
   464             SDL_PrivateJoystickAxis(joystick, 2, (Sint16) (gamepad.leftTrigger.value * 32767));
   465             SDL_PrivateJoystickAxis(joystick, 3, (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767));
   466             SDL_PrivateJoystickAxis(joystick, 4, (Sint16) (gamepad.rightThumbstick.yAxis.value * 32767));
   467             SDL_PrivateJoystickAxis(joystick, 5, (Sint16) (gamepad.rightTrigger.value * 32767));
   468 
   469             hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
   470 
   471             /* Button order matches the XInput Windows mappings. */
   472             SDL_PrivateJoystickButton(joystick, 0, gamepad.buttonA.isPressed);
   473             SDL_PrivateJoystickButton(joystick, 1, gamepad.buttonB.isPressed);
   474             SDL_PrivateJoystickButton(joystick, 2, gamepad.buttonX.isPressed);
   475             SDL_PrivateJoystickButton(joystick, 3, gamepad.buttonY.isPressed);
   476             SDL_PrivateJoystickButton(joystick, 4, gamepad.leftShoulder.isPressed);
   477             SDL_PrivateJoystickButton(joystick, 5, gamepad.rightShoulder.isPressed);
   478         } else if (controller.gamepad) {
   479             GCGamepad *gamepad = controller.gamepad;
   480 
   481             hatstate = SDL_SYS_MFIJoystickHatStateForDPad(gamepad.dpad);
   482 
   483             /* Button order matches the XInput Windows mappings. */
   484             SDL_PrivateJoystickButton(joystick, 0, gamepad.buttonA.isPressed);
   485             SDL_PrivateJoystickButton(joystick, 1, gamepad.buttonB.isPressed);
   486             SDL_PrivateJoystickButton(joystick, 2, gamepad.buttonX.isPressed);
   487             SDL_PrivateJoystickButton(joystick, 3, gamepad.buttonY.isPressed);
   488             SDL_PrivateJoystickButton(joystick, 4, gamepad.leftShoulder.isPressed);
   489             SDL_PrivateJoystickButton(joystick, 5, gamepad.rightShoulder.isPressed);
   490         }
   491         /* TODO: Handle micro profiles on tvOS. */
   492 
   493         SDL_PrivateJoystickHat(joystick, 0, hatstate);
   494 
   495         for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   496             /* The pause button is always last. */
   497             Uint8 pausebutton = joystick->nbuttons - 1;
   498 
   499             SDL_PrivateJoystickButton(joystick, pausebutton, 1);
   500             SDL_PrivateJoystickButton(joystick, pausebutton, 0);
   501         }
   502 
   503         joystick->hwdata->num_pause_presses = 0;
   504     }
   505 #endif
   506 }
   507 
   508 /* Function to update the state of a joystick - called as a device poll.
   509  * This function shouldn't update the joystick structure directly,
   510  * but instead should call SDL_PrivateJoystick*() to deliver events
   511  * and update joystick device state.
   512  */
   513 void
   514 SDL_SYS_JoystickUpdate(SDL_Joystick * joystick)
   515 {
   516     SDL_JoystickDeviceItem *device = joystick->hwdata;
   517 
   518     if (device == NULL) {
   519         return;
   520     }
   521 
   522     if (device->accelerometer) {
   523         SDL_SYS_AccelerometerUpdate(joystick);
   524     } else if (device->controller) {
   525         SDL_SYS_MFIJoystickUpdate(joystick);
   526     }
   527 }
   528 
   529 /* Function to close a joystick after use */
   530 void
   531 SDL_SYS_JoystickClose(SDL_Joystick * joystick)
   532 {
   533     SDL_JoystickDeviceItem *device = joystick->hwdata;
   534 
   535     if (device == NULL) {
   536         return;
   537     }
   538 
   539     device->joystick = NULL;
   540 
   541     @autoreleasepool {
   542         if (device->accelerometer) {
   543             [motionManager stopAccelerometerUpdates];
   544         } else if (device->controller) {
   545 #ifdef SDL_JOYSTICK_MFI
   546             GCController *controller = device->controller;
   547             controller.controllerPausedHandler = nil;
   548 #endif
   549         }
   550     }
   551 }
   552 
   553 /* Function to perform any system-specific joystick related cleanup */
   554 void
   555 SDL_SYS_JoystickQuit(void)
   556 {
   557     @autoreleasepool {
   558 #ifdef SDL_JOYSTICK_MFI
   559         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
   560 
   561         if (connectObserver) {
   562             [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
   563             connectObserver = nil;
   564         }
   565 
   566         if (disconnectObserver) {
   567             [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
   568             disconnectObserver = nil;
   569         }
   570 #endif /* SDL_JOYSTICK_MFI */
   571 
   572         while (deviceList != NULL) {
   573             SDL_SYS_RemoveJoystickDevice(deviceList);
   574         }
   575 
   576         motionManager = nil;
   577     }
   578 
   579     numjoysticks = 0;
   580 }
   581 
   582 SDL_JoystickGUID SDL_SYS_JoystickGetDeviceGUID( int device_index )
   583 {
   584     SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   585     SDL_JoystickGUID guid;
   586     if (device) {
   587         guid = device->guid;
   588     } else {
   589         SDL_zero(guid);
   590     }
   591     return guid;
   592 }
   593 
   594 SDL_JoystickGUID SDL_SYS_JoystickGetGUID(SDL_Joystick * joystick)
   595 {
   596     SDL_JoystickGUID guid;
   597     if (joystick->hwdata) {
   598         guid = joystick->hwdata->guid;
   599     } else {
   600         SDL_zero(guid);
   601     }
   602     return guid;
   603 }
   604 
   605 /* vi: set ts=4 sw=4 expandtab: */