SDL_JoystickNumButtons always returns 0

After getting a SDL_JOYDEVICEADDED event I call SDL_JoystickOpen(event.jdevice.which) which returns non-nil as expected. After that however I don’t get any events from the device and SDL_JoystickNumButtons always returns 0.

This is a USB NES controller and the name is returned as “USB Gamepad” so it must know it got something (SDL_IsGameController is false btw).

How can I debug this? The controller works fine in my emulators. Calling SDL_GetError doesn’t help and just returns “Window data not set” every time I call it.

EDIT: I’m on macOS 13 (M1) and SDL 2.24.1.

I also have a USB NES controller which seems to work just fine.

if (event.type == SDL_JOYDEVICEADDED)
	printf("Is game controller? %s\n", SDL_IsGameController(event.jdevice.which) ? "true" : "false");
	SDL_Joystick* joy = SDL_JoystickOpen(event.jdevice.which);
	printf("Name: %s\n", SDL_JoystickName(joy));
	printf("Buttons: %d\n", SDL_JoystickNumButtons(joy));

I don’t know if it’s the same controller as you have, but the name is the same, except mine is lower case. I use Linux and SDL 2.0.5 so maybe that makes a difference.

I don’t know why it reports 10 buttons.

A = 1
B = 0

The D-pad is treated as axes.

The implementation is totally different for each platform so it’s hard to say. macOS 13 came out only a couple weeks ago so it may all be broken for all I know unless someone else can confirm otherwise.

I tried another wired SNES controller and go the same 0 buttons error. Now I have tried a wireless USB SNES controller and it DOES return 16 buttons and receives events.

Should I report this as a bug with SDL? I know the controller works and even the “gamepad-tool” app which maps buttons recognizes the 2 controller wired controllers which don’t work and believe that was made with SDL (SDL2 Gamepad Tool by General Arcade). I could test on another system (I have an older MacBook which runs Windows too) but I think SDL is bugged.

Are they exposed by the driver as hats instead, maybe (or axes)?

I don’t remember if I checked that and the controller isn’t with me right now. I’ll have to test later. Thanks or the idea. It still seems wrong though that controller buttons could become “hats” instead of buttons. I guess you’re supposed to check for all these things yourself and let the user configure what ever they want as buttons?

I think most games use the higher level SDL_GameController API (and events) when dealing with gamepads. I’d definitely recommend it – you can also use this sort of database to get more gamepads recognized by the API: GitHub - gabomdq/SDL_GameControllerDB: A community sourced database of game controller mappings to be used with SDL2 Game Controller functionality

Usually for gamepads that aren’t recognized even with that sort of database, you’d use the GameController binding APIs in a control options screen in your game to let users set up or modify their gamepad’s bindings.

Typically the lower level joystick API is only really useful for non-gamepad joysticks like flight sticks. As you’ve discovered, different drivers can expose pretty drastically different joystick mappings for gamepads, so the higher level GameController API abstracts that away nicely.

I actually was playing with this again I realized the controller that ARE working are returning true for SDL_IsGameController. So it’s not that I don’t want to use that API but the wired NES controller are returning false.

Looking at my code I can see I was reading SDL_JOYHATMOTION events so if those controllers were HAT I should have gotten some input from them. I’m still leaning towards SDL has some problem with macOS and maybe os 13 in particular.

Because the construction of controllers and its handling has always been a terrible mess. Secondly, this mess results not only from the poor system API to support joysticks, but also from the laziness of OS and game developers. And this condition has persisted for decades.

In short, USB controllers have more buttons visible from the code level than there are physical ones, because the codes assigned to these buttons by the manufacturers are fixed to match pseudo-universal standards. Therefore, the clone of the NES controller has codes 8 and 9 for Select and Start, and the D-Pad is represented as the first two axes to match the pseudo-universal handling of the left analog stick.

So don’t worry if SDL sees more buttons and axes than there actually are, because that’s how the system (e.g. Windows) sees it — this is normal (unfortunately).

The universal mapper built into SDL is fundamentally wrong. You can’t treat all controllers the same way, because each one is different, has a different number of hats, axes, analog sticks and buttons, as well as a different physical arrangement of these manipulators and differently defined manipulator codes (mainly axis and button codes). This will sound controversial, but creating such a universal mapper for all types of controllers is nothing more but asking for trouble.

If, as a game developer, you want to ensure that the support for all types of controllers always is not only correct, but also ~unlimited, then the only sensible solution is a low-level API and your own implementation of the input mapper, operating at the application level. Don’t be lazy — write a code.

Each professional game should allow the use of any input device for control (mouse, keyboard, gamepad), give the ability to assign game functions to any manipulator of any type, and also provide the ability to calibrate them (e.g. setting dead zones, sensitivity, etc.). If the mapper is written well, there will be no problem even with assigning several devices to one player (e.g. mouse+keyboard, mouse+gamepad, gamepad+gamepad etc.), i.e. giving possibilities that are seemingly not useful, but which can allow players to play however they want and players with various disabilities to play comfortably.

This is what I prefer and what I did in my engine — the custom input mapper that is powerful and highly configurable. The engine can handle any joystick, assign devices as the player wants (one per player or several), and assign specific control functions to the manipulators that the player wants to use (the player, not the developer). All of this exists solely because of SDL’s low-level joystick API.

There are two downsides to this approach — you have to write quite a lot of code (around 2kLoC, depends of the programming language) and the player has to set the mapping themselves in order to use the device in the game. However, the advantages are so many that there is practically nothing to think about — freedom of mapping is worth its weight in gold, especially for players with various disabilities.

Ok I got back to use this controller again and all fields are returning 0.

SDL_JoystickName: USB Gamepad
SDL_JoystickGetAttached: TRUE
SDL_JoystickNumButtons: 0
SDL_JoystickNumHats: 0
SDL_JoystickNumAxes: 0
SDL_IsGameController: FALSE

The game pad tool app says it’s a retro link and it works within their app so I think I’m going to file an issue on Github as I still think SDL is broken.

Here’s my GitHub issue for reference USB controller is connected but has no inputs · Issue #6686 · libsdl-org/SDL · GitHub.