Skip to content

Xbox Controller Detection Fails on macOS Without Run Loop Processing #11742

@palmerj

Description

@palmerj

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions