Skip to content

Commit

Permalink
Fixed bug 5355 - Add GameController Framework support to macOS
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
slouken committed Nov 21, 2020
1 parent 5e0644c commit 1df593f
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 24 deletions.
24 changes: 22 additions & 2 deletions Xcode/SDL/SDL.xcodeproj/project.pbxproj
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */,
Expand All @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
);
Expand Down Expand Up @@ -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 */,
);
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions include/SDL_config_macosx.h
Expand Up @@ -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 */
Expand Down
4 changes: 4 additions & 0 deletions src/joystick/darwin/SDL_sysjoystick.c
Expand Up @@ -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) {
Expand Down
62 changes: 40 additions & 22 deletions src/joystick/iphoneos/SDL_sysjoystick.m
Expand Up @@ -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"
Expand Down Expand Up @@ -75,7 +77,7 @@ @interface GCMicroGamepad (SDL)
#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
Expand All @@ -89,7 +91,7 @@ @interface GCMicroGamepad (SDL)

#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 */
Expand Down Expand Up @@ -351,7 +353,7 @@ @interface GCMicroGamepad (SDL)
device->instance_id = SDL_GetNextJoystickInstanceID();

if (accelerometer) {
#if TARGET_OS_TV
#if TARGET_OS_TV || TARGET_OS_OSX
SDL_free(device);
return;
#else
Expand Down Expand Up @@ -455,8 +457,8 @@ @interface GCMicroGamepad (SDL)
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);
Expand All @@ -469,6 +471,8 @@ @interface GCMicroGamepad (SDL)
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);
}
Expand Down Expand Up @@ -593,7 +597,7 @@ @interface GCMicroGamepad (SDL)

@autoreleasepool {
if (device->accelerometer) {
#if !TARGET_OS_TV
#if !TARGET_OS_TV && !TARGET_OS_OSX
if (motionManager == nil) {
motionManager = [[CMMotionManager alloc] init];
}
Expand All @@ -614,7 +618,7 @@ @interface GCMicroGamepad (SDL)
}

#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) {
Expand All @@ -639,7 +643,7 @@ @interface GCMicroGamepad (SDL)
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;
Expand Down Expand Up @@ -823,7 +827,7 @@ @interface GCMicroGamepad (SDL)
}

#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];
Expand Down Expand Up @@ -916,7 +920,7 @@ @interface GCMicroGamepad (SDL)
}

#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;
Expand Down Expand Up @@ -960,8 +964,8 @@ @interface SDL_RumbleMotor : NSObject
@end

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

Expand All @@ -980,7 +984,7 @@ -(void)cleanup
-(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) {
Expand Down Expand Up @@ -1026,9 +1030,10 @@ -(int)setIntensity:(float)intensity
}
}

-(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];
Expand Down Expand Up @@ -1084,6 +1089,7 @@ -(id) initWithLowFrequencyMotor:(SDL_RumbleMotor*)low_frequency_motor
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;
Expand Down Expand Up @@ -1124,7 +1130,7 @@ -(void)cleanup
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];
Expand All @@ -1148,7 +1154,7 @@ -(void)cleanup
#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) {
Expand All @@ -1174,7 +1180,7 @@ -(void)cleanup
#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) {
Expand All @@ -1199,7 +1205,7 @@ -(void)cleanup
{
#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) {
Expand All @@ -1217,7 +1223,7 @@ -(void)cleanup
{
#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) {
Expand All @@ -1238,7 +1244,7 @@ -(void)cleanup
{
#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) {
Expand Down Expand Up @@ -1291,7 +1297,7 @@ -(void)cleanup
#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) {
Expand Down Expand Up @@ -1334,7 +1340,7 @@ -(void)cleanup
IOS_RemoveJoystickDevice(deviceList);
}

#if !TARGET_OS_TV
#if !TARGET_OS_TV && !TARGET_OS_OSX
motionManager = nil;
#endif /* !TARGET_OS_TV */
}
Expand All @@ -1348,6 +1354,18 @@ -(void)cleanup
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,
Expand Down

0 comments on commit 1df593f

Please sign in to comment.