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.
This commit is contained in:
Sam Lantinga 2020-11-21 13:15:33 -08:00
parent 5e0644c15a
commit 1df593fb16
4 changed files with 67 additions and 24 deletions

View file

@ -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;

View file

@ -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 */

View file

@ -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) {

View file

@ -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<CHHapticPatternPlayer> player API_AVAILABLE(ios(13.0), tvos(14.0));
CHHapticEngine *engine API_AVAILABLE(macos(11.0), ios(13.0), tvos(14.0));
id<CHHapticPatternPlayer> 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,