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() JoystickManager::JoystickManager()
{ {
JoystickImpl::initialize();
} }
@ -104,6 +105,8 @@ JoystickManager::~JoystickManager()
if (m_joysticks[i].state.connected) if (m_joysticks[i].state.connected)
m_joysticks[i].joystick.close(); m_joysticks[i].joystick.close();
} }
JoystickImpl::cleanup();
} }
} // namespace priv } // namespace priv

View File

@ -26,39 +26,121 @@
// Headers // Headers
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
#include <SFML/Window/JoystickImpl.hpp> #include <SFML/Window/JoystickImpl.hpp>
#include <SFML/System/Err.hpp>
#include <sys/inotify.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <cstdio> #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 sf
{ {
namespace priv 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) bool JoystickImpl::isConnected(unsigned int index)
{ {
char name[32]; // First check if new joysticks were added/removed since last update
std::sprintf(name, "/dev/input/js%u", index); if (canRead(notifyFd))
{
// Don't bother decomposing and interpreting the filename, just do a full scan
updatePluggedList();
struct stat info; // Flush all the pending events
return stat(name, &info) == 0; 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) bool JoystickImpl::open(unsigned int index)
{ {
char name[32]; if (plugged[index])
std::sprintf(name, "/dev/input/js%u", index);
m_file = ::open(name, O_RDONLY);
if (m_file > 0)
{ {
// Use non-blocking mode char name[32];
fcntl(m_file, F_SETFL, O_NONBLOCK); 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 // Retrieve the axes mapping
ioctl(m_file, JSIOCGAXMAP, m_mapping); ioctl(m_file, JSIOCGAXMAP, m_mapping);
@ -72,6 +154,11 @@ bool JoystickImpl::open(unsigned int index)
return false; return false;
} }
} }
else
{
return false;
}
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////

View File

@ -49,6 +49,18 @@ class JoystickImpl
{ {
public : 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 /// \brief Check if a joystick is currently connected
/// ///

View File

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

View File

@ -46,6 +46,18 @@ class JoystickImpl
{ {
public : 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 /// \brief Check if a joystick is currently connected
/// ///

View File

@ -35,10 +35,9 @@ namespace
{ {
struct ConnectionCache struct ConnectionCache
{ {
ConnectionCache() : connected(false), firstTime(true) {} ConnectionCache() : connected(false) {}
bool connected; bool connected;
sf::Clock timer; sf::Clock timer;
bool firstTime;
}; };
const sf::Time connectionRefreshDelay = sf::milliseconds(500); const sf::Time connectionRefreshDelay = sf::milliseconds(500);
@ -49,6 +48,33 @@ namespace sf
{ {
namespace priv 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) 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 // because of a strange (buggy?) behaviour of joyGetPosEx when joysticks
// are just plugged/unplugged -- it takes really long and kills the app performances // are just plugged/unplugged -- it takes really long and kills the app performances
ConnectionCache& cache = connectionCache[index]; ConnectionCache& cache = connectionCache[index];
if (cache.firstTime || (cache.timer.getElapsedTime() > connectionRefreshDelay)) if (cache.timer.getElapsedTime() > connectionRefreshDelay)
{ {
cache.timer.restart(); cache.timer.restart();
cache.firstTime = false;
JOYINFOEX joyInfo; JOYINFOEX joyInfo;
joyInfo.dwSize = sizeof(joyInfo); joyInfo.dwSize = sizeof(joyInfo);

View File

@ -54,6 +54,18 @@ class JoystickImpl
{ {
public : 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 /// \brief Check if a joystick is currently connected
/// ///