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 "LocalizedString.h"
#if LINUX
// FIXME: bit gross to include this here, see GH issue #73
#include "arch/InputHandler/LinuxInputManager.h"
#endif
#include <vector>
@@ -60,6 +65,11 @@ void RageInput::LoadDrivers()
m_InputHandlers.clear();
g_mapDeviceToHandler.clear();
#if LINUX
// Recreating this forces it to re-scan for devices.
LINUXINPUT = new LinuxInputManager;
#endif
// Init optional devices.
std::vector<InputHandler *> apDevices;
@@ -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();
}
/*
@@ -25,8 +25,14 @@ private:
void InputThread();
RageThread m_InputThread;
// Currently only m_bDevicesChanged is guarded by this mutex
RageMutex m_InputMutex;
InputDevice m_NextDevice;
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