src/joystick/iphoneos/SDL_sysjoystick.m
author Alex Szpakowski <slime73@gmail.com>
Fri, 25 Sep 2015 15:17:20 -0300
changeset 9879 8465a79c9f85
parent 9878 63f03a567fe3
child 9902 a0d15db7bd09
permissions -rw-r--r--
iOS: show message boxes using the new UIAlertController APIs when supported, rather than the deprecated UIAlertView.

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