1) Event -> LinuxEvent and Joystick -> LinuxJoystick 2) Rewritten Linux input device detection: introducing LinuxInputManager. It keeps IH_Linux_Event and IH_Linux_Joystick playing nice with each other, and will later be fundamental in hotplugging support.

This commit is contained in:
Ben "root" Anderson
2013-11-30 00:08:04 -06:00
parent a0eb83d60e
commit 0e0922ad8e
9 changed files with 322 additions and 118 deletions
+2 -1
View File
@@ -293,7 +293,8 @@ if UNIX
ArchHooks += arch/ArchHooks/ArchHooks_Unix.cpp arch/ArchHooks/ArchHooks_Unix.h
if LINUX
InputHandler += arch/InputHandler/InputHandler_Linux_Joystick.cpp arch/InputHandler/InputHandler_Linux_Joystick.h \
InputHandler += arch/InputHandler/LinuxInputManager.cpp arch/InputHandler/LinuxInputManager.h \
arch/InputHandler/InputHandler_Linux_Joystick.cpp arch/InputHandler/InputHandler_Linux_Joystick.h \
arch/InputHandler/InputHandler_Linux_Event.cpp arch/InputHandler/InputHandler_Linux_Event.h \
arch/InputHandler/InputHandler_Linux_PIUIO.cpp arch/InputHandler/InputHandler_Linux_PIUIO.h
@@ -2,6 +2,7 @@
#include "InputHandler_Linux_Event.h"
#include "RageLog.h"
#include "RageUtil.h"
#include "LinuxInputManager.h"
#include <unistd.h>
#include <fcntl.h>
@@ -11,9 +12,7 @@
#include <sys/stat.h>
#include <linux/input.h>
REGISTER_INPUT_HANDLER_CLASS2( Event, Linux_Event );
bool InputHandler_Linux_Event::m_bFoundAnyJoysticks;
REGISTER_INPUT_HANDLER_CLASS2( LinuxEvent, Linux_Event );
static RString BustypeToString( int iBus )
{
@@ -94,13 +93,7 @@ bool EventDevice::Open( RString sFile, InputDevice dev )
m_iFD = open( sFile, O_RDWR );
if( m_iFD == -1 )
{
if( errno == ENODEV )
return false;
if( !EventDeviceExists(m_iFD) )
return false;
LOG->Warn( "Error opening %s: %s", sFile.c_str(), strerror(errno) );
// HACK: Let the caller handle errno.
return false;
}
@@ -273,60 +266,68 @@ EventDevice::~EventDevice()
InputHandler_Linux_Event::InputHandler_Linux_Event()
{
if( InputHandler_Linux_Event::m_bFoundAnyJoysticks )
{
LOG->Trace( "InputHandler_Linux_Event disabled (joystick driver already loaded)" );
return;
}
m_NextDevice = DEVICE_JOY1;
m_bDevicesChanged = false;
/* Permission problems are likely. We want to warn about them only if there's actually
* an underlying device, but if we can't open the device, the only way we can tell if
* there'd be anything there is sysfs. That won't always be there. */
m_bFoundAnyJoysticks = false;
InputDevice NextDevice = DEVICE_JOY1;
for( int i = 0; i < 64; ++i )
{
RString sFile = ssprintf( "/dev/input/event%i", i );
if(LINUXINPUT == NULL) LINUXINPUT = new LinuxInputManager;
LINUXINPUT->InitDriver(this);
g_apEventDevices.push_back( new EventDevice );
EventDevice *pDev = g_apEventDevices.back();
if( !pDev->Open(sFile, NextDevice) )
{
delete pDev;
g_apEventDevices.pop_back();
continue;
}
NextDevice = enum_add2(NextDevice, 1);
m_bFoundAnyJoysticks = true;
}
m_bShutdown = false;
if( m_bFoundAnyJoysticks )
{
m_InputThread.SetName( "Event input thread" );
m_InputThread.Create( InputThread_Start, this );
/* We loaded joysticks, so disable joydev. */
}
if( ! g_apEventDevices.empty() ) // LinuxInputManager found at least one valid device for us
StartThread();
}
InputHandler_Linux_Event::~InputHandler_Linux_Event()
{
if( m_InputThread.IsCreated() )
{
m_bShutdown = true;
LOG->Trace( "Shutting down joystick thread ..." );
m_InputThread.Wait();
LOG->Trace( "Joystick thread shut down." );
}
if( m_InputThread.IsCreated() ) StopThread();
for( int i = 0; i < (int) g_apEventDevices.size(); ++i )
delete g_apEventDevices[i];
g_apEventDevices.clear();
}
void InputHandler_Linux_Event::StartThread()
{
m_bShutdown = false;
m_InputThread.SetName( "Event input thread" );
m_InputThread.Create( InputThread_Start, this );
}
void InputHandler_Linux_Event::StopThread()
{
m_bShutdown = true;
LOG->Trace( "Shutting down joystick thread ..." );
m_InputThread.Wait();
LOG->Trace( "Joystick thread shut down." );
}
bool InputHandler_Linux_Event::TryDevice(RString devfile)
{
EventDevice* pDev = new EventDevice;
if( pDev->Open(devfile, m_NextDevice) )
{
bool hotplug = false;
if( m_InputThread.IsCreated() ) { StopThread(); hotplug = true; }
/* Thread is stopped! DO NOT RETURN */
{
g_apEventDevices.push_back( pDev );
}
if( hotplug ) StartThread();
m_NextDevice = enum_add2(m_NextDevice, 1);
m_bDevicesChanged = true;
return true;
}
else
{
delete pDev;
// This is likely to fail; most systems still forbid ALL eventNN regardless
// of their type. Info it anyway, it could be useful for end-user
// troubleshooting.
LOG->Info("LinuxEvent: Couldn't open %s: %s.", devfile.c_str(), strerror(errno) );
return false;
}
}
int InputHandler_Linux_Event::InputThread_Start( void *p )
{
((InputHandler_Linux_Event *) p)->InputThread();
@@ -419,10 +420,13 @@ void InputHandler_Linux_Event::GetDevicesAndDescriptions( vector<InputDeviceInfo
EventDevice *pDev = g_apEventDevices[i];
vDevicesOut.push_back( InputDeviceInfo(pDev->m_Dev, pDev->m_sName) );
}
m_bDevicesChanged = false;
}
/*
* (c) 2003-2008 Glenn Maynard
* (c) 2013 Ben "root" Anderson
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -13,17 +13,19 @@ public:
enum { NUM_JOYSTICKS = 4 };
InputHandler_Linux_Event();
~InputHandler_Linux_Event();
bool TryDevice(RString devfile);
bool DevicesChanged() { return m_bDevicesChanged; }
void GetDevicesAndDescriptions( vector<InputDeviceInfo>& vDevicesOut );
/* Shared with InputHandler_Linux_Joystick.cpp: */
static bool m_bFoundAnyJoysticks;
private:
void StartThread();
void StopThread();
static int InputThread_Start( void *p );
void InputThread();
RageThread m_InputThread;
bool m_bShutdown;
InputDevice m_NextDevice;
bool m_bShutdown, m_bDevicesChanged;
};
#define USE_INPUT_HANDLER_LINUX_JOYSTICK
@@ -1,8 +1,8 @@
#include "global.h"
#include "InputHandler_Linux_Joystick.h"
#include "InputHandler_Linux_Event.h" // for m_bFoundAnyJoysticks
#include "RageLog.h"
#include "RageUtil.h"
#include "LinuxInputManager.h"
#include <unistd.h>
#include <fcntl.h>
@@ -14,7 +14,7 @@
#include <set>
REGISTER_INPUT_HANDLER_CLASS2( Joystick, Linux_Joystick );
REGISTER_INPUT_HANDLER_CLASS2( LinuxJoystick, Linux_Joystick );
static const char *Paths[InputHandler_Linux_Joystick::NUM_JOYSTICKS] =
{
@@ -26,83 +26,87 @@ static const char *Paths[InputHandler_Linux_Joystick::NUM_JOYSTICKS] =
InputHandler_Linux_Joystick::InputHandler_Linux_Joystick()
{
m_bDevicesChanged = false;
LOG->Trace( "InputHandler_Linux_Joystick::InputHandler_Linux_Joystick" );
for(int i = 0; i < NUM_JOYSTICKS; ++i)
fds[i] = -1;
m_iLastFd = 0;
if( InputHandler_Linux_Event::m_bFoundAnyJoysticks )
{
LOG->Trace( "InputHandler_Linux_Joystick disabled (joystick driver already loaded)" );
return;
}
/* We check both eg. /dev/js0 and /dev/input/js0. If both exist, they're probably
* the same device; keep track of device IDs so we don't open the same joystick
* twice. */
set< pair<int,int> > devices;
bool bFoundAnyJoysticks = false;
if( LINUXINPUT == NULL ) LINUXINPUT = new LinuxInputManager;
LINUXINPUT->InitDriver(this);
for(int i = 0; i < NUM_JOYSTICKS; ++i)
{
struct stat st;
if( stat( Paths[i], &st ) == -1 )
{
if( errno != ENOENT )
LOG->Warn( "Couldn't stat %s: %s", Paths[i], strerror(errno) );
continue;
}
if( !S_ISCHR( st.st_mode ) )
{
LOG->Warn( "Ignoring %s: not a character device", Paths[i] );
continue;
}
pair<int,int> dev( major(st.st_rdev), minor(st.st_rdev) );
if( devices.find(dev) != devices.end() )
continue; /* dupe */
devices.insert( dev );
fds[i] = open( Paths[i], O_RDONLY );
if(fds[i] != -1)
{
char szName[1024];
ZERO( szName );
if( ioctl(fds[i], JSIOCGNAME(sizeof(szName)), szName) < 0 )
m_sDescription[i] = ssprintf( "Unknown joystick at %s", Paths[i] );
else
m_sDescription[i] = szName;
LOG->Info("Opened %s", Paths[i]);
bFoundAnyJoysticks = true;
}
}
m_bShutdown = false;
if( bFoundAnyJoysticks )
{
m_InputThread.SetName( "Joystick thread" );
m_InputThread.Create( InputThread_Start, this );
InputHandler_Linux_Event::m_bFoundAnyJoysticks = true;
}
if( fds[0] != -1 ) // LinuxInputManager found at least one valid joystick for us
StartThread();
}
InputHandler_Linux_Joystick::~InputHandler_Linux_Joystick()
{
if( m_InputThread.IsCreated() )
{
m_bShutdown = true;
LOG->Trace( "Shutting down joystick thread ..." );
m_InputThread.Wait();
LOG->Trace( "Joystick thread shut down." );
}
StopThread();
for(int i = 0; i < NUM_JOYSTICKS; ++i)
if(fds[i] != -1) close(fds[i]);
}
void InputHandler_Linux_Joystick::StartThread()
{
m_bShutdown = false;
m_InputThread.SetName( "Joystick thread" );
m_InputThread.Create( InputThread_Start, this );
}
void InputHandler_Linux_Joystick::StopThread()
{
m_bShutdown = true;
LOG->Trace( "Shutting down joystick thread ..." );
m_InputThread.Wait();
LOG->Trace( "Joystick thread shut down." );
}
bool InputHandler_Linux_Joystick::TryDevice(RString dev)
{
bool hotplug = false;
struct stat st;
if( stat( dev, &st ) == -1 )
{ LOG->Warn( "LinuxJoystick: Couldn't stat %s: %s", dev.c_str(), strerror(errno) ); return false; }
if( !S_ISCHR( st.st_mode ) )
{ LOG->Warn( "LinuxJoystick: Ignoring %s: not a character device", dev.c_str() ); return false; }
bool ret = false;
if( m_InputThread.IsCreated() ) { StopThread(); hotplug = true; }
/* Thread is stopped! DO NOT RETURN */
{
fds[m_iLastFd] = open( dev, O_RDONLY );
if(fds[m_iLastFd] != -1)
{
char szName[1024];
ZERO( szName );
if( ioctl(fds[m_iLastFd], JSIOCGNAME(sizeof(szName)), szName) < 0 )
m_sDescription[m_iLastFd] = ssprintf( "Unknown joystick at %s", dev.c_str() );
else
m_sDescription[m_iLastFd] = szName;
LOG->Info("LinuxJoystick: Opened %s", dev.c_str() );
m_iLastFd++;
ret = true;
}
else LOG->Warn("LinuxJoystick: Failed to open %s: %s", dev.c_str(), strerror(errno) );
}
if( hotplug ) StartThread();
return ret;
}
int InputHandler_Linux_Joystick::InputThread_Start( void *p )
{
((InputHandler_Linux_Joystick *) p)->InputThread();
@@ -197,10 +201,12 @@ void InputHandler_Linux_Joystick::GetDevicesAndDescriptions( vector<InputDeviceI
vDevicesOut.push_back( InputDeviceInfo(InputDevice(DEVICE_JOY1+i), m_sDescription[i]) );
}
m_bDevicesChanged = false;
}
/*
* (c) 2003-2004 Glenn Maynard
* (c) 2013 Ben "root" Anderson
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
@@ -11,16 +11,21 @@ public:
enum { NUM_JOYSTICKS = 4 };
InputHandler_Linux_Joystick();
~InputHandler_Linux_Joystick();
bool TryDevice(RString dev);
bool DevicesChanged() { return m_bDevicesChanged; }
void GetDevicesAndDescriptions( vector<InputDeviceInfo>& vDevicesOut );
private:
void StartThread();
void StopThread();
static int InputThread_Start( void *p );
void InputThread();
int fds[NUM_JOYSTICKS];
int m_iLastFd;
RString m_sDescription[NUM_JOYSTICKS];
RageThread m_InputThread;
bool m_bShutdown;
bool m_bShutdown, m_bDevicesChanged;
};
#endif
+126
View File
@@ -0,0 +1,126 @@
#include "LinuxInputManager.h"
#include "InputHandler_Linux_Event.h"
#include "InputHandler_Linux_Joystick.h"
#include "RageInput.h" // g_sInputDrivers
#include "RageLog.h"
#include "Foreach.h"
#include <string> // std::string::npos
#include <dirent.h>
#include <errno.h>
RString getDevice(RString inputDir, RString type)
{
DIR* dir = opendir( inputDir.c_str() );
if(dir == NULL)
{ LOG->Warn("LinuxInputManager: Couldn't open %s: %s.", inputDir.c_str(), strerror(errno) ); return ""; }
struct dirent* d;
while( ( d = readdir(dir) ) != NULL)
if( strncmp( type.c_str(), d->d_name, type.size() ) == 0) break;
closedir(dir);
if( d == NULL ) return "";
return RString("/dev/input/") + d->d_name;
}
LinuxInputManager::LinuxInputManager()
{
m_bEventEnabled = g_sInputDrivers.Get().find("LinuxEvent") != std::string::npos;
m_bJoystickEnabled = g_sInputDrivers.Get().find("LinuxJoystick") != std::string::npos;
// HACK: If empty, assume both are enabled
if( g_sInputDrivers.Get() == "" )
{ m_bEventEnabled = true; m_bJoystickEnabled = true; }
m_EventDriver = NULL;
m_JoystickDriver = NULL;
// XXX: Can I use RageFile for this?
DIR* sysClassInput = opendir("/sys/class/input");
if( sysClassInput == NULL )
{
// XXX: Probably should throw a Dialog. But Linux doesn't have a DialogDriver yet so eh.
LOG->Warn("Couldn't open /sys/class/input: %s. Joysticks will not work!", strerror(errno) );
return;
}
struct dirent* d;
while( ( d = readdir(sysClassInput) ) != NULL)
{
if( strncmp( "input", d->d_name, 5) != 0) continue;
RString dName = RString("/sys/class/input/") + d->d_name;
bool bEventPresent = getDevice(dName, "event") != "";
if( m_bEventEnabled && bEventPresent )
{ m_vsPendingEventDevices.push_back(dName); continue; }
bool bJoystickPresent = getDevice(dName, "js") != "";
if( m_bJoystickEnabled && bJoystickPresent )
{ m_vsPendingJoystickDevices.push_back(dName); continue; }
if( !bEventPresent && !bJoystickPresent )
LOG->Info("LinuxInputManager: %s seems to have no eventNN or jsNN.", dName.c_str() );
}
}
void LinuxInputManager::InitDriver(InputHandler_Linux_Event* driver)
{
m_EventDriver = driver;
FOREACH(RString, m_vsPendingEventDevices, dev)
{
RString devFile = getDevice(*dev, "event");
ASSERT( devFile != "" );
if( ! driver->TryDevice(devFile) && m_bJoystickEnabled && getDevice(*dev, "js") != "" )
m_vsPendingJoystickDevices.push_back(*dev);
}
if( m_JoystickDriver != NULL ) InitDriver(m_JoystickDriver);
m_vsPendingEventDevices.clear();
}
void LinuxInputManager::InitDriver(InputHandler_Linux_Joystick* driver)
{
m_JoystickDriver = driver;
FOREACH(RString, m_vsPendingJoystickDevices, dev)
{
RString devFile = getDevice(*dev, "js");
ASSERT( devFile != "" );
driver->TryDevice(devFile);
}
m_vsPendingJoystickDevices.clear();
}
LinuxInputManager* LINUXINPUT = NULL; // global and accessible anywhere in our program
/*
* (c) 2013 Ben "root" Anderson
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
+58
View File
@@ -0,0 +1,58 @@
#ifndef LINUX_INPUT_MANAGER
#define LINUX_INPUT_MANAGER 1
#include <vector>
using namespace std;
#include "global.h"
class InputHandler_Linux_Joystick;
class InputHandler_Linux_Event;
// Enumerates the input devices on the system and dispatches them to
// IH_Linux_Event and IH_Linux_Joystick as appropriate.
class LinuxInputManager
{
public:
LinuxInputManager();
void InitDriver(InputHandler_Linux_Joystick* drv);
void InitDriver(InputHandler_Linux_Event* drv);
~LinuxInputManager();
private:
bool m_bEventEnabled;
InputHandler_Linux_Event* m_EventDriver;
vector<RString> m_vsPendingEventDevices;
bool m_bJoystickEnabled;
InputHandler_Linux_Joystick* m_JoystickDriver;
vector<RString> m_vsPendingJoystickDevices;
};
extern LinuxInputManager* LINUXINPUT; // global and accessible from anywhere in our program
#endif // LINUX_INPUT_MANAGER
/*
* (c) 2013 Ben "root" Anderson
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
+2
View File
@@ -37,6 +37,8 @@ void MakeInputHandlers( const RString &drivers, vector<InputHandler *> &Add )
#ifdef USE_INPUT_HANDLER_LINUX_JOYSTICK
if( !s->CompareNoCase("Joystick") ) ret = new InputHandler_Linux_Joystick;
#endif
#ifdef USE_INPUT_HANDLER_LINUX_EVENT
if !s->CompareNocase("LinuxEvent") ) ret = new InputHandler_Linux_Event;
#ifdef USE_INPUT_HANDLER_LINUX_TTY
if( !s->CompareNoCase("tty") ) ret = new InputHandler_Linux_tty;
#endif
+1 -1
View File
@@ -34,7 +34,7 @@
#include "LoadingWindow/LoadingWindow_Gtk.h"
#endif
#if defined(LINUX)
#define DEFAULT_INPUT_DRIVER_LIST "X11,Event,Joystick"
#define DEFAULT_INPUT_DRIVER_LIST "X11,LinuxEvent,LinuxJoystick"
#else
#define DEFAULT_INPUT_DRIVER_LIST "X11"
#endif