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-03-02 11:41:43 -08:00
committed by teejusb
parent 368c257e95
commit fe3e73ae0d
3 changed files with 84 additions and 3 deletions
+10
View File
@@ -6,6 +6,11 @@
#include "LuaManager.h" #include "LuaManager.h"
#include "LocalizedString.h" #include "LocalizedString.h"
#if LINUX
// FIXME: bit gross to include this here, see GH issue #73
#include "arch/InputHandler/LinuxInputManager.h"
#endif
#include <vector> #include <vector>
@@ -60,6 +65,11 @@ void RageInput::LoadDrivers()
m_InputHandlers.clear(); m_InputHandlers.clear();
g_mapDeviceToHandler.clear(); g_mapDeviceToHandler.clear();
#if LINUX
// Recreating this forces it to re-scan for devices.
LINUXINPUT = new LinuxInputManager;
#endif
// Init optional devices. // Init optional devices.
std::vector<InputHandler *> apDevices; std::vector<InputHandler *> apDevices;
@@ -20,6 +20,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <linux/input.h> #include <linux/input.h>
#include <libudev.h>
REGISTER_INPUT_HANDLER_CLASS2( LinuxEvent, Linux_Event ); REGISTER_INPUT_HANDLER_CLASS2( LinuxEvent, Linux_Event );
@@ -266,14 +267,32 @@ EventDevice::~EventDevice()
} }
InputHandler_Linux_Event::InputHandler_Linux_Event() InputHandler_Linux_Event::InputHandler_Linux_Event()
: m_NextDevice(DEVICE_JOY10) : m_InputMutex("InputHandler_Linux")
, m_NextDevice(DEVICE_JOY10)
, m_bShutdown(true) , m_bShutdown(true)
, m_bDevicesChanged(false) , m_bDevicesChanged(false)
, m_udev_fd(-1)
{ {
if(LINUXINPUT == nullptr) LINUXINPUT = new LinuxInputManager; if(LINUXINPUT == nullptr) LINUXINPUT = new LinuxInputManager;
LINUXINPUT->InitDriver(this); LINUXINPUT->InitDriver(this);
if( ! g_apEventDevices.empty() ) // LinuxInputManager found at least one valid device for us 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(); StartThread();
} }
@@ -284,6 +303,9 @@ InputHandler_Linux_Event::~InputHandler_Linux_Event()
for( int i = 0; i < (int) g_apEventDevices.size(); ++i ) for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
delete g_apEventDevices[i]; delete g_apEventDevices[i];
g_apEventDevices.clear(); g_apEventDevices.clear();
udev_monitor_unref(m_udev_monitor);
udev_unref(m_udev);
} }
void InputHandler_Linux_Event::StartThread() void InputHandler_Linux_Event::StartThread()
@@ -315,7 +337,11 @@ bool InputHandler_Linux_Event::TryDevice(RString devfile)
if( hotplug ) StartThread(); if( hotplug ) StartThread();
m_NextDevice = enum_add2(m_NextDevice, 1); m_NextDevice = enum_add2(m_NextDevice, 1);
m_InputMutex.Lock();
m_bDevicesChanged = true; m_bDevicesChanged = true;
m_InputMutex.Unlock();
return true; return true;
} }
else else
@@ -343,6 +369,12 @@ void InputHandler_Linux_Event::InputThread()
FD_ZERO( &fdset ); FD_ZERO( &fdset );
int iMaxFD = -1; 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 ) for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
{ {
int iFD = g_apEventDevices[i]->m_iFD; int iFD = g_apEventDevices[i]->m_iFD;
@@ -361,6 +393,31 @@ void InputHandler_Linux_Event::InputThread()
continue; continue;
RageTimer now; 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 ) for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
{ {
if( !g_apEventDevices[i]->IsOpen() ) 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) ); LOG->Warn( "Error reading from %s: %s; disabled", g_apEventDevices[i]->m_sPath.c_str(), strerror(errno) );
g_apEventDevices[i]->Close(); g_apEventDevices[i]->Close();
m_InputMutex.Lock();
m_bDevicesChanged = true;
m_InputMutex.Unlock();
continue; 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); LOG->Warn("Unexpected packet (size %i != %i) from joystick %i; disabled", ret, (int)sizeof(event), i);
g_apEventDevices[i]->Close(); g_apEventDevices[i]->Close();
m_InputMutex.Lock();
m_bDevicesChanged = true;
m_InputMutex.Unlock();
continue; continue;
} }
@@ -441,7 +504,9 @@ void InputHandler_Linux_Event::GetDevicesAndDescriptions( std::vector<InputDevic
vDevicesOut.push_back( InputDeviceInfo(pDev->m_Dev, pDev->m_sName) ); vDevicesOut.push_back( InputDeviceInfo(pDev->m_Dev, pDev->m_sName) );
} }
m_InputMutex.Lock();
m_bDevicesChanged = false; m_bDevicesChanged = false;
m_InputMutex.Unlock();
} }
/* /*
@@ -25,8 +25,14 @@ private:
void InputThread(); void InputThread();
RageThread m_InputThread; RageThread m_InputThread;
// Currently only m_bDevicesChanged is guarded by this mutex
RageMutex m_InputMutex;
InputDevice m_NextDevice; InputDevice m_NextDevice;
bool m_bShutdown, m_bDevicesChanged; bool m_bShutdown, m_bDevicesChanged;
// Used to monitor for new devices
int m_udev_fd;
struct udev* m_udev;
struct udev_monitor* m_udev_monitor;
}; };
#define USE_INPUT_HANDLER_LINUX_JOYSTICK #define USE_INPUT_HANDLER_LINUX_JOYSTICK