123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2020 Sam Lantinga <[email protected]>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "../../SDL_internal.h"
- /* This is the iOS implementation of the SDL joystick API */
- #include "SDL_sysjoystick_c.h"
- /* needed for SDL_IPHONE_MAX_GFORCE macro */
- #include "../../../include/SDL_config_iphoneos.h"
- #include "SDL_assert.h"
- #include "SDL_events.h"
- #include "SDL_joystick.h"
- #include "SDL_hints.h"
- #include "SDL_stdinc.h"
- #include "../SDL_sysjoystick.h"
- #include "../SDL_joystick_c.h"
- #include "../usb_ids.h"
- #if !SDL_EVENTS_DISABLED
- #include "../../events/SDL_events_c.h"
- #endif
- #if !TARGET_OS_TV
- #import <CoreMotion/CoreMotion.h>
- #endif
- #ifdef SDL_JOYSTICK_MFI
- #import <GameController/GameController.h>
- static id connectObserver = nil;
- static id disconnectObserver = nil;
- #include <Availability.h>
- #include <objc/message.h>
- /* remove compilation warnings for strict builds by defining these selectors, even though
- * they are only ever used indirectly through objc_msgSend
- */
- @interface GCExtendedGamepad (SDL)
- #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 121000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 121000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1401000))
- @property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
- @property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
- #endif
- #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 130000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1500000))
- @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
- @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions;
- #endif
- #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000))
- @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonHome;
- #endif
- @end
- @interface GCMicroGamepad (SDL)
- #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 130000) || (__MAC_OS_VERSION_MAX_ALLOWED >= 1500000))
- @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
- #endif
- @end
- #if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000)
- #define ENABLE_MFI_BATTERY
- #define ENABLE_MFI_RUMBLE
- #define ENABLE_MFI_LIGHT
- #define ENABLE_PHYSICAL_INPUT_PROFILE
- #endif
- #ifdef ENABLE_MFI_RUMBLE
- #import <CoreHaptics/CoreHaptics.h>
- #endif
- #endif /* SDL_JOYSTICK_MFI */
- #if !TARGET_OS_TV
- static const char *accelerometerName = "iOS Accelerometer";
- static CMMotionManager *motionManager = nil;
- #endif /* !TARGET_OS_TV */
- static SDL_JoystickDeviceItem *deviceList = NULL;
- static int numjoysticks = 0;
- int SDL_AppleTVRemoteOpenedAsJoystick = 0;
- static SDL_JoystickDeviceItem *
- GetDeviceForIndex(int device_index)
- {
- SDL_JoystickDeviceItem *device = deviceList;
- int i = 0;
- while (i < device_index) {
- if (device == NULL) {
- return NULL;
- }
- device = device->next;
- i++;
- }
- return device;
- }
- static void
- IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
- {
- #ifdef SDL_JOYSTICK_MFI
- Uint16 *guid16 = (Uint16 *)device->guid.data;
- Uint16 vendor = 0;
- Uint16 product = 0;
- Uint8 subtype = 0;
- const char *name = NULL;
- /* Explicitly retain the controller because SDL_JoystickDeviceItem is a
- * struct, and ARC doesn't work with structs. */
- device->controller = (__bridge GCController *) CFBridgingRetain(controller);
- if (controller.vendorName) {
- name = controller.vendorName.UTF8String;
- }
- if (!name) {
- name = "MFi Gamepad";
- }
- device->name = SDL_CreateJoystickName(0, 0, NULL, name);
- if (controller.extendedGamepad) {
- GCExtendedGamepad *gamepad = controller.extendedGamepad;
- BOOL is_xbox = [controller.vendorName containsString: @"Xbox"];
- BOOL is_ps4 = [controller.vendorName containsString: @"DUALSHOCK"];
- #if TARGET_OS_TV
- BOOL is_MFi = (!is_xbox && !is_ps4);
- #endif
- int nbuttons = 0;
- /* These buttons are part of the original MFi spec */
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
- nbuttons += 6;
- /* These buttons are available on some newer controllers */
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability-new"
- if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK);
- ++nbuttons;
- }
- if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK);
- ++nbuttons;
- }
- if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK);
- ++nbuttons;
- }
- if ([gamepad respondsToSelector:@selector(buttonHome)] && gamepad.buttonHome) {
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_GUIDE);
- ++nbuttons;
- }
- BOOL has_direct_menu = [gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu;
- #if TARGET_OS_TV
- /* On tvOS MFi controller menu button brings you to the home screen */
- if (is_MFi) {
- has_direct_menu = FALSE;
- }
- #endif
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
- ++nbuttons;
- if (!has_direct_menu) {
- device->uses_pause_handler = SDL_TRUE;
- }
- #ifdef ENABLE_PHYSICAL_INPUT_PROFILE
- if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
- if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
- device->has_dualshock_touchpad = SDL_TRUE;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_MISC1);
- ++nbuttons;
- }
- if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
- device->has_xbox_paddles = SDL_TRUE;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE1);
- ++nbuttons;
- }
- if (controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo] != nil) {
- device->has_xbox_paddles = SDL_TRUE;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE2);
- ++nbuttons;
- }
- if (controller.physicalInputProfile.buttons[GCInputXboxPaddleThree] != nil) {
- device->has_xbox_paddles = SDL_TRUE;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE3);
- ++nbuttons;
- }
- if (controller.physicalInputProfile.buttons[GCInputXboxPaddleFour] != nil) {
- device->has_xbox_paddles = SDL_TRUE;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_PADDLE4);
- ++nbuttons;
- }
- }
- #endif
- #pragma clang diagnostic pop
- if (is_xbox) {
- vendor = USB_VENDOR_MICROSOFT;
- if (device->has_xbox_paddles) {
- /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */
- product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH;
- subtype = 1;
- } else {
- /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */
- product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
- subtype = 0;
- }
- } else if (is_ps4) {
- /* Assume DS4 Slim unless/until GCController flows VID/PID */
- vendor = USB_VENDOR_SONY;
- product = USB_PRODUCT_SONY_DS4_SLIM;
- if ( device->has_dualshock_touchpad ) {
- subtype = 1;
- } else {
- subtype = 0;
- }
- } else {
- vendor = USB_VENDOR_APPLE;
- product = 1;
- subtype = 1;
- }
- device->naxes = 6; /* 2 thumbsticks and 2 triggers */
- device->nhats = 1; /* d-pad */
- device->nbuttons = nbuttons;
- } else if (controller.gamepad) {
- int nbuttons = 0;
- /* These buttons are part of the original MFi spec */
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
- nbuttons += 7;
- device->uses_pause_handler = SDL_TRUE;
- vendor = USB_VENDOR_APPLE;
- product = 2;
- subtype = 2;
- device->naxes = 0; /* no traditional analog inputs */
- device->nhats = 1; /* d-pad */
- device->nbuttons = nbuttons;
- }
- #if TARGET_OS_TV
- else if (controller.microGamepad) {
- int nbuttons = 0;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */
- nbuttons += 2;
- device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
- ++nbuttons;
- device->uses_pause_handler = SDL_TRUE;
- vendor = USB_VENDOR_APPLE;
- product = 3;
- subtype = 3;
- device->naxes = 2; /* treat the touch surface as two axes */
- device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
- device->nbuttons = nbuttons;
- controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
- }
- #endif /* TARGET_OS_TV */
- /* We only need 16 bits for each of these; space them out to fill 128. */
- /* Byteswap so devices get same GUID on little/big endian platforms. */
- *guid16++ = SDL_SwapLE16(SDL_HARDWARE_BUS_BLUETOOTH);
- *guid16++ = 0;
- *guid16++ = SDL_SwapLE16(vendor);
- *guid16++ = 0;
- *guid16++ = SDL_SwapLE16(product);
- *guid16++ = 0;
- *guid16++ = SDL_SwapLE16(device->button_mask);
- if (subtype != 0) {
- /* Note that this is an MFI controller and what subtype it is */
- device->guid.data[14] = 'm';
- device->guid.data[15] = subtype;
- }
- /* This will be set when the first button press of the controller is
- * detected. */
- controller.playerIndex = -1;
- #endif /* SDL_JOYSTICK_MFI */
- }
- static void
- IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer)
- {
- SDL_JoystickDeviceItem *device = deviceList;
- #if TARGET_OS_TV
- if (!SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, SDL_TRUE)) {
- /* Ignore devices that aren't actually controllers (e.g. remotes), they'll be handled as keyboard input */
- if (controller && !controller.extendedGamepad && !controller.gamepad && controller.microGamepad) {
- return;
- }
- }
- #endif
- while (device != NULL) {
- if (device->controller == controller) {
- return;
- }
- device = device->next;
- }
- device = (SDL_JoystickDeviceItem *) SDL_calloc(1, sizeof(SDL_JoystickDeviceItem));
- if (device == NULL) {
- return;
- }
- device->accelerometer = accelerometer;
- device->instance_id = SDL_GetNextJoystickInstanceID();
- if (accelerometer) {
- #if TARGET_OS_TV
- SDL_free(device);
- return;
- #else
- device->name = SDL_strdup(accelerometerName);
- device->naxes = 3; /* Device acceleration in the x, y, and z axes. */
- device->nhats = 0;
- device->nbuttons = 0;
- /* Use the accelerometer name as a GUID. */
- SDL_memcpy(&device->guid.data, device->name, SDL_min(sizeof(SDL_JoystickGUID), SDL_strlen(device->name)));
- #endif /* TARGET_OS_TV */
- } else if (controller) {
- IOS_AddMFIJoystickDevice(device, controller);
- }
- if (deviceList == NULL) {
- deviceList = device;
- } else {
- SDL_JoystickDeviceItem *lastdevice = deviceList;
- while (lastdevice->next != NULL) {
- lastdevice = lastdevice->next;
- }
- lastdevice->next = device;
- }
- ++numjoysticks;
- SDL_PrivateJoystickAdded(device->instance_id);
- }
- static SDL_JoystickDeviceItem *
- IOS_RemoveJoystickDevice(SDL_JoystickDeviceItem *device)
- {
- SDL_JoystickDeviceItem *prev = NULL;
- SDL_JoystickDeviceItem *next = NULL;
- SDL_JoystickDeviceItem *item = deviceList;
- if (device == NULL) {
- return NULL;
- }
- next = device->next;
- while (item != NULL) {
- if (item == device) {
- break;
- }
- prev = item;
- item = item->next;
- }
- /* Unlink the device item from the device list. */
- if (prev) {
- prev->next = device->next;
- } else if (device == deviceList) {
- deviceList = device->next;
- }
- if (device->joystick) {
- device->joystick->hwdata = NULL;
- }
- #ifdef SDL_JOYSTICK_MFI
- @autoreleasepool {
- if (device->controller) {
- /* The controller was explicitly retained in the struct, so it
- * should be explicitly released before freeing the struct. */
- GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
- controller.controllerPausedHandler = nil;
- device->controller = nil;
- }
- }
- #endif /* SDL_JOYSTICK_MFI */
- --numjoysticks;
- SDL_PrivateJoystickRemoved(device->instance_id);
- SDL_free(device->name);
- SDL_free(device);
- return next;
- }
- #if TARGET_OS_TV
- static void SDLCALL
- SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char *oldValue, const char *newValue)
- {
- BOOL allowRotation = newValue != NULL && *newValue != '0';
- @autoreleasepool {
- for (GCController *controller in [GCController controllers]) {
- if (controller.microGamepad) {
- controller.microGamepad.allowsRotation = allowRotation;
- }
- }
- }
- }
- #endif /* TARGET_OS_TV */
- static int
- IOS_JoystickInit(void)
- {
- @autoreleasepool {
- #if !TARGET_OS_TV
- if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) {
- /* Default behavior, accelerometer as joystick */
- IOS_AddJoystickDevice(nil, SDL_TRUE);
- }
- #endif /* !TARGET_OS_TV */
- #ifdef SDL_JOYSTICK_MFI
- /* GameController.framework was added in iOS 7. */
- if (![GCController class]) {
- return 0;
- }
- for (GCController *controller in [GCController controllers]) {
- IOS_AddJoystickDevice(controller, SDL_FALSE);
- }
- #if TARGET_OS_TV
- SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
- SDL_AppleTVRemoteRotationHintChanged, NULL);
- #endif /* TARGET_OS_TV */
- NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
- connectObserver = [center addObserverForName:GCControllerDidConnectNotification
- object:nil
- queue:nil
- usingBlock:^(NSNotification *note) {
- GCController *controller = note.object;
- IOS_AddJoystickDevice(controller, SDL_FALSE);
- }];
- disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
- object:nil
- queue:nil
- usingBlock:^(NSNotification *note) {
- GCController *controller = note.object;
- SDL_JoystickDeviceItem *device = deviceList;
- while (device != NULL) {
- if (device->controller == controller) {
- IOS_RemoveJoystickDevice(device);
- break;
- }
- device = device->next;
- }
- }];
- #endif /* SDL_JOYSTICK_MFI */
- }
- return 0;
- }
- static int
- IOS_JoystickGetCount(void)
- {
- return numjoysticks;
- }
- static void
- IOS_JoystickDetect(void)
- {
- }
- static const char *
- IOS_JoystickGetDeviceName(int device_index)
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- return device ? device->name : "Unknown";
- }
- static int
- IOS_JoystickGetDevicePlayerIndex(int device_index)
- {
- #ifdef SDL_JOYSTICK_MFI
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- if (device && device->controller) {
- return (int)device->controller.playerIndex;
- }
- #endif
- return -1;
- }
- static void
- IOS_JoystickSetDevicePlayerIndex(int device_index, int player_index)
- {
- #ifdef SDL_JOYSTICK_MFI
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- if (device && device->controller) {
- device->controller.playerIndex = player_index;
- }
- #endif
- }
- static SDL_JoystickGUID
- IOS_JoystickGetDeviceGUID( int device_index )
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- SDL_JoystickGUID guid;
- if (device) {
- guid = device->guid;
- } else {
- SDL_zero(guid);
- }
- return guid;
- }
- static SDL_JoystickID
- IOS_JoystickGetDeviceInstanceID(int device_index)
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- return device ? device->instance_id : -1;
- }
- static int
- IOS_JoystickOpen(SDL_Joystick * joystick, int device_index)
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- if (device == NULL) {
- return SDL_SetError("Could not open Joystick: no hardware device for the specified index");
- }
- joystick->hwdata = device;
- joystick->instance_id = device->instance_id;
- joystick->naxes = device->naxes;
- joystick->nhats = device->nhats;
- joystick->nbuttons = device->nbuttons;
- joystick->nballs = 0;
- device->joystick = joystick;
- @autoreleasepool {
- if (device->accelerometer) {
- #if !TARGET_OS_TV
- if (motionManager == nil) {
- motionManager = [[CMMotionManager alloc] init];
- }
- /* Shorter times between updates can significantly increase CPU usage. */
- motionManager.accelerometerUpdateInterval = 0.1;
- [motionManager startAccelerometerUpdates];
- #endif /* !TARGET_OS_TV */
- } else {
- #ifdef SDL_JOYSTICK_MFI
- if (device->uses_pause_handler) {
- GCController *controller = device->controller;
- controller.controllerPausedHandler = ^(GCController *c) {
- if (joystick->hwdata) {
- ++joystick->hwdata->num_pause_presses;
- }
- };
- }
- #endif /* SDL_JOYSTICK_MFI */
- }
- }
- if (device->remote) {
- ++SDL_AppleTVRemoteOpenedAsJoystick;
- }
- return 0;
- }
- static void
- IOS_AccelerometerUpdate(SDL_Joystick * joystick)
- {
- #if !TARGET_OS_TV
- const float maxgforce = SDL_IPHONE_MAX_GFORCE;
- const SInt16 maxsint16 = 0x7FFF;
- CMAcceleration accel;
- @autoreleasepool {
- if (!motionManager.isAccelerometerActive) {
- return;
- }
- accel = motionManager.accelerometerData.acceleration;
- }
- /*
- Convert accelerometer data from floating point to Sint16, which is what
- the joystick system expects.
- To do the conversion, the data is first clamped onto the interval
- [-SDL_IPHONE_MAX_G_FORCE, SDL_IPHONE_MAX_G_FORCE], then the data is multiplied
- by MAX_SINT16 so that it is mapped to the full range of an Sint16.
- You can customize the clamped range of this function by modifying the
- SDL_IPHONE_MAX_GFORCE macro in SDL_config_iphoneos.h.
- Once converted to Sint16, the accelerometer data no longer has coherent
- units. You can convert the data back to units of g-force by multiplying
- it in your application's code by SDL_IPHONE_MAX_GFORCE / 0x7FFF.
- */
- /* clamp the data */
- accel.x = SDL_min(SDL_max(accel.x, -maxgforce), maxgforce);
- accel.y = SDL_min(SDL_max(accel.y, -maxgforce), maxgforce);
- accel.z = SDL_min(SDL_max(accel.z, -maxgforce), maxgforce);
- /* pass in data mapped to range of SInt16 */
- SDL_PrivateJoystickAxis(joystick, 0, (accel.x / maxgforce) * maxsint16);
- SDL_PrivateJoystickAxis(joystick, 1, -(accel.y / maxgforce) * maxsint16);
- SDL_PrivateJoystickAxis(joystick, 2, (accel.z / maxgforce) * maxsint16);
- #endif /* !TARGET_OS_TV */
- }
- #ifdef SDL_JOYSTICK_MFI
- static Uint8
- IOS_MFIJoystickHatStateForDPad(GCControllerDirectionPad *dpad)
- {
- Uint8 hat = 0;
- if (dpad.up.isPressed) {
- hat |= SDL_HAT_UP;
- } else if (dpad.down.isPressed) {
- hat |= SDL_HAT_DOWN;
- }
- if (dpad.left.isPressed) {
- hat |= SDL_HAT_LEFT;
- } else if (dpad.right.isPressed) {
- hat |= SDL_HAT_RIGHT;
- }
- if (hat == 0) {
- return SDL_HAT_CENTERED;
- }
- return hat;
- }
- #endif
- static void
- IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
- {
- #if SDL_JOYSTICK_MFI
- @autoreleasepool {
- GCController *controller = joystick->hwdata->controller;
- Uint8 hatstate = SDL_HAT_CENTERED;
- int i;
- int pause_button_index = 0;
- if (controller.extendedGamepad) {
- GCExtendedGamepad *gamepad = controller.extendedGamepad;
- /* Axis order matches the XInput Windows mappings. */
- Sint16 axes[] = {
- (Sint16) (gamepad.leftThumbstick.xAxis.value * 32767),
- (Sint16) (gamepad.leftThumbstick.yAxis.value * -32767),
- (Sint16) ((gamepad.leftTrigger.value * 65535) - 32768),
- (Sint16) (gamepad.rightThumbstick.xAxis.value * 32767),
- (Sint16) (gamepad.rightThumbstick.yAxis.value * -32767),
- (Sint16) ((gamepad.rightTrigger.value * 65535) - 32768),
- };
- /* Button order matches the XInput Windows mappings. */
- Uint8 buttons[joystick->nbuttons];
- int button_count = 0;
- /* These buttons are part of the original MFi spec */
- buttons[button_count++] = gamepad.buttonA.isPressed;
- buttons[button_count++] = gamepad.buttonB.isPressed;
- buttons[button_count++] = gamepad.buttonX.isPressed;
- buttons[button_count++] = gamepad.buttonY.isPressed;
- buttons[button_count++] = gamepad.leftShoulder.isPressed;
- buttons[button_count++] = gamepad.rightShoulder.isPressed;
- /* These buttons are available on some newer controllers */
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability-new"
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) {
- buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
- }
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) {
- buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
- }
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) {
- buttons[button_count++] = gamepad.buttonOptions.isPressed;
- }
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_GUIDE)) {
- buttons[button_count++] = gamepad.buttonHome.isPressed;
- }
- /* This must be the last button, so we can optionally handle it with pause_button_index below */
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
- if (joystick->hwdata->uses_pause_handler) {
- pause_button_index = button_count;
- buttons[button_count++] = joystick->delayed_guide_button;
- } else {
- buttons[button_count++] = gamepad.buttonMenu.isPressed;
- }
- }
- #ifdef ENABLE_PHYSICAL_INPUT_PROFILE
- if (joystick->hwdata->has_dualshock_touchpad) {
- buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed;
- }
- if (joystick->hwdata->has_xbox_paddles) {
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE1)) {
- buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed;
- }
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE2)) {
- buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed;
- }
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE3)) {
- buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed;
- }
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_PADDLE4)) {
- buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed;
- }
- /*
- SDL_Log("Paddles: [%d,%d,%d,%d]",
- controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed,
- controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed,
- controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed,
- controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed);
- */
- }
- #endif
- #pragma clang diagnostic pop
- hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
- for (i = 0; i < SDL_arraysize(axes); i++) {
- SDL_PrivateJoystickAxis(joystick, i, axes[i]);
- }
- for (i = 0; i < button_count; i++) {
- SDL_PrivateJoystickButton(joystick, i, buttons[i]);
- }
- } else if (controller.gamepad) {
- GCGamepad *gamepad = controller.gamepad;
- /* Button order matches the XInput Windows mappings. */
- Uint8 buttons[joystick->nbuttons];
- int button_count = 0;
- buttons[button_count++] = gamepad.buttonA.isPressed;
- buttons[button_count++] = gamepad.buttonB.isPressed;
- buttons[button_count++] = gamepad.buttonX.isPressed;
- buttons[button_count++] = gamepad.buttonY.isPressed;
- buttons[button_count++] = gamepad.leftShoulder.isPressed;
- buttons[button_count++] = gamepad.rightShoulder.isPressed;
- pause_button_index = button_count;
- buttons[button_count++] = joystick->delayed_guide_button;
- hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
- for (i = 0; i < button_count; i++) {
- SDL_PrivateJoystickButton(joystick, i, buttons[i]);
- }
- }
- #if TARGET_OS_TV
- else if (controller.microGamepad) {
- GCMicroGamepad *gamepad = controller.microGamepad;
- Sint16 axes[] = {
- (Sint16) (gamepad.dpad.xAxis.value * 32767),
- (Sint16) (gamepad.dpad.yAxis.value * -32767),
- };
- for (i = 0; i < SDL_arraysize(axes); i++) {
- SDL_PrivateJoystickAxis(joystick, i, axes[i]);
- }
- Uint8 buttons[joystick->nbuttons];
- int button_count = 0;
- buttons[button_count++] = gamepad.buttonA.isPressed;
- buttons[button_count++] = gamepad.buttonX.isPressed;
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wunguarded-availability-new"
- /* This must be the last button, so we can optionally handle it with pause_button_index below */
- if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
- if (joystick->hwdata->uses_pause_handler) {
- pause_button_index = button_count;
- buttons[button_count++] = joystick->delayed_guide_button;
- } else {
- buttons[button_count++] = gamepad.buttonMenu.isPressed;
- }
- }
- #pragma clang diagnostic pop
- for (i = 0; i < button_count; i++) {
- SDL_PrivateJoystickButton(joystick, i, buttons[i]);
- }
- }
- #endif /* TARGET_OS_TV */
- if (joystick->nhats > 0) {
- SDL_PrivateJoystickHat(joystick, 0, hatstate);
- }
- if (joystick->hwdata->uses_pause_handler) {
- for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
- SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED);
- SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED);
- }
- joystick->hwdata->num_pause_presses = 0;
- }
- #ifdef ENABLE_MFI_BATTERY
- if (@available(iOS 14.0, tvOS 14.0, *)) {
- GCDeviceBattery *battery = controller.battery;
- if (battery) {
- SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN;
- switch (battery.batteryState) {
- case GCDeviceBatteryStateDischarging:
- {
- float power_level = battery.batteryLevel;
- if (power_level <= 0.05f) {
- ePowerLevel = SDL_JOYSTICK_POWER_EMPTY;
- } else if (power_level <= 0.20f) {
- ePowerLevel = SDL_JOYSTICK_POWER_LOW;
- } else if (power_level <= 0.70f) {
- ePowerLevel = SDL_JOYSTICK_POWER_MEDIUM;
- } else {
- ePowerLevel = SDL_JOYSTICK_POWER_FULL;
- }
- }
- break;
- case GCDeviceBatteryStateCharging:
- ePowerLevel = SDL_JOYSTICK_POWER_WIRED;
- break;
- case GCDeviceBatteryStateFull:
- ePowerLevel = SDL_JOYSTICK_POWER_FULL;
- break;
- default:
- break;
- }
- SDL_PrivateJoystickBatteryLevel(joystick, ePowerLevel);
- }
- }
- #endif /* ENABLE_MFI_BATTERY */
- }
- #endif /* SDL_JOYSTICK_MFI */
- }
- #ifdef ENABLE_MFI_RUMBLE
- @interface SDL_RumbleMotor : NSObject
- @end
- @implementation SDL_RumbleMotor {
- CHHapticEngine *engine API_AVAILABLE(ios(13.0), tvos(14.0));
- id<CHHapticPatternPlayer> player API_AVAILABLE(ios(13.0), tvos(14.0));
- bool active;
- }
- -(void)cleanup
- {
- if (self->player != nil) {
- [self->player cancelAndReturnError:nil];
- self->player = nil;
- }
- if (self->engine != nil) {
- [self->engine stopWithCompletionHandler:nil];
- self->engine = nil;
- }
- }
- -(int)setIntensity:(float)intensity
- {
- @autoreleasepool {
- if (@available(iOS 14.0, tvOS 14.0, *)) {
- NSError *error;
-
- if (self->engine == nil) {
- return SDL_SetError("Haptics engine was stopped");
- }
- if (intensity == 0.0f) {
- if (self->player && self->active) {
- [self->player stopAtTime:0 error:&error];
- }
- self->active = false;
- return 0;
- }
- if (self->player == nil) {
- CHHapticEventParameter *param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
- CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:param, nil] relativeTime:0 duration:GCHapticDurationInfinite];
- CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error];
- if (error != nil) {
- return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]);
- }
- self->player = [self->engine createPlayerWithPattern:pattern error:&error];
- if (error != nil) {
- return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]);
- }
- self->active = false;
- }
- CHHapticDynamicParameter *param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0];
- [self->player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error];
- if (error != nil) {
- return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]);
- }
- if (!self->active) {
- [self->player startAtTime:0 error:&error];
- self->active = true;
- }
- }
- return 0;
- }
- }
- -(id) initWithController:(GCController*)controller locality:(GCHapticsLocality)locality API_AVAILABLE(ios(14.0), tvos(14.0))
- {
- @autoreleasepool {
- NSError *error;
- self->engine = [controller.haptics createEngineWithLocality:locality];
- if (self->engine == nil) {
- SDL_SetError("Couldn't create haptics engine");
- return nil;
- }
- [self->engine startAndReturnError:&error];
- if (error != nil) {
- SDL_SetError("Couldn't start haptics engine");
- return nil;
- }
- __weak typeof(self) weakSelf = self;
- self->engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
- SDL_RumbleMotor *_this = weakSelf;
- if (_this == nil) {
- return;
- }
- _this->player = nil;
- _this->engine = nil;
- };
- self->engine.resetHandler = ^{
- SDL_RumbleMotor *_this = weakSelf;
- if (_this == nil) {
- return;
- }
- _this->player = nil;
- [_this->engine startAndReturnError:nil];
- };
- return self;
- }
- }
- @end
- @interface SDL_RumbleContext : NSObject
- @end
- @implementation SDL_RumbleContext {
- SDL_RumbleMotor *low_frequency_motor;
- SDL_RumbleMotor *high_frequency_motor;
- }
- -(id) initWithLowFrequencyMotor:(SDL_RumbleMotor*)low_frequency_motor andHighFrequencyMotor:(SDL_RumbleMotor*)high_frequency_motor
- {
- self->low_frequency_motor = low_frequency_motor;
- self->high_frequency_motor = high_frequency_motor;
- return self;
- }
- -(int) rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble
- {
- int result = 0;
- result += [self->low_frequency_motor setIntensity:((float)low_frequency_rumble / 65535.0f)];
- result += [self->high_frequency_motor setIntensity:((float)high_frequency_rumble / 65535.0f)];
- return ((result < 0) ? -1 : 0);
- }
- -(void)cleanup
- {
- [self->low_frequency_motor cleanup];
- [self->high_frequency_motor cleanup];
- }
- @end
- static SDL_RumbleContext *IOS_JoystickInitRumble(GCController *controller)
- {
- @autoreleasepool {
- if (@available(iOS 14.0, tvOS 14.0, *)) {
- SDL_RumbleMotor *low_frequency_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
- SDL_RumbleMotor *high_frequency_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
- if (low_frequency_motor && high_frequency_motor) {
- return [[SDL_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor andHighFrequencyMotor:high_frequency_motor];
- }
- }
- }
- return nil;
- }
- #endif /* ENABLE_MFI_RUMBLE */
- static int
- IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
- {
- #ifdef ENABLE_MFI_RUMBLE
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (@available(iOS 14.0, tvOS 14.0, *)) {
- if (!device->rumble && device->controller && device->controller.haptics) {
- SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
- if (rumble) {
- device->rumble = (void *)CFBridgingRetain(rumble);
- }
- }
- }
- if (device->rumble) {
- SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
- return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble];
- } else {
- return SDL_Unsupported();
- }
- #else
- return SDL_Unsupported();
- #endif
- }
- static SDL_bool
- IOS_JoystickHasLED(SDL_Joystick * joystick)
- {
- #ifdef ENABLE_MFI_LIGHT
- @autoreleasepool {
- if (@available(iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = joystick->hwdata->controller;
- GCDeviceLight *light = controller.light;
- if (light) {
- return SDL_TRUE;
- }
- }
- }
- #endif /* ENABLE_MFI_LIGHT */
- return SDL_FALSE;
- }
- static int
- IOS_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
- {
- #ifdef ENABLE_MFI_LIGHT
- @autoreleasepool {
- if (@available(iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = joystick->hwdata->controller;
- GCDeviceLight *light = controller.light;
- if (light) {
- light.color = [[GCColor alloc] initWithRed:(float)red / 255.0f
- green:(float)green / 255.0f
- blue:(float)blue / 255.0f];
- return 0;
- }
- }
- }
- #endif /* ENABLE_MFI_LIGHT */
- return SDL_Unsupported();
- }
- static void
- IOS_JoystickUpdate(SDL_Joystick * joystick)
- {
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return;
- }
-
- if (device->accelerometer) {
- IOS_AccelerometerUpdate(joystick);
- } else if (device->controller) {
- IOS_MFIJoystickUpdate(joystick);
- }
- }
- static void
- IOS_JoystickClose(SDL_Joystick * joystick)
- {
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return;
- }
- device->joystick = NULL;
- @autoreleasepool {
- #ifdef ENABLE_MFI_RUMBLE
- if (device->rumble) {
- SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
- [rumble cleanup];
- CFRelease(device->rumble);
- device->rumble = NULL;
- }
- #endif /* ENABLE_MFI_RUMBLE */
- if (device->accelerometer) {
- #if !TARGET_OS_TV
- [motionManager stopAccelerometerUpdates];
- #endif /* !TARGET_OS_TV */
- } else if (device->controller) {
- #ifdef SDL_JOYSTICK_MFI
- GCController *controller = device->controller;
- controller.controllerPausedHandler = nil;
- controller.playerIndex = -1;
- #endif
- }
- }
- if (device->remote) {
- --SDL_AppleTVRemoteOpenedAsJoystick;
- }
- }
- static void
- IOS_JoystickQuit(void)
- {
- @autoreleasepool {
- #ifdef SDL_JOYSTICK_MFI
- NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
- if (connectObserver) {
- [center removeObserver:connectObserver name:GCControllerDidConnectNotification object:nil];
- connectObserver = nil;
- }
- if (disconnectObserver) {
- [center removeObserver:disconnectObserver name:GCControllerDidDisconnectNotification object:nil];
- disconnectObserver = nil;
- }
- #if TARGET_OS_TV
- SDL_DelHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
- SDL_AppleTVRemoteRotationHintChanged, NULL);
- #endif /* TARGET_OS_TV */
- #endif /* SDL_JOYSTICK_MFI */
- while (deviceList != NULL) {
- IOS_RemoveJoystickDevice(deviceList);
- }
- #if !TARGET_OS_TV
- motionManager = nil;
- #endif /* !TARGET_OS_TV */
- }
- numjoysticks = 0;
- }
- static SDL_bool
- IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
- {
- return SDL_FALSE;
- }
- SDL_JoystickDriver SDL_IOS_JoystickDriver =
- {
- IOS_JoystickInit,
- IOS_JoystickGetCount,
- IOS_JoystickDetect,
- IOS_JoystickGetDeviceName,
- IOS_JoystickGetDevicePlayerIndex,
- IOS_JoystickSetDevicePlayerIndex,
- IOS_JoystickGetDeviceGUID,
- IOS_JoystickGetDeviceInstanceID,
- IOS_JoystickOpen,
- IOS_JoystickRumble,
- IOS_JoystickHasLED,
- IOS_JoystickSetLED,
- IOS_JoystickUpdate,
- IOS_JoystickClose,
- IOS_JoystickQuit,
- IOS_JoystickGetGamepadMapping
- };
- /* vi: set ts=4 sw=4 expandtab: */
|