Replaced Unix joystick enumeration with a fully native udev implementation which supports an unlimited number of devices (still limited higher up by sf::Joystick::Count though).

This commit is contained in:
binary1248 2015-03-25 18:52:48 +01:00 committed by Lukas Dürrenberger
parent bbfef72fc6
commit 0076ea50db

View File

@ -32,60 +32,42 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <sstream> #include <vector>
#include <string>
#include <cstring> #include <cstring>
namespace namespace
{ {
struct udev* udevContext = 0; udev* udevContext = 0;
struct udev_monitor* udevMonitor = 0; udev_monitor* udevMonitor = 0;
bool plugged[sf::Joystick::Count];
struct udev_device* getUdevDevice(const std::string& node) struct JoystickRecord
{ {
struct stat statBuffer; std::string deviceNode;
std::string systemPath;
bool plugged;
};
if (stat(node.c_str(), &statBuffer) < 0) typedef std::vector<JoystickRecord> JoystickList;
return NULL; JoystickList joystickList;
char deviceType;
if (S_ISBLK(statBuffer.st_mode))
deviceType = 'b';
else if (S_ISCHR(statBuffer.st_mode))
deviceType = 'c';
else
return NULL;
return udev_device_new_from_devnum(udevContext, deviceType, statBuffer.st_rdev);
}
int getJoystickNumberFromNode(std::string node)
{
std::string::size_type n = node.find("/js");
if (n != std::string::npos)
node = node.substr(n + 3);
else
return -1;
std::stringstream sstr(node);
int i = -1;
if (!(sstr >> i))
i = -1;
return i;
}
bool isJoystick(udev_device* udevDevice) bool isJoystick(udev_device* udevDevice)
{ {
// If anything goes wrong, we go safe and return true // If anything goes wrong, we go safe and return true
// No device to check, assume joystick // No device to check, assume not a joystick
if (!udevDevice) if (!udevDevice)
return true; return false;
const char* devnode = udev_device_get_devnode(udevDevice);
// We only consider devices with a device node
if (!devnode)
return false;
// SFML doesn't support evdev yet, so make sure we only handle /js nodes
if (!std::strstr(devnode, "/js"))
return false;
// Check if this device is a joystick // Check if this device is a joystick
if (udev_device_get_property_value(udevDevice, "ID_INPUT_JOYSTICK")) if (udev_device_get_property_value(udevDevice, "ID_INPUT_JOYSTICK"))
@ -126,7 +108,7 @@ namespace
return false; return false;
} }
// At this point, assume it's a joystick // At this point, assume it is a joystick
return true; return true;
} }
@ -134,68 +116,136 @@ namespace
{ {
if (udevDevice) if (udevDevice)
{ {
const char* node = udev_device_get_devnode(udevDevice); const char* action = udev_device_get_action(udevDevice);
if (node) if (action)
{ {
int i = getJoystickNumberFromNode(node); if (isJoystick(udevDevice))
if ((i >= 0) && (i < sf::Joystick::Count))
{ {
int file = ::open(node, O_RDONLY); // Since isJoystick returned true, this has to succeed
const char* devnode = udev_device_get_devnode(udevDevice);
if (file < 0) JoystickList::iterator record;
for (record = joystickList.begin(); record != joystickList.end(); ++record)
{ {
plugged[i] = false; if (record->deviceNode == devnode)
{
if (std::strstr(action, "add"))
{
// The system path might have changed so update it
const char* syspath = udev_device_get_syspath(udevDevice);
record->plugged = true;
record->systemPath = syspath ? syspath : "";
break;
}
else if (std::strstr(action, "remove"))
{
record->plugged = false;
break;
}
}
} }
else
if (record == joystickList.end())
{ {
::close(file); if (std::strstr(action, "add"))
plugged[i] = isJoystick(udevDevice); {
return; // If not mapped before and it got added, map it now
const char* syspath = udev_device_get_syspath(udevDevice);
JoystickRecord record;
record.deviceNode = devnode;
record.systemPath = syspath ? syspath : "";
record.plugged = true;
joystickList.push_back(record);
}
else if (std::strstr(action, "remove"))
{
// Not mapped during the initial scan, and removed (shouldn't happen)
sf::err() << "Trying to disconnect joystick that wasn't connected" << std::endl;
}
} }
} }
return;
} }
// Do a full rescan if there was no action just to be sure
} }
for (unsigned int i = 0; i < sf::Joystick::Count; ++i) // Reset the plugged status of each mapping since we are doing a full rescan
for (JoystickList::iterator record = joystickList.begin(); record != joystickList.end(); ++record)
record->plugged = false;
udev_enumerate* udevEnumerator = udev_enumerate_new(udevContext);
if (!udevEnumerator)
{ {
std::ostringstream name; sf::err() << "Error while creating udev enumerator" << std::endl;
name << "js" << i; return;
std::string nameString = name.str(); }
int file = ::open(("/dev/input/" + nameString).c_str(), O_RDONLY); int result = 0;
if (file < 0) result = udev_enumerate_add_match_subsystem(udevEnumerator, "input");
if (result < 0)
{
sf::err() << "Error while adding udev enumerator match" << std::endl;
return;
}
result = udev_enumerate_scan_devices(udevEnumerator);
if (result < 0)
{
sf::err() << "Error while enumerating udev devices" << std::endl;
return;
}
udev_list_entry* devices = udev_enumerate_get_list_entry(udevEnumerator);
udev_list_entry* device;
udev_list_entry_foreach(device, devices) {
const char* syspath = udev_list_entry_get_name(device);
udev_device* udevDevice = udev_device_new_from_syspath(udevContext, syspath);
if (udevDevice && isJoystick(udevDevice))
{ {
plugged[i] = false; // Since isJoystick returned true, this has to succeed
continue; const char* devnode = udev_device_get_devnode(udevDevice);
JoystickList::iterator record;
// Check if the device node has been mapped before
for (record = joystickList.begin(); record != joystickList.end(); ++record)
{
if (record->deviceNode == devnode)
{
record->plugged = true;
break;
}
}
// If not mapped before, map it now
if (record == joystickList.end())
{
JoystickRecord record;
record.deviceNode = devnode;
record.systemPath = syspath;
record.plugged = true;
joystickList.push_back(record);
}
} }
::close(file);
// Now we check if the device is really a joystick
if (!udevContext)
{
// Go safe and assume it is if udev isn't available
plugged[i] = true;
continue;
}
udev_device* udevDevice = udev_device_new_from_subsystem_sysname(udevContext, "input", nameString.c_str());
if (!udevDevice)
{
// Go safe and assume it is if we can't get the device
plugged[i] = true;
continue;
}
plugged[i] = isJoystick(udevDevice);
udev_device_unref(udevDevice); udev_device_unref(udevDevice);
} }
udev_enumerate_unref(udevEnumerator);
} }
bool hasMonitorEvent() bool hasMonitorEvent()
@ -229,17 +279,11 @@ namespace
return udev_device_get_sysattr_value(udevDeviceParent, attributeName.c_str()); return udev_device_get_sysattr_value(udevDeviceParent, attributeName.c_str());
} }
// Get a system attribute for a joystick devnode as an unsigned int // Get a USB attribute for a joystick as an unsigned int
unsigned int getUsbAttributeUint(const std::string& node, const std::string& attributeName) unsigned int getUsbAttributeUint(udev_device* udevDevice, const std::string& attributeName)
{ {
udev_device* udevDevice = getUdevDevice(node);
if (!udevDevice) if (!udevDevice)
{
sf::err() << "Unable to get joystick attribute. "
<< "Could not get udev device for joystick " << node << std::endl;
return 0; return 0;
}
const char* attribute = getUsbAttribute(udevDevice, attributeName); const char* attribute = getUsbAttribute(udevDevice, attributeName);
unsigned int value = 0; unsigned int value = 0;
@ -247,21 +291,14 @@ namespace
if (attribute) if (attribute)
value = static_cast<unsigned int>(std::strtoul(attribute, NULL, 16)); value = static_cast<unsigned int>(std::strtoul(attribute, NULL, 16));
udev_device_unref(udevDevice);
return value; return value;
} }
// Get a property value for a joystick devnode as an unsigned int // Get a udev property value for a joystick as an unsigned int
unsigned int getUdevAttributeUint(const std::string& node, const std::string& attributeName) unsigned int getUdevAttributeUint(udev_device* udevDevice, const std::string& attributeName)
{ {
udev_device* udevDevice = getUdevDevice(node);
if (!udevDevice) if (!udevDevice)
{
sf::err() << "Unable to get joystick attribute. "
<< "Could not get udev device for joystick " << node << std::endl;
return 0; return 0;
}
const char* attribute = getUdevAttribute(udevDevice, attributeName); const char* attribute = getUdevAttribute(udevDevice, attributeName);
unsigned int value = 0; unsigned int value = 0;
@ -269,59 +306,98 @@ namespace
if (attribute) if (attribute)
value = static_cast<unsigned int>(std::strtoul(attribute, NULL, 16)); value = static_cast<unsigned int>(std::strtoul(attribute, NULL, 16));
udev_device_unref(udevDevice);
return value; return value;
} }
// Get vendor id for a joystick devnode // Get the joystick vendor id
unsigned int getJoystickVendorId(const std::string& node) unsigned int getJoystickVendorId(unsigned int index)
{ {
if (!udevContext)
{
sf::err() << "Failed to get vendor ID of joystick " << joystickList[index].deviceNode << std::endl;
return 0;
}
udev_device* udevDevice = udev_device_new_from_syspath(udevContext, joystickList[index].systemPath.c_str());
if (!udevDevice)
{
sf::err() << "Failed to get vendor ID of joystick " << joystickList[index].deviceNode << std::endl;
return 0;
}
unsigned int id = 0; unsigned int id = 0;
// First try using udev // First try using udev
id = getUdevAttributeUint(node, "ID_VENDOR_ID"); id = getUdevAttributeUint(udevDevice, "ID_VENDOR_ID");
if (id) if (id)
{
udev_device_unref(udevDevice);
return id; return id;
}
// Fall back to using USB attribute // Fall back to using USB attribute
id = getUsbAttributeUint(node, "idVendor"); id = getUsbAttributeUint(udevDevice, "idVendor");
udev_device_unref(udevDevice);
if (id) if (id)
return id; return id;
sf::err() << "Failed to get vendor ID of joystick " << node << std::endl; sf::err() << "Failed to get vendor ID of joystick " << joystickList[index].deviceNode << std::endl;
return 0; return 0;
} }
// Get product id for a joystick devnode // Get the joystick product id
unsigned int getJoystickProductId(const std::string& node) unsigned int getJoystickProductId(unsigned int index)
{ {
if (!udevContext)
{
sf::err() << "Failed to get product ID of joystick " << joystickList[index].deviceNode << std::endl;
return 0;
}
udev_device* udevDevice = udev_device_new_from_syspath(udevContext, joystickList[index].systemPath.c_str());
if (!udevDevice)
{
sf::err() << "Failed to get product ID of joystick " << joystickList[index].deviceNode << std::endl;
return 0;
}
unsigned int id = 0; unsigned int id = 0;
// First try using udev // First try using udev
id = getUdevAttributeUint(node, "ID_MODEL_ID"); id = getUdevAttributeUint(udevDevice, "ID_MODEL_ID");
if (id) if (id)
{
udev_device_unref(udevDevice);
return id; return id;
}
// Fall back to using USB attribute // Fall back to using USB attribute
id = getUsbAttributeUint(node, "idProduct"); id = getUsbAttributeUint(udevDevice, "idProduct");
udev_device_unref(udevDevice);
if (id) if (id)
return id; return id;
sf::err() << "Failed to get product ID of joystick " << node << std::endl; sf::err() << "Failed to get product ID of joystick " << joystickList[index].deviceNode << std::endl;
return 0; return 0;
} }
// Get the joystick name // Get the joystick name
std::string getJoystickName(const std::string& node) std::string getJoystickName(unsigned int index)
{ {
std::string devnode = joystickList[index].deviceNode;
// First try using ioctl with JSIOCGNAME // First try using ioctl with JSIOCGNAME
int fd = ::open(node.c_str(), O_RDONLY | O_NONBLOCK); int fd = ::open(devnode.c_str(), O_RDONLY | O_NONBLOCK);
if (fd >= 0) if (fd >= 0)
{ {
@ -340,7 +416,7 @@ namespace
// Fall back to manual USB chain walk via udev // Fall back to manual USB chain walk via udev
if (udevContext) if (udevContext)
{ {
udev_device* udevDevice = getUdevDevice(node); udev_device* udevDevice = udev_device_new_from_syspath(udevContext, joystickList[index].systemPath.c_str());
if (udevDevice) if (udevDevice)
{ {
@ -352,7 +428,7 @@ namespace
} }
} }
sf::err() << "Unable to get name for joystick " << node << std::endl; sf::err() << "Unable to get name for joystick " << devnode << std::endl;
return std::string("Unknown Joystick"); return std::string("Unknown Joystick");
} }
@ -366,15 +442,15 @@ namespace priv
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void JoystickImpl::initialize() void JoystickImpl::initialize()
{ {
// Reset the array of plugged joysticks udevContext = udev_new();
std::fill(plugged, plugged + Joystick::Count, false);
udev* udevContext = udev_new();
if (!udevContext) if (!udevContext)
sf::err() << "Failed to create udev context, getting joystick attributes not available" << std::endl; {
else sf::err() << "Failed to create udev context, joystick support not available" << std::endl;
udevMonitor = udev_monitor_new_from_netlink(udevContext, "udev"); return;
}
udevMonitor = udev_monitor_new_from_netlink(udevContext, "udev");
if (!udevMonitor) if (!udevMonitor)
{ {
@ -441,7 +517,7 @@ bool JoystickImpl::isConnected(unsigned int index)
else if (hasMonitorEvent()) else if (hasMonitorEvent())
{ {
// Check if new joysticks were added/removed since last update // Check if new joysticks were added/removed since last update
struct udev_device* udevDevice = udev_monitor_receive_device(udevMonitor); udev_device* udevDevice = udev_monitor_receive_device(udevMonitor);
// If we can get the specific device, we check that, // If we can get the specific device, we check that,
// otherwise just do a full scan if udevDevice == NULL // otherwise just do a full scan if udevDevice == NULL
@ -451,32 +527,37 @@ bool JoystickImpl::isConnected(unsigned int index)
udev_device_unref(udevDevice); udev_device_unref(udevDevice);
} }
if (index >= joystickList.size())
return false;
// Then check if the joystick is connected // Then check if the joystick is connected
return plugged[index]; return joystickList[index].plugged;
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
bool JoystickImpl::open(unsigned int index) bool JoystickImpl::open(unsigned int index)
{ {
if (plugged[index]) if (index >= joystickList.size())
return false;
if (joystickList[index].plugged)
{ {
std::ostringstream name; std::string devnode = joystickList[index].deviceNode;
name << "/dev/input/js" << index;
// Open the joystick's file descriptor (read-only and non-blocking) // Open the joystick's file descriptor (read-only and non-blocking)
m_file = ::open(name.str().c_str(), O_RDONLY | O_NONBLOCK); m_file = ::open(devnode.c_str(), O_RDONLY | O_NONBLOCK);
if (m_file >= 0) if (m_file >= 0)
{ {
// Retrieve the axes mapping // Retrieve the axes mapping
ioctl(m_file, JSIOCGAXMAP, m_mapping); ioctl(m_file, JSIOCGAXMAP, m_mapping);
// Get info // Get info
m_identification.name = getJoystickName(name.str()); m_identification.name = getJoystickName(index);
if (udevContext) if (udevContext)
{ {
m_identification.vendorId = getJoystickVendorId(name.str()); m_identification.vendorId = getJoystickVendorId(index);
m_identification.productId = getJoystickProductId(name.str()); m_identification.productId = getJoystickProductId(index);
} }
// Reset the joystick state // Reset the joystick state
@ -486,7 +567,7 @@ bool JoystickImpl::open(unsigned int index)
} }
else else
{ {
err() << "Failed to open joystick " << name.str() << ": " << errno << std::endl; err() << "Failed to open joystick " << devnode << ": " << errno << std::endl;
} }
} }