From 1df593fb16c931f6bd5aecea1fec36a31533fce2 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 21 Nov 2020 13:15:33 -0800 Subject: [PATCH] Fixed bug 5355 - Add GameController Framework support to macOS C.W. Betts This patch adds support to the GameController framework on macOS Big Sur and later, adding support for MFi controllers as well as rumble support for PS4 and Xbox One. There is some code to make sure that the IOKit joystick handler doesn't include two controllers at once. While the GameController framework is present in earlier versions of macOS, there was no public, approved way of checking if a specific IOHIDDevice is a controller that GameController could handle. This was changed in Big Sur. --- Xcode/SDL/SDL.xcodeproj/project.pbxproj | 24 +++++++++- include/SDL_config_macosx.h | 1 + src/joystick/darwin/SDL_sysjoystick.c | 4 ++ src/joystick/iphoneos/SDL_sysjoystick.m | 62 ++++++++++++++++--------- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index c7f1827b4..bc1961e8a 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -12,6 +12,16 @@ 00CFA89D106B4BA100758660 /* ForceFeedback.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00CFA89C106B4BA100758660 /* ForceFeedback.framework */; }; 00D0D08410675DD9004B05EF /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; }; 00D0D0D810675E46004B05EF /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 007317C10858E15000B2BC32 /* Carbon.framework */; }; + 552673EB2546054600085751 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 552673EC2546055000085751 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 5563A8722559F25300722F7F /* SDL_sysjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7AC23E2513E00DCD162 /* SDL_sysjoystick_c.h */; }; + 5563A87D2559F25400722F7F /* SDL_sysjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7AC23E2513E00DCD162 /* SDL_sysjoystick_c.h */; }; + 5563A8882559F25500722F7F /* SDL_sysjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = A7D8A7AC23E2513E00DCD162 /* SDL_sysjoystick_c.h */; }; + 557D0CBB2545829E003913E3 /* SDL_sysjoystick.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7AB23E2513E00DCD162 /* SDL_sysjoystick.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 557D0CC6254582A9003913E3 /* SDL_sysjoystick.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7AB23E2513E00DCD162 /* SDL_sysjoystick.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 557D0CD1254582AA003913E3 /* SDL_sysjoystick.m in Sources */ = {isa = PBXBuildFile; fileRef = A7D8A7AB23E2513E00DCD162 /* SDL_sysjoystick.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F37DC5F225350EBC0002E6F7 /* CoreHaptics.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A75FDABD23E28B6200529352 /* GameController.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 560572062473687700B46B66 /* SDL_syslocale.m in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CC246274CB00718109 /* SDL_syslocale.m */; }; 560572072473687800B46B66 /* SDL_syslocale.m in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CC246274CB00718109 /* SDL_syslocale.m */; }; 560572092473687900B46B66 /* SDL_syslocale.m in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CC246274CB00718109 /* SDL_syslocale.m */; }; @@ -4681,6 +4691,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 557D0CFB254586D7003913E3 /* GameController.framework in Frameworks */, + 557D0CFA254586CA003913E3 /* CoreHaptics.framework in Frameworks */, 564624381FF821DA0074AC87 /* Metal.framework in Frameworks */, 564624361FF821C20074AC87 /* QuartzCore.framework in Frameworks */, A7381E971D8B6A0300B177DD /* AudioToolbox.framework in Frameworks */, @@ -4706,6 +4718,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 552673EC2546055000085751 /* CoreHaptics.framework in Frameworks */, + 552673EB2546054600085751 /* GameController.framework in Frameworks */, 5646243C1FF822170074AC87 /* Metal.framework in Frameworks */, 5646243B1FF822100074AC87 /* QuartzCore.framework in Frameworks */, 56C5237F1D8F4985001F2F30 /* CoreAudio.framework in Frameworks */, @@ -7521,6 +7535,7 @@ AA7557FC1595D4D800BBD41B /* close_code.h in Headers */, A7D8B5B723E2514300DCD162 /* controller_type.h in Headers */, A7D8BB4B23E2514500DCD162 /* default_cursor.h in Headers */, + 5563A8722559F25300722F7F /* SDL_sysjoystick_c.h in Headers */, A7D8B1D623E2514200DCD162 /* edid.h in Headers */, A7D8B23C23E2514200DCD162 /* egl.h in Headers */, A7D8B24223E2514200DCD162 /* eglext.h in Headers */, @@ -7679,6 +7694,7 @@ AA75582B1595D4D800BBD41B /* SDL_mouse.h in Headers */, 560572192473688C00B46B66 /* SDL_syslocale.h in Headers */, AA75582D1595D4D800BBD41B /* SDL_mutex.h in Headers */, + 5563A87D2559F25400722F7F /* SDL_sysjoystick_c.h in Headers */, A7D8B3B323E2514200DCD162 /* SDL_yuv_c.h in Headers */, A7D8BBA223E2514500DCD162 /* scancodes_xfree86.h in Headers */, A7D8B5D823E2514300DCD162 /* SDL_syspower.h in Headers */, @@ -7921,6 +7937,7 @@ A7D8B21D23E2514200DCD162 /* imKStoUCS.h in Headers */, 5605721B2473688D00B46B66 /* SDL_syslocale.h in Headers */, A7D8AB6023E2514100DCD162 /* SDL_offscreenevents_c.h in Headers */, + 5563A8882559F25500722F7F /* SDL_sysjoystick_c.h in Headers */, A7D8B1B123E2514200DCD162 /* SDL_x11sym.h in Headers */, A7D8B8D123E2514400DCD162 /* SDL_coreaudio.h in Headers */, A7D8BA1E23E2514400DCD162 /* SDL_draw.h in Headers */, @@ -9769,6 +9786,7 @@ A7D8AF0C23E2514100DCD162 /* SDL_cocoaclipboard.m in Sources */, A7D8BBE523E2574800DCD162 /* SDL_uikitview.m in Sources */, A7D8BBE923E2574800DCD162 /* SDL_uikitvulkan.m in Sources */, + 557D0CBB2545829E003913E3 /* SDL_sysjoystick.m in Sources */, A7D8ABCD23E2514100DCD162 /* SDL_blit_slow.c in Sources */, A7D8BA9723E2514400DCD162 /* s_copysign.c in Sources */, A7D8AAB623E2514100DCD162 /* SDL_haptic.c in Sources */, @@ -10100,6 +10118,7 @@ A7D8A94E23E2514000DCD162 /* SDL.c in Sources */, A7D8B15B23E2514200DCD162 /* SDL_x11opengl.c in Sources */, A7D8BBF823E2574800DCD162 /* SDL_uikitmodes.m in Sources */, + 557D0CC6254582A9003913E3 /* SDL_sysjoystick.m in Sources */, A7D8AEA323E2514100DCD162 /* SDL_cocoavulkan.m in Sources */, A7D8AB6423E2514100DCD162 /* SDL_offscreenwindow.c in Sources */, ); @@ -10300,6 +10319,7 @@ A7D8A95023E2514000DCD162 /* SDL.c in Sources */, A7D8B15D23E2514200DCD162 /* SDL_x11opengl.c in Sources */, A7D8AEA523E2514100DCD162 /* SDL_cocoavulkan.m in Sources */, + 557D0CD1254582AA003913E3 /* SDL_sysjoystick.m in Sources */, A7D8AC6823E2514100DCD162 /* SDL_uikitappdelegate.m in Sources */, A7D8AB6623E2514100DCD162 /* SDL_offscreenwindow.c in Sources */, ); @@ -10371,7 +10391,7 @@ INFOPLIST_FILE = "Info-Framework.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.6; + MACOSX_DEPLOYMENT_TARGET = 10.9; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL2; PRODUCT_NAME = SDL2; STRIP_STYLE = "non-global"; @@ -10450,7 +10470,7 @@ INFOPLIST_FILE = "Info-Framework.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.6; + MACOSX_DEPLOYMENT_TARGET = 10.9; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.libsdl.SDL2; PRODUCT_NAME = SDL2; diff --git a/include/SDL_config_macosx.h b/include/SDL_config_macosx.h index 5da22b7b2..55ac0e11c 100644 --- a/include/SDL_config_macosx.h +++ b/include/SDL_config_macosx.h @@ -144,6 +144,7 @@ #define SDL_JOYSTICK_HIDAPI 1 #define SDL_JOYSTICK_IOKIT 1 #define SDL_JOYSTICK_VIRTUAL 1 +#define SDL_JOYSTICK_MFI 1 #define SDL_HAPTIC_IOKIT 1 /* Enable the dummy sensor driver */ diff --git a/src/joystick/darwin/SDL_sysjoystick.c b/src/joystick/darwin/SDL_sysjoystick.c index 8bea24410..66b93db30 100644 --- a/src/joystick/darwin/SDL_sysjoystick.c +++ b/src/joystick/darwin/SDL_sysjoystick.c @@ -526,6 +526,10 @@ GetDeviceInfo(IOHIDDeviceRef hidDevice, recDevice *pDevice) static SDL_bool JoystickAlreadyKnown(IOHIDDeviceRef ioHIDDeviceObject) { + extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device); + if (IOS_SupportedHIDDevice(ioHIDDeviceObject)) { + return SDL_TRUE; + } recDevice *i; for (i = gpDeviceList; i != NULL; i = i->pNext) { if (i->deviceRef == ioHIDDeviceObject) { diff --git a/src/joystick/iphoneos/SDL_sysjoystick.m b/src/joystick/iphoneos/SDL_sysjoystick.m index 3a4c73f39..e4996fc07 100644 --- a/src/joystick/iphoneos/SDL_sysjoystick.m +++ b/src/joystick/iphoneos/SDL_sysjoystick.m @@ -23,8 +23,10 @@ /* This is the iOS implementation of the SDL joystick API */ #include "SDL_sysjoystick_c.h" +#if !TARGET_OS_OSX /* needed for SDL_IPHONE_MAX_GFORCE macro */ #include "../../../include/SDL_config_iphoneos.h" +#endif #include "SDL_assert.h" #include "SDL_events.h" @@ -75,7 +77,7 @@ static id disconnectObserver = nil; #endif @end -#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000) +#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000) || (__APPLETV_OS_VERSION_MAX_ALLOWED >= 140000) || (__MAC_OS_VERSION_MAX_ALLOWED > 1500000) || (__MAC_OS_X_VERSION_MAX_ALLOWED > 101600) #define ENABLE_MFI_BATTERY #define ENABLE_MFI_RUMBLE #define ENABLE_MFI_LIGHT @@ -89,7 +91,7 @@ static id disconnectObserver = nil; #endif /* SDL_JOYSTICK_MFI */ -#if !TARGET_OS_TV +#if !TARGET_OS_TV && !TARGET_OS_OSX static const char *accelerometerName = "iOS Accelerometer"; static CMMotionManager *motionManager = nil; #endif /* !TARGET_OS_TV */ @@ -351,7 +353,7 @@ IOS_AddJoystickDevice(GCController *controller, SDL_bool accelerometer) device->instance_id = SDL_GetNextJoystickInstanceID(); if (accelerometer) { -#if TARGET_OS_TV +#if TARGET_OS_TV || TARGET_OS_OSX SDL_free(device); return; #else @@ -455,8 +457,8 @@ SDL_AppleTVRemoteRotationHintChanged(void *udata, const char *name, const char * static int IOS_JoystickInit(void) { - @autoreleasepool { -#if !TARGET_OS_TV + if (@available(macos 11.0, *)) @autoreleasepool { +#if !TARGET_OS_TV && !TARGET_OS_OSX if (SDL_GetHintBoolean(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, SDL_TRUE)) { /* Default behavior, accelerometer as joystick */ IOS_AddJoystickDevice(nil, SDL_TRUE); @@ -469,6 +471,8 @@ IOS_JoystickInit(void) return 0; } + /* For whatever reason, this always returns an empty array on + macOS 11.0.1 */ for (GCController *controller in [GCController controllers]) { IOS_AddJoystickDevice(controller, SDL_FALSE); } @@ -593,7 +597,7 @@ IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) @autoreleasepool { if (device->accelerometer) { -#if !TARGET_OS_TV +#if !TARGET_OS_TV && !TARGET_OS_OSX if (motionManager == nil) { motionManager = [[CMMotionManager alloc] init]; } @@ -614,7 +618,7 @@ IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) } #ifdef ENABLE_MFI_SENSORS - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; GCMotion *motion = controller.motion; if (motion && motion.hasRotationRate) { @@ -639,7 +643,7 @@ IOS_JoystickOpen(SDL_Joystick *joystick, int device_index) static void IOS_AccelerometerUpdate(SDL_Joystick *joystick) { -#if !TARGET_OS_TV +#if !TARGET_OS_TV && !TARGET_OS_OSX const float maxgforce = SDL_IPHONE_MAX_GFORCE; const SInt16 maxsint16 = 0x7FFF; CMAcceleration accel; @@ -823,7 +827,7 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } #ifdef ENABLE_MFI_SENSORS - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCMotion *motion = controller.motion; if (motion && motion.sensorsActive) { float data[3]; @@ -916,7 +920,7 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } #ifdef ENABLE_MFI_BATTERY - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { GCDeviceBattery *battery = controller.battery; if (battery) { SDL_JoystickPowerLevel ePowerLevel = SDL_JOYSTICK_POWER_UNKNOWN; @@ -960,8 +964,8 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) @end @implementation SDL_RumbleMotor { - CHHapticEngine *engine API_AVAILABLE(ios(13.0), tvos(14.0)); - id player API_AVAILABLE(ios(13.0), tvos(14.0)); + CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); + id player API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0)); bool active; } @@ -980,7 +984,7 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) -(int)setIntensity:(float)intensity { @autoreleasepool { - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { NSError *error; if (self->engine == nil) { @@ -1026,9 +1030,10 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) } } --(id) initWithController:(GCController*)controller locality:(GCHapticsLocality)locality API_AVAILABLE(ios(14.0), tvos(14.0)) +-(id) initWithController:(GCController*)controller locality:(GCHapticsLocality)locality API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) { @autoreleasepool { + self = [super init]; NSError *error; self->engine = [controller.haptics createEngineWithLocality:locality]; @@ -1084,6 +1089,7 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) LeftTriggerMotor:(SDL_RumbleMotor*)left_trigger_motor RightTriggerMotor:(SDL_RumbleMotor*)right_trigger_motor { + self = [super init]; self->low_frequency_motor = low_frequency_motor; self->high_frequency_motor = high_frequency_motor; self->left_trigger_motor = left_trigger_motor; @@ -1124,7 +1130,7 @@ IOS_MFIJoystickUpdate(SDL_Joystick *joystick) static SDL_RumbleContext *IOS_JoystickInitRumble(GCController *controller) { @autoreleasepool { - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, 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]; SDL_RumbleMotor *left_trigger_motor = [[SDL_RumbleMotor alloc] initWithController:controller locality:GCHapticsLocalityLeftTrigger]; @@ -1148,7 +1154,7 @@ IOS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 h #ifdef ENABLE_MFI_RUMBLE SDL_JoystickDeviceItem *device = joystick->hwdata; - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (!device->rumble && device->controller && device->controller.haptics) { SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); if (rumble) { @@ -1174,7 +1180,7 @@ IOS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 ri #ifdef ENABLE_MFI_RUMBLE SDL_JoystickDeviceItem *device = joystick->hwdata; - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { if (!device->rumble && device->controller && device->controller.haptics) { SDL_RumbleContext *rumble = IOS_JoystickInitRumble(device->controller); if (rumble) { @@ -1199,7 +1205,7 @@ IOS_JoystickHasLED(SDL_Joystick *joystick) { #ifdef ENABLE_MFI_LIGHT @autoreleasepool { - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; GCDeviceLight *light = controller.light; if (light) { @@ -1217,7 +1223,7 @@ IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { #ifdef ENABLE_MFI_LIGHT @autoreleasepool { - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macos 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; GCDeviceLight *light = controller.light; if (light) { @@ -1238,7 +1244,7 @@ IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled) { #ifdef ENABLE_MFI_SENSORS @autoreleasepool { - if (@available(iOS 14.0, tvOS 14.0, *)) { + if (@available(macOS 11.0, iOS 14.0, tvOS 14.0, *)) { GCController *controller = joystick->hwdata->controller; GCMotion *motion = controller.motion; if (motion) { @@ -1291,7 +1297,7 @@ IOS_JoystickClose(SDL_Joystick *joystick) #endif /* ENABLE_MFI_RUMBLE */ if (device->accelerometer) { -#if !TARGET_OS_TV +#if !TARGET_OS_TV && !TARGET_OS_OSX [motionManager stopAccelerometerUpdates]; #endif /* !TARGET_OS_TV */ } else if (device->controller) { @@ -1334,7 +1340,7 @@ IOS_JoystickQuit(void) IOS_RemoveJoystickDevice(deviceList); } -#if !TARGET_OS_TV +#if !TARGET_OS_TV && !TARGET_OS_OSX motionManager = nil; #endif /* !TARGET_OS_TV */ } @@ -1348,6 +1354,18 @@ IOS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) return SDL_FALSE; } +#if TARGET_OS_OSX +extern SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device); +SDL_bool IOS_SupportedHIDDevice(IOHIDDeviceRef device) +{ + if (@available(macOS 11.0, *)) { + return [GCController supportsHIDDevice:device] ? SDL_TRUE: SDL_FALSE; + } else { + return SDL_FALSE; + } +} +#endif + SDL_JoystickDriver SDL_IOS_JoystickDriver = { IOS_JoystickInit,