Now using inotify on Linux to avoid constantly polling joystick connections (#96)

This commit is contained in:
Laurent Gomila 2013-06-30 14:34:00 +02:00
parent af81ac60f3
commit 6ec100aeb7
7 changed files with 189 additions and 23 deletions

View File

@ -93,6 +93,7 @@ void JoystickManager::update()
////////////////////////////////////////////////////////////
JoystickManager::JoystickManager()
{
JoystickImpl::initialize();
}
@ -104,6 +105,8 @@ JoystickManager::~JoystickManager()
if (m_joysticks[i].state.connected)
m_joysticks[i].joystick.close();
}
JoystickImpl::cleanup();
}
} // namespace priv

View File

@ -26,39 +26,121 @@
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Window/JoystickImpl.hpp>
#include <SFML/System/Err.hpp>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <cstdio>
namespace
{
int notifyFd = -1;
int inputFd = -1;
bool plugged[sf::Joystick::Count];
void updatePluggedList()
{
for (unsigned int i = 0; i < sf::Joystick::Count; ++i)
{
char name[32];
std::snprintf(name, sizeof(name), "/dev/input/js%u", i);
struct stat info;
plugged[i] = (stat(name, &info) == 0);
}
}
bool canRead(int descriptor)
{
fd_set set;
FD_ZERO(&set);
FD_SET(descriptor, &set);
timeval timeout = {0, 0};
return select(descriptor + 1, &set, NULL, NULL, &timeout) > 0 &&
FD_ISSET(notifyFd, &set);
}
}
namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
void JoystickImpl::initialize()
{
// Reset the array of plugged joysticks
std::fill(plugged, plugged + Joystick::Count, false);
// Create the inotify instance
notifyFd = inotify_init();
if (notifyFd < 0)
{
err() << "Failed to initialize inotify, joystick connections and disconnections won't be notified" << std::endl;
return;
}
// Watch nodes created and deleted in the /dev/input directory
inputFd = inotify_add_watch(notifyFd, "/dev/input", IN_CREATE | IN_DELETE);
if (inputFd < 0)
{
err() << "Failed to initialize inotify, joystick connections and disconnections won't be notified" << std::endl;
return;
}
// Do an initial scan
updatePluggedList();
}
////////////////////////////////////////////////////////////
void JoystickImpl::cleanup()
{
// Stop watching the /dev/input directory
if (inputFd >= 0)
inotify_rm_watch(notifyFd, inputFd);
// Close the inotify file descriptor
if (inputFd >= 0)
::close(notifyFd);
}
////////////////////////////////////////////////////////////
bool JoystickImpl::isConnected(unsigned int index)
{
char name[32];
std::sprintf(name, "/dev/input/js%u", index);
// First check if new joysticks were added/removed since last update
if (canRead(notifyFd))
{
// Don't bother decomposing and interpreting the filename, just do a full scan
updatePluggedList();
struct stat info;
return stat(name, &info) == 0;
// Flush all the pending events
while (canRead(notifyFd))
{
char buffer[128];
read(notifyFd, buffer, sizeof(buffer));
}
}
// Then check if the joystick is connected
return plugged[index];
}
////////////////////////////////////////////////////////////
bool JoystickImpl::open(unsigned int index)
{
char name[32];
std::sprintf(name, "/dev/input/js%u", index);
m_file = ::open(name, O_RDONLY);
if (m_file > 0)
if (plugged[index])
{
// Use non-blocking mode
fcntl(m_file, F_SETFL, O_NONBLOCK);
char name[32];
std::snprintf(name, sizeof(name), "/dev/input/js%u", index);
// Open the joystick's file descriptor (read-only and non-blocking)
m_file = ::open(name, O_RDONLY | O_NONBLOCK);
if (m_file >= 0)
{
// Retrieve the axes mapping
ioctl(m_file, JSIOCGAXMAP, m_mapping);
@ -72,6 +154,11 @@ bool JoystickImpl::open(unsigned int index)
return false;
}
}
else
{
return false;
}
}
////////////////////////////////////////////////////////////

View File

