SDL2 - GameController events not firing (solved)

I’m currently trying to use SDL’s GameController subsystem to detect removing and attaching controllers, but have come up against a wall. The program won’t fire an event when attaching and removing while running, but if it’s plugged in before the loop starts, then it fires the CONTROLLERATTACHED event. I can see that when I plug in the controller, extra dlls are loaded (particularly deviceaccess.dll), so I was worried that it was just me. So here’s a list of things I did:

  1. I downloaded the controller test program, ran that, and it worked.
  2. I changed the controller test to get the source via cmake, and that still worked.
  3. Breakpoint on the switch statement. No events firing at all.
  4. Adding allow background events hint. No change.
  5. Initializing the event subsystem. Nothing
  6. Initializing the video subsystem. Nothing.
  7. Listening for JOYDEVICE events. No events firing still.
  8. Disabling everything except for the directly relevant files.
  9. Removing key_controller.

Going over the subsystems I was using and comparing them step by step doesn’t seem to reveal any major differences aside from my choice to use a vector instead of an array, wait event instead of pump events, and not using a screen (another library to be added). But that to me still doesn’t explain why no events are being pushed. Everything I read shows that JOYSTICK and GAMECONTROLLER should still fire their events.

Here is the code (Most of the code has been moved to the main file for quick testing):

#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <vector>
#include <iostream>
#include <thread>
#include "KeyBindController.h"
//More includes not relevant

void sdl_loop(KeyBindController* key_controller)
{ 
	bool active = true ;

	SDL_Event event ;

	while( active )
	{
		if( SDL_WaitEvent( &event ) )
		{
			switch( event.type )
			{
//case SDL_JOYDEVICEADDED:
			case SDL_CONTROLLERDEVICEADDED:
				std::cout << "Added device" << std::endl ;
				key_controller->notify_device( &event ) ;
				break ;
			case SDL_CONTROLLERDEVICEREMOVED:
				std::cout << "Removed device" << std::endl ;
				key_controller->notify_device( &event ) ;
				break ;
			case SDL_USEREVENT:
				std::cout << "Quitting SDL\n" ;
				active = false ;
				break ;
			}
		}
	}
}

std::vector<SDL_GameController*> game_controllers ;

SDL_GameController* find_device( SDL_JoystickID _dev_id )
{
	//Loop through devices. Uses vector instead of array.
	for( auto iter = game_controllers.begin() ; iter != game_controllers.end() ; iter++ )
	{
		if( _dev_id == SDL_JoystickInstanceID( SDL_GameControllerGetJoystick( *iter ) ) )
		{
			return *iter ;
		}
	}
	return nullptr ;
}

void add_device( int device_index )
{
	SDL_JoystickID controller_id = SDL_JoystickGetDeviceInstanceID( device_index ) ;
	// Check if controller id exists
	if( controller_id < 0 )
		return ;

	//Check if this exists in the list. If it does, quit this function as we don't need it.
	if( find_device( controller_id ) )
		return ;


	SDL_GameController* controller = SDL_GameControllerOpen( device_index ) ;
	// Check if controller was created
	if( !controller )
		return ;
	game_controllers.push_back( controller ) ;
}

void remove_device( SDL_JoystickID _controller )
{
	SDL_GameController* control_p = find_device( _controller ) ;

	// Check for nullptr
	if( !control_p )
		return ;

	//Search through vector
	for( auto iter = game_controllers.begin() ; iter != game_controllers.end() ; iter++ )
	{
		//Erase item
		if( *iter == control_p )
		{
			game_controllers.erase( iter ) ;
			return ;
		}
	}
}

int main()
{
	//Start testing
	if( SDL_Init( SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) < 0 )
	{
		std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl ;
		return 1 ;
	}
        //Add in the initial gamecontrollers
	for( int iter = 0 ; iter < SDL_NumJoysticks() ; iter++ )
	{
		if( SDL_IsGameController( iter ) )
		{
			add_device( iter ) ;
		}
	}
//key1 can be removed from this whole thing, nothing changes
	KeyBindController key1(...);
	key1.import( ...) ;

	std::thread sdl( sdl_loop, &key1 ) ;
        //Another thread here fires the USEREVENT that results in quitting when given a text input
	sdl.join() ;
	for( auto iter = game_controllers.begin() ; iter != game_controllers.end() ; iter++ )
	{
		SDL_GameControllerClose( ( *iter ) ) ;
	}
	SDL_Quit() ;
	return 0;
}

Any suggestions or help would be appreciated.

Does KeyBindController::notify_device call add_device?

Yes, it does. Forgot to replace it in the example, but it never occured to me because it’s never reached since no events get called. It could simply be replaced with add_device(event.cdevice.which) for adding and remove_device(event.cdevice.which) for removing.

Does it work if you call sdl_loop directly from main?

sdl_loop(&key1);

Unfortunately not. I initially had it like that, but moved to threads because I wanted to take input and test the quit event.

OK, the reason I asked was because some SDL functions are not thread-safe and can only be safely called from the main thread.

Where is the “controller test program” that you you said worked for you?

I simplified your code into something that I could run.

#define SDL_MAIN_HANDLED
#include <SDL.h>
#include <vector>
#include <iostream>
#include <thread>

void sdl_loop()
{ 
	bool active = true ;

	SDL_Event event ;

	while( active )
	{
		if( SDL_WaitEvent( &event ) )
		{
			switch( event.type )
			{
			case SDL_CONTROLLERDEVICEADDED:
				std::cout << "Added device" << std::endl ;
				break ;
			case SDL_CONTROLLERDEVICEREMOVED:
				std::cout << "Removed device" << std::endl ;
				break ;
			case SDL_USEREVENT:
				std::cout << "Quitting SDL\n" ;
				active = false ;
				break ;
			}
		}
	}
}



int main()
{
	//Start testing
	if( SDL_Init( SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER ) < 0 )
	{
		std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl ;
		return 1 ;
	}

	sdl_loop();
	
	SDL_Quit() ;
	return 0;
}

This works fine for me on Linux. If it works for you too then your problem must be in the part of the code that you haven’t shown. Otherwise it would be interesting to compare it against the example that you said worked for you to see what the difference is.

Gave the wrong link

I tried the code you simplified down, and plugging in/removing still does the same thing. I can see it load the dlls in VS22, but the events don’t fire.

I finally figured it out. It does require the SDL_INIT_VIDEO. However, I may have had a bug when I tried it earlier, and the docs don’t really indicate that it is required. Thank you for the help you provided.