Linux: Detect joystick hotplug

Interface with libudev to get notified when a new joystick appears.

The code is in IH_Linux_Event rather than IH_Linux_Joystick because it
seemed like the former uses evdev (standard for most devices nowadays)
whereas the latter uses the old joydev API. The result should be the
same in any case...

Implementation is a bit messy due to LinuxInputManager only scanning for
devices from its constructor, this forces RageInput to have an exception
for LinuxInputManager. Possibly with more involved surgery a cleaner
solution could be devised...
This commit is contained in:
Thomas Calvert
2025-01-16 23:10:53 +00:00
committed by teejusb
parent 368c257e95
commit fe3e73ae0d
3 changed files with 84 additions and 3 deletions
@@ -20,6 +20,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/input.h>
#include <libudev.h>
REGISTER_INPUT_HANDLER_CLASS2( LinuxEvent, Linux_Event );
@@ -266,15 +267,33 @@ EventDevice::~EventDevice()
}
InputHandler_Linux_Event::InputHandler_Linux_Event()
: m_NextDevice(DEVICE_JOY10)
: m_InputMutex("InputHandler_Linux")
, m_NextDevice(DEVICE_JOY10)
, m_bShutdown(true)
, m_bDevicesChanged(false)
, m_udev_fd(-1)
{
if(LINUXINPUT == nullptr) LINUXINPUT = new LinuxInputManager;
LINUXINPUT->InitDriver(this);
if( ! g_apEventDevices.empty() ) // LinuxInputManager found at least one valid device for us
StartThread();
m_udev = udev_new();
if( ! m_udev )
LOG->Warn( "Failed to create udev object" );
m_udev_monitor = udev_monitor_new_from_netlink(m_udev, "udev");
if( ! m_udev_monitor )
{
LOG->Warn( "Failed to create udev object" );
udev_unref(m_udev);
}
udev_monitor_filter_add_match_subsystem_devtype(m_udev_monitor, "input", NULL);
udev_monitor_enable_receiving(m_udev_monitor);
m_udev_fd = udev_monitor_get_fd(m_udev_monitor);
// Always start thread, if only to monitor udev events
StartThread();
}
InputHandler_Linux_Event::~InputHandler_Linux_Event()
@@ -284,6 +303,9 @@ InputHandler_Linux_Event::~InputHandler_Linux_Event()
for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
delete g_apEventDevices[i];
g_apEventDevices.clear();
udev_monitor_unref(m_udev_monitor);
udev_unref(m_udev);
}
void InputHandler_Linux_Event::StartThread()
@@ -315,7 +337,11 @@ bool InputHandler_Linux_Event::TryDevice(RString devfile)
if( hotplug ) StartThread();
m_NextDevice = enum_add2(m_NextDevice, 1);
m_InputMutex.Lock();
m_bDevicesChanged = true;
m_InputMutex.Unlock();
return true;
}
else
@@ -343,6 +369,12 @@ void InputHandler_Linux_Event::InputThread()
FD_ZERO( &fdset );
int iMaxFD = -1;
if( m_udev_fd >= 0 )
{
FD_SET( m_udev_fd, &fdset );
iMaxFD = std::max( iMaxFD, m_udev_fd );
}
for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
{
int iFD = g_apEventDevices[i]->m_iFD;
@@ -361,6 +393,31 @@ void InputHandler_Linux_Event::InputThread()
continue;
RageTimer now;
if( FD_ISSET(m_udev_fd, &fdset) )
{
struct udev_device* device = udev_monitor_receive_device(m_udev_monitor);
if( device )
{
const char *action = udev_device_get_action(device);
const char *devnode = udev_device_get_devnode(device);
if( action && devnode && strcmp(action, "add") == 0 )
{
// Check if the device is a joystick
const char *devtype = udev_device_get_property_value(device, "ID_INPUT_JOYSTICK");
if( devtype && strcmp(devtype, "1") == 0 )
{
m_InputMutex.Lock();
m_bDevicesChanged = true;
m_InputMutex.Unlock();
LOG->Info("LinuxEvent: New joystick detected: %s\n", devnode);
}
}
udev_device_unref(device);
}
}
for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
{
if( !g_apEventDevices[i]->IsOpen() )
@@ -375,6 +432,9 @@ void InputHandler_Linux_Event::InputThread()
{
LOG->Warn( "Error reading from %s: %s; disabled", g_apEventDevices[i]->m_sPath.c_str(), strerror(errno) );
g_apEventDevices[i]->Close();
m_InputMutex.Lock();
m_bDevicesChanged = true;
m_InputMutex.Unlock();
continue;
}
@@ -382,6 +442,9 @@ void InputHandler_Linux_Event::InputThread()
{
LOG->Warn("Unexpected packet (size %i != %i) from joystick %i; disabled", ret, (int)sizeof(event), i);
g_apEventDevices[i]->Close();
m_InputMutex.Lock();
m_bDevicesChanged = true;
m_InputMutex.Unlock();
continue;
}
@@ -441,7 +504,9 @@ void InputHandler_Linux_Event::GetDevicesAndDescriptions( std::vector<InputDevic
vDevicesOut.push_back( InputDeviceInfo(pDev->m_Dev, pDev->m_sName) );
}
m_InputMutex.Lock();
m_bDevicesChanged = false;
m_InputMutex.Unlock();
}
/*