| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
- 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.h"
- #include "../SDL_joystick_c.h"
- #include "../hidapi/SDL_hidapijoystick_c.h"
- #include "../usb_ids.h"
- #include "../../events/SDL_events_c.h"
- #ifdef SDL_VIDEO_DRIVER_UIKIT
- #include "../../video/uikit/SDL_uikitvideo.h"
- #endif
- #include "SDL_mfijoystick_c.h"
- #if defined(SDL_PLATFORM_IOS) && !defined(SDL_PLATFORM_TVOS)
- #import <CoreMotion/CoreMotion.h>
- #endif
- #ifdef SDL_PLATFORM_MACOS
- #include <IOKit/hid/IOHIDManager.h>
- #include <AppKit/NSApplication.h>
- #ifndef NSAppKitVersionNumber10_15
- #define NSAppKitVersionNumber10_15 1894
- #endif
- #endif // SDL_PLATFORM_MACOS
- #import <GameController/GameController.h>
- #ifdef SDL_JOYSTICK_MFI
- static id connectObserver = nil;
- static id disconnectObserver = nil;
- #include <objc/message.h>
- // Fix build errors when using an older SDK by defining these selectors
- @interface GCController (SDL)
- #if !((__IPHONE_OS_VERSION_MAX_ALLOWED >= 140500) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140500) || (__MAC_OS_X_VERSION_MAX_ALLOWED >= 110300))
- @property(class, nonatomic, readwrite) BOOL shouldMonitorBackgroundEvents;
- #endif
- @end
- #import <CoreHaptics/CoreHaptics.h>
- #endif // SDL_JOYSTICK_MFI
- 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;
- }
- #ifdef SDL_JOYSTICK_MFI
- static bool IsControllerPS4(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"DualShock 4"]) {
- return true;
- }
- } else {
- if ([controller.vendorName containsString:@"DUALSHOCK"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerPS5(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"DualSense"]) {
- return true;
- }
- } else {
- if ([controller.vendorName containsString:@"DualSense"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerXbox(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"Xbox One"]) {
- return true;
- }
- } else {
- if ([controller.vendorName containsString:@"Xbox"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerSwitchPro(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"Switch Pro Controller"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerSwitchJoyConL(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L)"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerSwitchJoyConR(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (R)"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerSwitchJoyConPair(GCController *controller)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) {
- return true;
- }
- }
- return false;
- }
- static bool IsControllerStadia(GCController *controller)
- {
- if ([controller.vendorName hasPrefix:@"Stadia"]) {
- return true;
- }
- return false;
- }
- static bool IsControllerBackboneOne(GCController *controller)
- {
- if ([controller.vendorName hasPrefix:@"Backbone One"]) {
- return true;
- }
- return false;
- }
- static void CheckControllerSiriRemote(GCController *controller, int *is_siri_remote)
- {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if ([controller.productCategory hasPrefix:@"Siri Remote"]) {
- *is_siri_remote = 1;
- SDL_sscanf(controller.productCategory.UTF8String, "Siri Remote (%i%*s Generation)", is_siri_remote);
- return;
- }
- }
- *is_siri_remote = 0;
- }
- static bool ElementAlreadyHandled(SDL_JoystickDeviceItem *device, NSString *element, NSDictionary<NSString *, GCControllerElement *> *elements)
- {
- if ([element isEqualToString:@"Left Thumbstick Left"] ||
- [element isEqualToString:@"Left Thumbstick Right"]) {
- if (elements[@"Left Thumbstick X Axis"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Left Thumbstick Up"] ||
- [element isEqualToString:@"Left Thumbstick Down"]) {
- if (elements[@"Left Thumbstick Y Axis"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Right Thumbstick Left"] ||
- [element isEqualToString:@"Right Thumbstick Right"]) {
- if (elements[@"Right Thumbstick X Axis"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Right Thumbstick Up"] ||
- [element isEqualToString:@"Right Thumbstick Down"]) {
- if (elements[@"Right Thumbstick Y Axis"]) {
- return true;
- }
- }
- if (device->is_siri_remote) {
- if ([element isEqualToString:@"Direction Pad Left"] ||
- [element isEqualToString:@"Direction Pad Right"]) {
- if (elements[@"Direction Pad X Axis"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Direction Pad Up"] ||
- [element isEqualToString:@"Direction Pad Down"]) {
- if (elements[@"Direction Pad Y Axis"]) {
- return true;
- }
- }
- } else {
- if ([element isEqualToString:@"Direction Pad X Axis"]) {
- if (elements[@"Direction Pad Left"] &&
- elements[@"Direction Pad Right"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Direction Pad Y Axis"]) {
- if (elements[@"Direction Pad Up"] &&
- elements[@"Direction Pad Down"]) {
- return true;
- }
- }
- }
- if ([element isEqualToString:@"Cardinal Direction Pad X Axis"]) {
- if (elements[@"Cardinal Direction Pad Left"] &&
- elements[@"Cardinal Direction Pad Right"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Cardinal Direction Pad Y Axis"]) {
- if (elements[@"Cardinal Direction Pad Up"] &&
- elements[@"Cardinal Direction Pad Down"]) {
- return true;
- }
- }
- if ([element isEqualToString:@"Touchpad 1 X Axis"] ||
- [element isEqualToString:@"Touchpad 1 Y Axis"] ||
- [element isEqualToString:@"Touchpad 1 Left"] ||
- [element isEqualToString:@"Touchpad 1 Right"] ||
- [element isEqualToString:@"Touchpad 1 Up"] ||
- [element isEqualToString:@"Touchpad 1 Down"] ||
- [element isEqualToString:@"Touchpad 2 X Axis"] ||
- [element isEqualToString:@"Touchpad 2 Y Axis"] ||
- [element isEqualToString:@"Touchpad 2 Left"] ||
- [element isEqualToString:@"Touchpad 2 Right"] ||
- [element isEqualToString:@"Touchpad 2 Up"] ||
- [element isEqualToString:@"Touchpad 2 Down"]) {
- // The touchpad is handled separately
- return true;
- }
- if ([element isEqualToString:@"Button Home"]) {
- if (device->is_switch_joycon_pair) {
- // The Nintendo Switch JoyCon home button doesn't ever show as being held down
- return true;
- }
- #ifdef SDL_PLATFORM_TVOS
- // The OS uses the home button, it's not available to apps
- return true;
- #endif
- }
- if ([element isEqualToString:@"Button Share"]) {
- if (device->is_backbone_one) {
- // The Backbone app uses share button
- return true;
- }
- }
- return false;
- }
- static bool IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controller)
- {
- Uint16 vendor = 0;
- Uint16 product = 0;
- Uint8 subtype = 0;
- const char *name = NULL;
- if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) {
- if (!GCController.shouldMonitorBackgroundEvents) {
- GCController.shouldMonitorBackgroundEvents = YES;
- }
- }
- /* 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);
- #ifdef DEBUG_CONTROLLER_PROFILE
- NSLog(@"Product name: %@\n", controller.vendorName);
- NSLog(@"Product category: %@\n", controller.productCategory);
- NSLog(@"Elements available:\n");
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
- for (id key in controller.physicalInputProfile.buttons) {
- NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
- }
- for (id key in controller.physicalInputProfile.axes) {
- NSLog(@"\tAxis: %@\n", key);
- }
- for (id key in controller.physicalInputProfile.dpads) {
- NSLog(@"\tHat: %@\n", key);
- }
- }
- #endif // DEBUG_CONTROLLER_PROFILE
- device->is_xbox = IsControllerXbox(controller);
- device->is_ps4 = IsControllerPS4(controller);
- device->is_ps5 = IsControllerPS5(controller);
- device->is_switch_pro = IsControllerSwitchPro(controller);
- device->is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller);
- device->is_stadia = IsControllerStadia(controller);
- device->is_backbone_one = IsControllerBackboneOne(controller);
- device->is_switch_joyconL = IsControllerSwitchJoyConL(controller);
- device->is_switch_joyconR = IsControllerSwitchJoyConR(controller);
- #ifdef SDL_JOYSTICK_HIDAPI
- if ((device->is_xbox && (HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE) ||
- HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOX360))) ||
- (device->is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) ||
- (device->is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) ||
- (device->is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) ||
- (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) ||
- (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) ||
- (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) ||
- (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, "")) ||
- (SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) ||
- (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, "")))) {
- // The HIDAPI driver is taking care of this device
- return false;
- }
- #endif
- if (device->is_xbox && SDL_strncmp(name, "GamePad-", 8) == 0) {
- // This is a Steam Virtual Gamepad, which isn't supported by GCController
- return false;
- }
- CheckControllerSiriRemote(controller, &device->is_siri_remote);
- if (device->is_siri_remote && !SDL_GetHintBoolean(SDL_HINT_TV_REMOTE_AS_JOYSTICK, true)) {
- // Ignore remotes, they'll be handled as keyboard input
- return false;
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton] != nil) {
- device->has_dualshock_touchpad = TRUE;
- }
- if (controller.physicalInputProfile.buttons[GCInputXboxPaddleOne] != nil) {
- device->has_xbox_paddles = TRUE;
- }
- if (controller.physicalInputProfile.buttons[@"Button Share"] != nil) {
- device->has_xbox_share_button = TRUE;
- }
- }
- if (device->is_backbone_one) {
- vendor = USB_VENDOR_BACKBONE;
- if (device->is_ps5) {
- product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5;
- } else {
- product = USB_PRODUCT_BACKBONE_ONE_IOS;
- }
- } else if (device->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;
- } else if (device->has_xbox_share_button) {
- // Assume Xbox Series X Controller unless/until GCController flows VID/PID
- product = USB_PRODUCT_XBOX_SERIES_X_BLE;
- } else {
- // Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID
- product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
- }
- } else if (device->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 if (device->is_ps5) {
- vendor = USB_VENDOR_SONY;
- product = USB_PRODUCT_SONY_DS5;
- } else if (device->is_switch_pro) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_PRO;
- device->has_nintendo_buttons = TRUE;
- } else if (device->is_switch_joycon_pair) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
- device->has_nintendo_buttons = TRUE;
- } else if (device->is_switch_joyconL) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT;
- } else if (device->is_switch_joyconR) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT;
- } else if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- vendor = USB_VENDOR_APPLE;
- product = 4;
- subtype = 4;
- } else if (controller.extendedGamepad) {
- vendor = USB_VENDOR_APPLE;
- product = 1;
- subtype = 1;
- #ifdef SDL_PLATFORM_TVOS
- } else if (controller.microGamepad) {
- vendor = USB_VENDOR_APPLE;
- product = 3;
- subtype = 3;
- #endif
- } else {
- // We don't know how to get input events from this device
- return false;
- }
- if (SDL_ShouldIgnoreJoystick(vendor, product, 0, name)) {
- return false;
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
- // Provide both axes and analog buttons as SDL axes
- NSArray *axes = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
- filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
- if (ElementAlreadyHandled(device, (NSString *)object, elements)) {
- return false;
- }
- GCControllerElement *element = elements[object];
- if (element.analog) {
- if ([element isKindOfClass:[GCControllerAxisInput class]] ||
- [element isKindOfClass:[GCControllerButtonInput class]]) {
- return true;
- }
- }
- return false;
- }]];
- NSArray *buttons = [[[elements allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]
- filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
- if (ElementAlreadyHandled(device, (NSString *)object, elements)) {
- return false;
- }
- GCControllerElement *element = elements[object];
- if ([element isKindOfClass:[GCControllerButtonInput class]]) {
- return true;
- }
- return false;
- }]];
- /* Explicitly retain the arrays because SDL_JoystickDeviceItem is a
- * struct, and ARC doesn't work with structs. */
- device->naxes = (int)axes.count;
- device->axes = (__bridge NSArray *)CFBridgingRetain(axes);
- device->nbuttons = (int)buttons.count;
- device->buttons = (__bridge NSArray *)CFBridgingRetain(buttons);
- subtype = 4;
- #ifdef DEBUG_CONTROLLER_PROFILE
- NSLog(@"Elements used:\n", controller.vendorName);
- for (id key in device->buttons) {
- NSLog(@"\tButton: %@ (%s)\n", key, elements[key].analog ? "analog" : "digital");
- }
- for (id key in device->axes) {
- NSLog(@"\tAxis: %@\n", key);
- }
- #endif // DEBUG_CONTROLLER_PROFILE
- #ifdef SDL_PLATFORM_TVOS
- // tvOS turns the menu button into a system gesture, so we grab it here instead
- if (elements[GCInputButtonMenu] && !elements[@"Button Home"]) {
- device->pause_button_index = (int)[device->buttons indexOfObject:GCInputButtonMenu];
- }
- #endif
- } else if (controller.extendedGamepad) {
- GCExtendedGamepad *gamepad = controller.extendedGamepad;
- int nbuttons = 0;
- BOOL has_direct_menu = FALSE;
- // These buttons are part of the original MFi spec
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST);
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_NORTH);
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_SHOULDER);
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER);
- nbuttons += 6;
- // These buttons are available on some newer controllers
- if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
- if (gamepad.leftThumbstickButton) {
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK);
- ++nbuttons;
- }
- if (gamepad.rightThumbstickButton) {
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK);
- ++nbuttons;
- }
- }
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if (gamepad.buttonOptions) {
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_BACK);
- ++nbuttons;
- }
- }
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_START);
- ++nbuttons;
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if (gamepad.buttonMenu) {
- has_direct_menu = TRUE;
- }
- }
- #ifdef SDL_PLATFORM_TVOS
- // The single menu button isn't very reliable, at least as of tvOS 16.1
- if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) == 0) {
- has_direct_menu = FALSE;
- }
- #endif
- if (!has_direct_menu) {
- device->pause_button_index = (nbuttons - 1);
- }
- device->naxes = 6; // 2 thumbsticks and 2 triggers
- device->nhats = 1; // d-pad
- device->nbuttons = nbuttons;
- }
- #ifdef SDL_PLATFORM_TVOS
- else if (controller.microGamepad) {
- int nbuttons = 0;
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_WEST); // Button X on microGamepad
- device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
- nbuttons += 3;
- device->pause_button_index = (nbuttons - 1);
- 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, false);
- }
- #endif
- else {
- // We don't know how to get input events from this device
- return false;
- }
- Uint16 signature;
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- signature = 0;
- signature = SDL_crc16(signature, device->name, SDL_strlen(device->name));
- for (id key in device->axes) {
- const char *string = ((NSString *)key).UTF8String;
- signature = SDL_crc16(signature, string, SDL_strlen(string));
- }
- for (id key in device->buttons) {
- const char *string = ((NSString *)key).UTF8String;
- signature = SDL_crc16(signature, string, SDL_strlen(string));
- }
- } else {
- signature = device->button_mask;
- }
- device->guid = SDL_CreateJoystickGUID(SDL_HARDWARE_BUS_BLUETOOTH, vendor, product, signature, NULL, name, 'm', subtype);
- /* This will be set when the first button press of the controller is
- * detected. */
- controller.playerIndex = -1;
- return true;
- }
- #endif // SDL_JOYSTICK_MFI
- #ifdef SDL_JOYSTICK_MFI
- static void IOS_AddJoystickDevice(GCController *controller)
- {
- SDL_JoystickDeviceItem *device = deviceList;
- 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->instance_id = SDL_GetNextObjectID();
- device->pause_button_index = -1;
- if (controller) {
- #ifdef SDL_JOYSTICK_MFI
- if (!IOS_AddMFIJoystickDevice(device, controller)) {
- SDL_free(device->name);
- SDL_free(device);
- return;
- }
- #else
- SDL_free(device);
- return;
- #endif // SDL_JOYSTICK_MFI
- }
- 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);
- }
- #endif // SDL_JOYSTICK_MFI
- 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 {
- // These were explicitly retained in the struct, so they should be explicitly released before freeing the struct.
- if (device->controller) {
- GCController *controller = CFBridgingRelease((__bridge CFTypeRef)(device->controller));
- controller.controllerPausedHandler = nil;
- device->controller = nil;
- }
- if (device->axes) {
- CFRelease((__bridge CFTypeRef)device->axes);
- device->axes = nil;
- }
- if (device->buttons) {
- CFRelease((__bridge CFTypeRef)device->buttons);
- device->buttons = nil;
- }
- }
- #endif // SDL_JOYSTICK_MFI
- --numjoysticks;
- SDL_PrivateJoystickRemoved(device->instance_id);
- SDL_free(device->name);
- SDL_free(device);
- return next;
- }
- #ifdef SDL_PLATFORM_TVOS
- 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 // SDL_PLATFORM_TVOS
- static bool IOS_JoystickInit(void)
- {
- if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) {
- return true;
- }
- #ifdef SDL_PLATFORM_MACOS
- if (@available(macOS 11.0, *)) {
- // Continue with initialization on macOS 11+
- } else {
- return true;
- }
- #endif
- @autoreleasepool {
- #ifdef SDL_JOYSTICK_MFI
- NSNotificationCenter *center;
- #endif
- #ifdef SDL_JOYSTICK_MFI
- // GameController.framework was added in iOS 7.
- if (![GCController class]) {
- return true;
- }
- /* For whatever reason, this always returns an empty array on
- macOS 11.0.1 */
- for (GCController *controller in [GCController controllers]) {
- IOS_AddJoystickDevice(controller);
- }
- #ifdef SDL_PLATFORM_TVOS
- SDL_AddHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
- SDL_AppleTVRemoteRotationHintChanged, NULL);
- #endif // SDL_PLATFORM_TVOS
- center = [NSNotificationCenter defaultCenter];
- connectObserver = [center addObserverForName:GCControllerDidConnectNotification
- object:nil
- queue:nil
- usingBlock:^(NSNotification *note) {
- GCController *controller = note.object;
- SDL_LockJoysticks();
- IOS_AddJoystickDevice(controller);
- SDL_UnlockJoysticks();
- }];
- disconnectObserver = [center addObserverForName:GCControllerDidDisconnectNotification
- object:nil
- queue:nil
- usingBlock:^(NSNotification *note) {
- GCController *controller = note.object;
- SDL_JoystickDeviceItem *device;
- SDL_LockJoysticks();
- for (device = deviceList; device != NULL; device = device->next) {
- if (device->controller == controller) {
- IOS_RemoveJoystickDevice(device);
- break;
- }
- }
- SDL_UnlockJoysticks();
- }];
- #endif // SDL_JOYSTICK_MFI
- #ifdef SDL_VIDEO_DRIVER_UIKIT
- UIKit_SetGameControllerInteraction(true);
- #endif
- }
- return true;
- }
- static int IOS_JoystickGetCount(void)
- {
- return numjoysticks;
- }
- static void IOS_JoystickDetect(void)
- {
- }
- static bool IOS_JoystickIsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
- {
- // We don't override any other drivers through this method
- return false;
- }
- static const char *IOS_JoystickGetDeviceName(int device_index)
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- return device ? device->name : "Unknown";
- }
- static const char *IOS_JoystickGetDevicePath(int device_index)
- {
- return NULL;
- }
- static int IOS_JoystickGetDeviceSteamVirtualGamepadSlot(int device_index)
- {
- return -1;
- }
- 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_GUID IOS_JoystickGetDeviceGUID(int device_index)
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- SDL_GUID 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 : 0;
- }
- static bool 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->naxes = device->naxes;
- joystick->nhats = device->nhats;
- joystick->nbuttons = device->nbuttons;
- if (device->has_dualshock_touchpad) {
- SDL_PrivateJoystickAddTouchpad(joystick, 2);
- }
- device->joystick = joystick;
- @autoreleasepool {
- #ifdef SDL_JOYSTICK_MFI
- if (device->pause_button_index >= 0) {
- GCController *controller = device->controller;
- controller.controllerPausedHandler = ^(GCController *c) {
- if (joystick->hwdata) {
- joystick->hwdata->pause_button_pressed = SDL_GetTicks();
- }
- };
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = joystick->hwdata->controller;
- GCMotion *motion = controller.motion;
- if (motion && motion.hasRotationRate) {
- SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 0.0f);
- }
- if (motion && motion.hasGravityAndUserAcceleration) {
- SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 0.0f);
- }
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = joystick->hwdata->controller;
- for (id key in controller.physicalInputProfile.buttons) {
- GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
- if ([button isBoundToSystemGesture]) {
- button.preferredSystemGestureState = GCSystemGestureStateDisabled;
- }
- }
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = device->controller;
- if (controller.light) {
- SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, true);
- }
- if (controller.haptics) {
- for (GCHapticsLocality locality in controller.haptics.supportedLocalities) {
- if ([locality isEqualToString:GCHapticsLocalityHandles]) {
- SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN, true);
- } else if ([locality isEqualToString:GCHapticsLocalityTriggers]) {
- SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN, true);
- }
- }
- }
- }
- #endif // SDL_JOYSTICK_MFI
- }
- if (device->is_siri_remote) {
- ++SDL_AppleTVRemoteOpenedAsJoystick;
- }
- return true;
- }
- #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)
- {
- #ifdef SDL_JOYSTICK_MFI
- @autoreleasepool {
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- GCController *controller = device->controller;
- Uint8 hatstate = SDL_HAT_CENTERED;
- int i;
- Uint64 timestamp = SDL_GetTicksNS();
- #ifdef DEBUG_CONTROLLER_STATE
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (controller.physicalInputProfile) {
- for (id key in controller.physicalInputProfile.buttons) {
- GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
- if (button.isPressed)
- NSLog(@"Button %@ = %s\n", key, button.isPressed ? "pressed" : "released");
- }
- for (id key in controller.physicalInputProfile.axes) {
- GCControllerAxisInput *axis = controller.physicalInputProfile.axes[key];
- if (axis.value != 0.0f)
- NSLog(@"Axis %@ = %g\n", key, axis.value);
- }
- for (id key in controller.physicalInputProfile.dpads) {
- GCControllerDirectionPad *dpad = controller.physicalInputProfile.dpads[key];
- if (dpad.up.isPressed || dpad.down.isPressed || dpad.left.isPressed || dpad.right.isPressed) {
- NSLog(@"Hat %@ =%s%s%s%s\n", key,
- dpad.up.isPressed ? " UP" : "",
- dpad.down.isPressed ? " DOWN" : "",
- dpad.left.isPressed ? " LEFT" : "",
- dpad.right.isPressed ? " RIGHT" : "");
- }
- }
- }
- }
- #endif // DEBUG_CONTROLLER_STATE
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
- NSDictionary<NSString *, GCControllerButtonInput *> *buttons = controller.physicalInputProfile.buttons;
- int axis = 0;
- for (id key in device->axes) {
- Sint16 value;
- GCControllerElement *element = elements[key];
- if ([element isKindOfClass:[GCControllerAxisInput class]]) {
- value = (Sint16)([(GCControllerAxisInput *)element value] * 32767);
- } else {
- value = (Sint16)([(GCControllerButtonInput *)element value] * 32767);
- }
- SDL_SendJoystickAxis(timestamp, joystick, axis++, value);
- }
- int button = 0;
- for (id key in device->buttons) {
- bool down;
- if (button == device->pause_button_index) {
- down = (device->pause_button_pressed > 0);
- } else {
- down = buttons[key].isPressed;
- }
- SDL_SendJoystickButton(timestamp, joystick, button++, down);
- }
- } else if (controller.extendedGamepad) {
- bool isstack;
- 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.
- bool *buttons = SDL_small_alloc(bool, joystick->nbuttons, &isstack);
- int button_count = 0;
- if (buttons == NULL) {
- return;
- }
- // 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
- if (@available(macOS 10.14.1, iOS 12.1, tvOS 12.1, *)) {
- if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
- buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
- }
- if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {
- buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
- }
- }
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
- buttons[button_count++] = gamepad.buttonOptions.isPressed;
- }
- }
- if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
- if (device->pause_button_index >= 0) {
- // Guaranteed if buttonMenu is not supported on this OS
- buttons[button_count++] = (device->pause_button_pressed > 0);
- } else {
- if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
- buttons[button_count++] = gamepad.buttonMenu.isPressed;
- }
- }
- }
- hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
- for (i = 0; i < SDL_arraysize(axes); i++) {
- SDL_SendJoystickAxis(timestamp, joystick, i, axes[i]);
- }
- for (i = 0; i < button_count; i++) {
- SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]);
- }
- SDL_small_free(buttons, isstack);
- }
- #ifdef SDL_PLATFORM_TVOS
- 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_SendJoystickAxis(timestamp, joystick, i, axes[i]);
- }
- bool buttons[joystick->nbuttons];
- int button_count = 0;
- buttons[button_count++] = gamepad.buttonA.isPressed;
- buttons[button_count++] = gamepad.buttonX.isPressed;
- buttons[button_count++] = (device->pause_button_pressed > 0);
- for (i = 0; i < button_count; i++) {
- SDL_SendJoystickButton(timestamp, joystick, i, buttons[i]);
- }
- }
- #endif // SDL_PLATFORM_TVOS
- if (joystick->nhats > 0) {
- SDL_SendJoystickHat(timestamp, joystick, 0, hatstate);
- }
- if (device->pause_button_pressed) {
- // The pause callback is instantaneous, so we extend the duration to allow "holding down" by pressing it repeatedly
- const int PAUSE_BUTTON_PRESS_DURATION_MS = 250;
- if (SDL_GetTicks() >= device->pause_button_pressed + PAUSE_BUTTON_PRESS_DURATION_MS) {
- device->pause_button_pressed = 0;
- }
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (device->has_dualshock_touchpad) {
- GCControllerDirectionPad *dpad;
- dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadOne];
- if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) {
- SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
- } else {
- SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, false, 0.0f, 0.0f, 1.0f);
- }
- dpad = controller.physicalInputProfile.dpads[GCInputDualShockTouchpadTwo];
- if (dpad.xAxis.value != 0.f || dpad.yAxis.value != 0.f) {
- SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, true, (1.0f + dpad.xAxis.value) * 0.5f, 1.0f - (1.0f + dpad.yAxis.value) * 0.5f, 1.0f);
- } else {
- SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, false, 0.0f, 0.0f, 1.0f);
- }
- }
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCMotion *motion = controller.motion;
- if (motion && motion.sensorsActive) {
- float data[3];
- if (motion.hasRotationRate) {
- GCRotationRate rate = motion.rotationRate;
- data[0] = rate.x;
- data[1] = rate.z;
- data[2] = -rate.y;
- SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, timestamp, data, 3);
- }
- if (motion.hasGravityAndUserAcceleration) {
- GCAcceleration accel = motion.acceleration;
- data[0] = -accel.x * SDL_STANDARD_GRAVITY;
- data[1] = -accel.y * SDL_STANDARD_GRAVITY;
- data[2] = -accel.z * SDL_STANDARD_GRAVITY;
- SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, data, 3);
- }
- }
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCDeviceBattery *battery = controller.battery;
- if (battery) {
- SDL_PowerState state = SDL_POWERSTATE_UNKNOWN;
- int percent = (int)SDL_roundf(battery.batteryLevel * 100.0f);
- switch (battery.batteryState) {
- case GCDeviceBatteryStateDischarging:
- state = SDL_POWERSTATE_ON_BATTERY;
- break;
- case GCDeviceBatteryStateCharging:
- state = SDL_POWERSTATE_CHARGING;
- break;
- case GCDeviceBatteryStateFull:
- state = SDL_POWERSTATE_CHARGED;
- break;
- default:
- break;
- }
- SDL_SendJoystickPowerInfo(joystick, state, percent);
- }
- }
- }
- #endif // SDL_JOYSTICK_MFI
- }
- #ifdef SDL_JOYSTICK_MFI
- @interface SDL3_RumbleMotor : NSObject
- @property(nonatomic, strong) CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
- @property(nonatomic, strong) id<CHHapticPatternPlayer> player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
- @property bool active;
- @end
- @implementation SDL3_RumbleMotor
- {
- }
- - (void)cleanup
- {
- @autoreleasepool {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (self.player != nil) {
- [self.player cancelAndReturnError:nil];
- self.player = nil;
- }
- if (self.engine != nil) {
- [self.engine stopWithCompletionHandler:nil];
- self.engine = nil;
- }
- }
- }
- }
- - (bool)setIntensity:(float)intensity
- {
- @autoreleasepool {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- NSError *error = nil;
- CHHapticDynamicParameter *param;
- 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 true;
- }
- if (self.player == nil) {
- CHHapticEventParameter *event_param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
- CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:event_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;
- }
- 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 true;
- }
- }
- - (id)initWithController:(GCController *)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0))
- {
- @autoreleasepool {
- NSError *error;
- __weak __typeof(self) weakSelf;
- self = [super init];
- weakSelf = self;
- 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;
- }
- self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
- SDL3_RumbleMotor *_this = weakSelf;
- if (_this == nil) {
- return;
- }
- _this.player = nil;
- _this.engine = nil;
- };
- self.engine.resetHandler = ^{
- SDL3_RumbleMotor *_this = weakSelf;
- if (_this == nil) {
- return;
- }
- _this.player = nil;
- [_this.engine startAndReturnError:nil];
- };
- return self;
- }
- }
- @end
- @interface SDL3_RumbleContext : NSObject
- @property(nonatomic, strong) SDL3_RumbleMotor *lowFrequencyMotor;
- @property(nonatomic, strong) SDL3_RumbleMotor *highFrequencyMotor;
- @property(nonatomic, strong) SDL3_RumbleMotor *leftTriggerMotor;
- @property(nonatomic, strong) SDL3_RumbleMotor *rightTriggerMotor;
- @end
- @implementation SDL3_RumbleContext
- {
- }
- - (id)initWithLowFrequencyMotor:(SDL3_RumbleMotor *)low_frequency_motor
- HighFrequencyMotor:(SDL3_RumbleMotor *)high_frequency_motor
- LeftTriggerMotor:(SDL3_RumbleMotor *)left_trigger_motor
- RightTriggerMotor:(SDL3_RumbleMotor *)right_trigger_motor
- {
- self = [super init];
- self.lowFrequencyMotor = low_frequency_motor;
- self.highFrequencyMotor = high_frequency_motor;
- self.leftTriggerMotor = left_trigger_motor;
- self.rightTriggerMotor = right_trigger_motor;
- return self;
- }
- - (bool)rumbleWithLowFrequency:(Uint16)low_frequency_rumble andHighFrequency:(Uint16)high_frequency_rumble
- {
- bool result = true;
- result &= [self.lowFrequencyMotor setIntensity:((float)low_frequency_rumble / 65535.0f)];
- result &= [self.highFrequencyMotor setIntensity:((float)high_frequency_rumble / 65535.0f)];
- return result;
- }
- - (bool)rumbleLeftTrigger:(Uint16)left_rumble andRightTrigger:(Uint16)right_rumble
- {
- bool result = false;
- if (self.leftTriggerMotor && self.rightTriggerMotor) {
- result &= [self.leftTriggerMotor setIntensity:((float)left_rumble / 65535.0f)];
- result &= [self.rightTriggerMotor setIntensity:((float)right_rumble / 65535.0f)];
- } else {
- result = SDL_Unsupported();
- }
- return result;
- }
- - (void)cleanup
- {
- [self.lowFrequencyMotor cleanup];
- [self.highFrequencyMotor cleanup];
- }
- @end
- static SDL3_RumbleContext *IOS_JoystickInitRumble(GCController *controller)
- {
- @autoreleasepool {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- SDL3_RumbleMotor *low_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftHandle];
- SDL3_RumbleMotor *high_frequency_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightHandle];
- SDL3_RumbleMotor *left_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger];
- SDL3_RumbleMotor *right_trigger_motor = [[SDL3_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityRightTrigger];
- if (low_frequency_motor && high_frequency_motor) {
- return [[SDL3_RumbleContext alloc] initWithLowFrequencyMotor:low_frequency_motor
- HighFrequencyMotor:high_frequency_motor
- LeftTriggerMotor:left_trigger_motor
- RightTriggerMotor:right_trigger_motor];
- }
- }
- }
- return nil;
- }
- #endif // SDL_JOYSTICK_MFI
- static bool IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
- {
- #ifdef SDL_JOYSTICK_MFI
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return SDL_SetError("Controller is no longer connected");
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (!device->rumble && device->controller && device->controller.haptics) {
- SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
- if (rumble) {
- device->rumble = (void *)CFBridgingRetain(rumble);
- }
- }
- }
- if (device->rumble) {
- SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
- return [rumble rumbleWithLowFrequency:low_frequency_rumble andHighFrequency:high_frequency_rumble];
- }
- #endif
- return SDL_Unsupported();
- }
- static bool IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
- {
- #ifdef SDL_JOYSTICK_MFI
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return SDL_SetError("Controller is no longer connected");
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (!device->rumble && device->controller && device->controller.haptics) {
- SDL3_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller);
- if (rumble) {
- device->rumble = (void *)CFBridgingRetain(rumble);
- }
- }
- }
- if (device->rumble) {
- SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
- return [rumble rumbleLeftTrigger:left_rumble andRightTrigger:right_rumble];
- }
- #endif
- return SDL_Unsupported();
- }
- static bool IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
- {
- @autoreleasepool {
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return SDL_SetError("Controller is no longer connected");
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = device->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 true;
- }
- }
- }
- return SDL_Unsupported();
- }
- static bool IOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
- {
- return SDL_Unsupported();
- }
- static bool IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
- {
- @autoreleasepool {
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return SDL_SetError("Controller is no longer connected");
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = device->controller;
- GCMotion *motion = controller.motion;
- if (motion) {
- motion.sensorsActive = enabled ? YES : NO;
- return true;
- }
- }
- }
- return SDL_Unsupported();
- }
- static void IOS_JoystickUpdate(SDL_Joystick *joystick)
- {
- SDL_JoystickDeviceItem *device = joystick->hwdata;
- if (device == NULL) {
- return;
- }
- 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;
- #ifdef SDL_JOYSTICK_MFI
- @autoreleasepool {
- if (device->rumble) {
- SDL3_RumbleContext *rumble = (__bridge SDL3_RumbleContext *)device->rumble;
- [rumble cleanup];
- CFRelease(device->rumble);
- device->rumble = NULL;
- }
- if (device->controller) {
- GCController *controller = device->controller;
- controller.controllerPausedHandler = nil;
- controller.playerIndex = -1;
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- for (id key in controller.physicalInputProfile.buttons) {
- GCControllerButtonInput *button = controller.physicalInputProfile.buttons[key];
- if ([button isBoundToSystemGesture]) {
- button.preferredSystemGestureState = GCSystemGestureStateEnabled;
- }
- }
- }
- }
- }
- #endif // SDL_JOYSTICK_MFI
- if (device->is_siri_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;
- }
- #ifdef SDL_PLATFORM_TVOS
- SDL_RemoveHintCallback(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION,
- SDL_AppleTVRemoteRotationHintChanged, NULL);
- #endif // SDL_PLATFORM_TVOS
- #endif // SDL_JOYSTICK_MFI
- while (deviceList != NULL) {
- IOS_RemoveJoystickDevice(deviceList);
- }
- #ifdef SDL_VIDEO_DRIVER_UIKIT
- UIKit_SetGameControllerInteraction(false);
- #endif
- }
- numjoysticks = 0;
- }
- static bool IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
- {
- SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
- if (device == NULL) {
- return false;
- }
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- int axis = 0;
- for (id key in device->axes) {
- if ([(NSString *)key isEqualToString:@"Left Thumbstick X Axis"] ||
- [(NSString *)key isEqualToString:@"Direction Pad X Axis"]) {
- out->leftx.kind = EMappingKind_Axis;
- out->leftx.target = axis;
- } else if ([(NSString *)key isEqualToString:@"Left Thumbstick Y Axis"] ||
- [(NSString *)key isEqualToString:@"Direction Pad Y Axis"]) {
- out->lefty.kind = EMappingKind_Axis;
- out->lefty.target = axis;
- out->lefty.axis_reversed = true;
- } else if ([(NSString *)key isEqualToString:@"Right Thumbstick X Axis"]) {
- out->rightx.kind = EMappingKind_Axis;
- out->rightx.target = axis;
- } else if ([(NSString *)key isEqualToString:@"Right Thumbstick Y Axis"]) {
- out->righty.kind = EMappingKind_Axis;
- out->righty.target = axis;
- out->righty.axis_reversed = true;
- } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) {
- out->lefttrigger.kind = EMappingKind_Axis;
- out->lefttrigger.target = axis;
- out->lefttrigger.half_axis_positive = true;
- } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) {
- out->righttrigger.kind = EMappingKind_Axis;
- out->righttrigger.target = axis;
- out->righttrigger.half_axis_positive = true;
- }
- ++axis;
- }
- int button = 0;
- for (id key in device->buttons) {
- SDL_InputMapping *mapping = NULL;
- if ([(NSString *)key isEqualToString:GCInputButtonA]) {
- if (device->is_siri_remote > 1) {
- // GCInputButtonA is triggered for any D-Pad press, ignore it in favor of "Button Center"
- } else if (device->has_nintendo_buttons) {
- mapping = &out->b;
- } else {
- mapping = &out->a;
- }
- } else if ([(NSString *)key isEqualToString:GCInputButtonB]) {
- if (device->has_nintendo_buttons) {
- mapping = &out->a;
- } else if (device->is_switch_joyconL || device->is_switch_joyconR) {
- mapping = &out->x;
- } else {
- mapping = &out->b;
- }
- } else if ([(NSString *)key isEqualToString:GCInputButtonX]) {
- if (device->has_nintendo_buttons) {
- mapping = &out->y;
- } else if (device->is_switch_joyconL || device->is_switch_joyconR) {
- mapping = &out->b;
- } else {
- mapping = &out->x;
- }
- } else if ([(NSString *)key isEqualToString:GCInputButtonY]) {
- if (device->has_nintendo_buttons) {
- mapping = &out->x;
- } else {
- mapping = &out->y;
- }
- } else if ([(NSString *)key isEqualToString:@"Direction Pad Left"]) {
- mapping = &out->dpleft;
- } else if ([(NSString *)key isEqualToString:@"Direction Pad Right"]) {
- mapping = &out->dpright;
- } else if ([(NSString *)key isEqualToString:@"Direction Pad Up"]) {
- mapping = &out->dpup;
- } else if ([(NSString *)key isEqualToString:@"Direction Pad Down"]) {
- mapping = &out->dpdown;
- } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Left"]) {
- mapping = &out->dpleft;
- } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Right"]) {
- mapping = &out->dpright;
- } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Up"]) {
- mapping = &out->dpup;
- } else if ([(NSString *)key isEqualToString:@"Cardinal Direction Pad Down"]) {
- mapping = &out->dpdown;
- } else if ([(NSString *)key isEqualToString:GCInputLeftShoulder]) {
- mapping = &out->leftshoulder;
- } else if ([(NSString *)key isEqualToString:GCInputRightShoulder]) {
- mapping = &out->rightshoulder;
- } else if ([(NSString *)key isEqualToString:GCInputLeftThumbstickButton]) {
- mapping = &out->leftstick;
- } else if ([(NSString *)key isEqualToString:GCInputRightThumbstickButton]) {
- mapping = &out->rightstick;
- } else if ([(NSString *)key isEqualToString:@"Button Home"]) {
- mapping = &out->guide;
- } else if ([(NSString *)key isEqualToString:GCInputButtonMenu]) {
- if (device->is_siri_remote) {
- mapping = &out->b;
- } else {
- mapping = &out->start;
- }
- } else if ([(NSString *)key isEqualToString:GCInputButtonOptions]) {
- mapping = &out->back;
- } else if ([(NSString *)key isEqualToString:@"Button Share"]) {
- mapping = &out->misc1;
- } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleOne]) {
- mapping = &out->right_paddle1;
- } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleTwo]) {
- mapping = &out->right_paddle2;
- } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleThree]) {
- mapping = &out->left_paddle1;
- } else if ([(NSString *)key isEqualToString:GCInputXboxPaddleFour]) {
- mapping = &out->left_paddle2;
- } else if ([(NSString *)key isEqualToString:GCInputLeftTrigger]) {
- mapping = &out->lefttrigger;
- } else if ([(NSString *)key isEqualToString:GCInputRightTrigger]) {
- mapping = &out->righttrigger;
- } else if ([(NSString *)key isEqualToString:GCInputDualShockTouchpadButton]) {
- mapping = &out->touchpad;
- } else if ([(NSString *)key isEqualToString:@"Button Center"]) {
- mapping = &out->a;
- }
- if (mapping && mapping->kind == EMappingKind_None) {
- mapping->kind = EMappingKind_Button;
- mapping->target = button;
- }
- ++button;
- }
- return true;
- }
- return false;
- }
- #if defined(SDL_JOYSTICK_MFI) && defined(SDL_PLATFORM_MACOS)
- bool IOS_SupportedHIDDevice(IOHIDDeviceRef device)
- {
- if (!SDL_GetHintBoolean(SDL_HINT_JOYSTICK_MFI, true)) {
- return false;
- }
- if (@available(macOS 11.0, *)) {
- const int MAX_ATTEMPTS = 3;
- for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
- if ([GCController supportsHIDDevice:device]) {
- return true;
- }
- // The framework may not have seen the device yet
- SDL_Delay(10);
- }
- }
- return false;
- }
- #endif
- #ifdef SDL_JOYSTICK_MFI
- /* NOLINTNEXTLINE(readability-non-const-parameter): getCString takes a non-const char* */
- static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *name)
- {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- if (element) {
- [element.sfSymbolsName getCString:name maxLength:255 encoding:NSASCIIStringEncoding];
- }
- }
- }
- static GCControllerDirectionPad *GetDirectionalPadForController(GCController *controller)
- {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- return controller.physicalInputProfile.dpads[GCInputDirectionPad];
- }
- if (controller.extendedGamepad) {
- return controller.extendedGamepad.dpad;
- }
- if (controller.microGamepad) {
- return controller.microGamepad.dpad;
- }
- return nil;
- }
- #endif // SDL_JOYSTICK_MFI
- const char *IOS_GetAppleSFSymbolsNameForButton(SDL_Gamepad *gamepad, SDL_GamepadButton button)
- {
- char elementName[256];
- elementName[0] = '\0';
- #ifdef SDL_JOYSTICK_MFI
- if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
- NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
- switch (button) {
- case SDL_GAMEPAD_BUTTON_SOUTH:
- GetAppleSFSymbolsNameForElement(elements[GCInputButtonA], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_EAST:
- GetAppleSFSymbolsNameForElement(elements[GCInputButtonB], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_WEST:
- GetAppleSFSymbolsNameForElement(elements[GCInputButtonX], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_NORTH:
- GetAppleSFSymbolsNameForElement(elements[GCInputButtonY], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_BACK:
- GetAppleSFSymbolsNameForElement(elements[GCInputButtonOptions], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_GUIDE:
- GetAppleSFSymbolsNameForElement(elements[@"Button Home"], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_START:
- GetAppleSFSymbolsNameForElement(elements[GCInputButtonMenu], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_LEFT_STICK:
- GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstickButton], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_RIGHT_STICK:
- GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstickButton], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
- GetAppleSFSymbolsNameForElement(elements[GCInputLeftShoulder], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
- GetAppleSFSymbolsNameForElement(elements[GCInputRightShoulder], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_DPAD_UP:
- {
- GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
- if (dpad) {
- GetAppleSFSymbolsNameForElement(dpad.up, elementName);
- if (SDL_strlen(elementName) == 0) {
- SDL_strlcpy(elementName, "dpad.up.fill", sizeof(elementName));
- }
- }
- break;
- }
- case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
- {
- GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
- if (dpad) {
- GetAppleSFSymbolsNameForElement(dpad.down, elementName);
- if (SDL_strlen(elementName) == 0) {
- SDL_strlcpy(elementName, "dpad.down.fill", sizeof(elementName));
- }
- }
- break;
- }
- case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
- {
- GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
- if (dpad) {
- GetAppleSFSymbolsNameForElement(dpad.left, elementName);
- if (SDL_strlen(elementName) == 0) {
- SDL_strlcpy(elementName, "dpad.left.fill", sizeof(elementName));
- }
- }
- break;
- }
- case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
- {
- GCControllerDirectionPad *dpad = GetDirectionalPadForController(controller);
- if (dpad) {
- GetAppleSFSymbolsNameForElement(dpad.right, elementName);
- if (SDL_strlen(elementName) == 0) {
- SDL_strlcpy(elementName, "dpad.right.fill", sizeof(elementName));
- }
- }
- break;
- }
- case SDL_GAMEPAD_BUTTON_MISC1:
- GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1:
- GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleOne], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1:
- GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleThree], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2:
- GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleTwo], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2:
- GetAppleSFSymbolsNameForElement(elements[GCInputXboxPaddleFour], elementName);
- break;
- case SDL_GAMEPAD_BUTTON_TOUCHPAD:
- GetAppleSFSymbolsNameForElement(elements[GCInputDualShockTouchpadButton], elementName);
- break;
- default:
- break;
- }
- }
- }
- #endif // SDL_JOYSTICK_MFI
- return *elementName ? SDL_GetPersistentString(elementName) : NULL;
- }
- const char *IOS_GetAppleSFSymbolsNameForAxis(SDL_Gamepad *gamepad, SDL_GamepadAxis axis)
- {
- char elementName[256];
- elementName[0] = '\0';
- #ifdef SDL_JOYSTICK_MFI
- if (gamepad && SDL_GetGamepadJoystick(gamepad)->driver == &SDL_IOS_JoystickDriver) {
- if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) {
- GCController *controller = SDL_GetGamepadJoystick(gamepad)->hwdata->controller;
- NSDictionary<NSString *, GCControllerElement *> *elements = controller.physicalInputProfile.elements;
- switch (axis) {
- case SDL_GAMEPAD_AXIS_LEFTX:
- GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName);
- break;
- case SDL_GAMEPAD_AXIS_LEFTY:
- GetAppleSFSymbolsNameForElement(elements[GCInputLeftThumbstick], elementName);
- break;
- case SDL_GAMEPAD_AXIS_RIGHTX:
- GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName);
- break;
- case SDL_GAMEPAD_AXIS_RIGHTY:
- GetAppleSFSymbolsNameForElement(elements[GCInputRightThumbstick], elementName);
- break;
- case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
- GetAppleSFSymbolsNameForElement(elements[GCInputLeftTrigger], elementName);
- break;
- case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
- GetAppleSFSymbolsNameForElement(elements[GCInputRightTrigger], elementName);
- break;
- default:
- break;
- }
- }
- }
- #endif // SDL_JOYSTICK_MFI
- return *elementName ? SDL_GetPersistentString(elementName) : NULL;
- }
- SDL_JoystickDriver SDL_IOS_JoystickDriver = {
- IOS_JoystickInit,
- IOS_JoystickGetCount,
- IOS_JoystickDetect,
- IOS_JoystickIsDevicePresent,
- IOS_JoystickGetDeviceName,
- IOS_JoystickGetDevicePath,
- IOS_JoystickGetDeviceSteamVirtualGamepadSlot,
- IOS_JoystickGetDevicePlayerIndex,
- IOS_JoystickSetDevicePlayerIndex,
- IOS_JoystickGetDeviceGUID,
- IOS_JoystickGetDeviceInstanceID,
- IOS_JoystickOpen,
- IOS_JoystickRumble,
- IOS_JoystickRumbleTriggers,
- IOS_JoystickSetLED,
- IOS_JoystickSendEffect,
- IOS_JoystickSetSensorsEnabled,
- IOS_JoystickUpdate,
- IOS_JoystickClose,
- IOS_JoystickQuit,
- IOS_JoystickGetGamepadMapping
- };
|