@ -49,6 +49,18 @@ class JoystickImpl
{
public :
////////////////////////////////////////////////////////////
/// \brief Perform the global initialization of the joystick module
///
////////////////////////////////////////////////////////////
static void initialize();
////////////////////////////////////////////////////////////
/// \brief Perform the global cleanup of the joystick module
///
////////////////////////////////////////////////////////////
static void cleanup();
////////////////////////////////////////////////////////////
/// \brief Check if a joystick is currently connected
///

View File

@ -30,15 +30,16 @@
#include <SFML/Window/OSX/HIDInputManager.hpp>
#include <SFML/Window/OSX/HIDJoystickManager.hpp>
// Translation unit namespace
namespace {
////////////////////////////////////////////////////////////
namespace
{
bool JoystickButtonSortPredicate(IOHIDElementRef b1, IOHIDElementRef b2)
{
return IOHIDElementGetUsage(b1) < IOHIDElementGetUsage(b2);
}
}
namespace sf
{
namespace priv
@ -47,6 +48,20 @@ namespace priv
JoystickImpl::Location JoystickImpl::m_locationIDs[sf::Joystick::Count] = { 0 };
////////////////////////////////////////////////////////////
void JoystickImpl::initialize()
{
// Nothing to do
}
////////////////////////////////////////////////////////////
void JoystickImpl::cleanup()
{
// Nothing to do
}
////////////////////////////////////////////////////////////
bool JoystickImpl::isConnected(unsigned int index)
{

View File

@ -46,6 +46,18 @@ class JoystickImpl
{
public :
////////////////////////////////////////////////////////////
/// \brief Perform the global initialization of the joystick module
///
////////////////////////////////////////////////////////////
static void initialize();
////////////////////////////////////////////////////////////
/// \brief Perform the global cleanup of the joystick module
///
////////////////////////////////////////////////////////////
static void cleanup();
////////////////////////////////////////////////////////////
/// \brief Check if a joystick is currently connected
///

View File

@ -35,10 +35,9 @@ namespace
{
struct ConnectionCache
{
ConnectionCache() : connected(false), firstTime(true) {}
ConnectionCache() : connected(false) {}
bool connected;
sf::Clock timer;
bool firstTime;
};
const sf::Time connectionRefreshDelay = sf::milliseconds(500);
@ -49,6 +48,33 @@ namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
void JoystickImpl::initialize()
{
// Perform the initial scan and populate the connection cache
for (unsigned int i = 0; i < Joystick::Count; ++i)
{
ConnectionCache& cache = connectionCache[i];
// Check if the joystick is connected
JOYINFOEX joyInfo;
joyInfo.dwSize = sizeof(joyInfo);
joyInfo.dwFlags = 0;
cache.connected = joyGetPosEx(JOYSTICKID1 + i, &joyInfo) == JOYERR_NOERROR;
// start the timeout
cache.timer.restart();
}
}
////////////////////////////////////////////////////////////
void JoystickImpl::cleanup()
{
// Nothing to do
}
////////////////////////////////////////////////////////////
bool JoystickImpl::isConnected(unsigned int index)
{
@ -56,10 +82,9 @@ bool JoystickImpl::isConnected(unsigned int index)
// because of a strange (buggy?) behaviour of joyGetPosEx when joysticks
// are just plugged/unplugged -- it takes really long and kills the app performances
ConnectionCache& cache = connectionCache[index];
if (cache.firstTime || (cache.timer.getElapsedTime() > connectionRefreshDelay))
if (cache.timer.getElapsedTime() > connectionRefreshDelay)
{
cache.timer.restart();
cache.firstTime = false;
JOYINFOEX joyInfo;
joyInfo.dwSize = sizeof(joyInfo);

View File

@ -54,6 +54,18 @@ class JoystickImpl
{
public :
////////////////////////////////////////////////////////////
/// \brief Perform the global initialization of the joystick module
///
////////////////////////////////////////////////////////////
static void initialize();
////////////////////////////////////////////////////////////
/// \brief Perform the global cleanup of the joystick module
///
////////////////////////////////////////////////////////////
static void cleanup();
////////////////////////////////////////////////////////////
/// \brief Check if a joystick is currently connected
///