-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
On macOS, Xbox controllers (and compatible controllers) are not detected by SDL after version 2.30.8 unless Core Foundation's run loop is processed before SDL initialisation. This appears to be related to changes in Xbox controller handling introduced in commit 63aff8e.
The controller was working in versions prior to 2.30.9 because HID controller detection was used instead. The commit 63aff8e forces all Xbox controllers to use GCController on macOS, which effectively breaks support for controllers because the GCController framework SDL2 codepath doesn't function correctly.
SDL2 Version Information
Last Working Version: SDL 2.30.8
First Broken Version: SDL 2.30.9
System Information
OS: macOS 15.2
Hardware: Apple Silicon (M2)
Controller: Xbox-compatible controller (8BitDo M30)
Vendor ID: 0x045e
Product ID: 0x028e
Steps to Reproduce
Initialise SDL with joystick support
Check SDL_NumJoysticks()
Observe that no controllers are detected
Expected Behaviour
Xbox controllers should be detected immediately after SDL initialisation, as they were in SDL 2.30.8 and earlier.
Actual Behaviour
No controllers are detected unless CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false) is called before SDL initialisation (or it has something to do with timing and ensuring the event loop for device enumeration is processed before SDL_NumJoysticks is called)
Minimal Test Case
#include <SDL2/SDL.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdio.h>
void print_sdl_info(int index) {
SDL_Joystick* joy = SDL_JoystickOpen(index);
if (joy) {
printf("\nSDL reports:\n");
printf(" Name: %s\n", SDL_JoystickName(joy));
SDL_JoystickGUID guid = SDL_JoystickGetGUID(joy);
char guid_str[33];
SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
printf(" GUID: %s\n", guid_str);
if (SDL_IsGameController(index)) {
SDL_GameController* controller = SDL_GameControllerOpen(index);
if (controller) {
SDL_GameControllerType type = SDL_GameControllerGetType(controller);
printf(" Controller Type: ");
switch(type) {
case SDL_CONTROLLER_TYPE_XBOX360: printf("Xbox 360\n"); break;
case SDL_CONTROLLER_TYPE_XBOXONE: printf("Xbox One\n"); break;
default: printf("Other (%d)\n", type); break;
}
SDL_GameControllerClose(controller);
}
}
SDL_JoystickClose(joy);
}
}
int main(void) {
SDL_version version;
SDL_GetVersion(&version);
printf("SDL Version: %d.%d.%d\n", version.major, version.minor, version.patch);
// First try without run loop processing
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
printf("SDL initialisation failed: %s\n", SDL_GetError());
return 1;
}
int num_joysticks = SDL_NumJoysticks();
printf("Without run loop: %d controllers\n", num_joysticks);
for (int i = 0; i < num_joysticks; i++) {
print_sdl_info(i);
}
SDL_Quit();
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) {
printf("SDL initialisation failed: %s\n", SDL_GetError());
return 1;
}
// Now try with run loop processing
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, false);
num_joysticks = SDL_NumJoysticks();
printf("\nWith run loop: %d controllers\n", num_joysticks);
for (int i = 0; i < num_joysticks; i++) {
print_sdl_info(i);
}
SDL_Quit();
return 0;
}
Ouput with 2.30.8
./test
SDL Version: 2.30.8
Without run loop: 1 controllers
SDL reports:
Name: Xbox 360 Controller
GUID: 030078385e0400008e02000014016800
Controller Type: Xbox 360
With run loop: 2 controllers
SDL reports:
Name: Xbox 360 Controller
GUID: 030078385e0400008e02000014016800
Controller Type: Xbox 360
SDL reports:
Name: Controller
GUID: 050000695e040000e0020000f31f6d04
Controller Type: Xbox One
Output with 2.30.10 (2.30.9 is the same):
./test
SDL Version: 2.30.10
Without run loop: 0 controllers
With run loop: 1 controllers
SDL reports:
Name: Controller
GUID: 050000695e040000e0020000f31f6d04
Controller Type: Xbox One