@@ -4,14 +4,14 @@
#include < algorithm>
#include < cstring>
#include < fcntl.h>
#include < libudev.h>
#include < map>
#include < memory>
#include < string>
#include < unistd.h>
#include < fcntl.h>
#include < libudev.h>
#include < sys/eventfd.h>
#include < unistd.h>
#include " Common/Assert.h"
#include " Common/Flag.h"
@@ -35,42 +35,80 @@ class Input : public Core::Device::Input
libevdev* const m_dev;
};
class Button final : public Input
class Button : public Input
{
public:
Button (u8 index, u16 code, libevdev* dev) : Input(code, dev), m_index(index) {}
std::string GetName () const override
ControlState GetState () const final override
{
// Buttons below 0x100 are mostly keyboard keys, and the names make sense
if (m_code < 0x100 )
int value = 0 ;
libevdev_fetch_event_value (m_dev, EV_KEY, m_code, &value);
return value;
}
protected:
std::optional<std::string> GetEventCodeName () const
{
if (const char * code_name = libevdev_event_code_get_name (EV_KEY, m_code))
{
const auto name = StripSpaces (code_name);
for (auto remove_prefix : {" BTN_" , " KEY_" })
{
if (name.find (remove_prefix) == 0 )
return std::string (name.substr (std::strlen (remove_prefix)));
}
return std::string (name);
}
else
{
const char * name = libevdev_event_code_get_name (EV_KEY, m_code);
if (name)
return std::string (StripSpaces (name));
return std::nullopt;
}
// But controllers use codes above 0x100, and the standard label often doesn't match.
// We are better off with Button 0 and so on.
return " Button " + std::to_string (m_index);
}
ControlState GetState () const override
std::string GetIndexedName () const { return " Button " + std::to_string (m_index); }
const u8 m_index;
};
class NumberedButton final : public Button
{
public:
using Button::Button;
std::string GetName () const override { return GetIndexedName (); }
};
class NamedButton final : public Button
{
public:
using Button::Button;
bool IsMatchingName (std::string_view name) const final override
{
int value = 0 ;
libevdev_fetch_event_value (m_dev, EV_KEY, m_code, &value);
return value;
// Match either the "START" naming provided by evdev or the "Button 0"-like naming.
return name == GetEventCodeName () || name == GetIndexedName ();
}
private:
const u8 m_index;
std::string GetName () const override { return GetEventCodeName ().value_or (GetIndexedName ()); }
};
class NamedButtonWithNoBackwardsCompat final : public Button
{
public:
using Button::Button;
std::string GetName () const override { return GetEventCodeName ().value_or (GetIndexedName ()); }
};
class AnalogInput : public Input
{
public:
using Input::Input;
ControlState GetState () const override
ControlState GetState () const final override
{
int value = 0 ;
libevdev_fetch_event_value (m_dev, EV_ABS, m_code, &value);
@@ -83,7 +121,7 @@ class AnalogInput : public Input
int m_base;
};
class Axis final : public AnalogInput
class Axis : public AnalogInput
{
public:
Axis (u8 index, u16 code, bool upper, libevdev* dev) : AnalogInput(code, dev), m_index(index)
@@ -95,7 +133,10 @@ class Axis final : public AnalogInput
m_range = (upper ? max : min) - m_base;
}
std::string GetName () const override
std::string GetName () const override { return GetIndexedName (); }
protected:
std::string GetIndexedName () const
{
return " Axis " + std::to_string (m_index) + (m_range < 0 ? ' -' : ' +' );
}
@@ -140,14 +181,96 @@ class MotionDataInput final : public AnalogInput
bool IsDetectable () override { return false ; }
};
class CursorInput final : public Axis
{
public:
using Axis::Axis;
std::string GetName () const final override
{
// "Cursor X-" naming.
return std::string (" Cursor " ) + char (' X' + m_code) + (m_range < 0 ? ' -' : ' +' );
}
bool IsDetectable () override { return false ; }
};
static std::thread s_hotplug_thread;
static Common::Flag s_hotplug_thread_running;
static int s_wakeup_eventfd;
// There is no easy way to get the device name from only a dev node
// during a device removed event, since libevdev can't work on removed devices;
// sysfs is not stable, so this is probably the easiest way to get a name for a node.
static std::map<std::string, std::string> s_devnode_name_map;
static std::map<std::string, std::weak_ptr<evdevDevice>> s_devnode_objects;
std::shared_ptr<evdevDevice> FindDeviceWithUniqueID (const char * unique_id)
{
if (!unique_id)
return nullptr ;
for (auto & [node, dev] : s_devnode_objects)
{
if (const auto device = dev.lock ())
{
const auto * dev_uniq = device->GetUniqueID ();
if (dev_uniq && std::strcmp (dev_uniq, unique_id) == 0 )
return device;
}
}
return nullptr ;
}
void AddDeviceNode (const char * devnode)
{
// Unfortunately udev gives us no way to filter out the non event device interfaces.
// So we open it and see if it works with evdev ioctls or not.
// The device file will be read on one of the main threads, so we open in non-blocking mode.
const int fd = open (devnode, O_RDWR | O_NONBLOCK);
if (fd == -1 )
{
return ;
}
libevdev* dev = nullptr ;
if (libevdev_new_from_fd (fd, &dev) != 0 )
{
// This usually fails because the device node isn't an evdev device, such as /dev/input/js0
close (fd);
return ;
}
const auto uniq = libevdev_get_uniq (dev);
auto evdev_device = FindDeviceWithUniqueID (uniq);
if (evdev_device)
{
NOTICE_LOG (SERIALINTERFACE, " evdev combining devices with unique id: %s" , uniq);
evdev_device->AddNode (devnode, fd, dev);
// Remove and re-add device as naming and inputs may have changed.
// This will also give it the correct index and invoke device change callbacks.
g_controller_interface.RemoveDevice ([&evdev_device](const auto * device) {
return static_cast <const evdevDevice*>(device) == evdev_device.get ();
});
g_controller_interface.AddDevice (evdev_device);
}
else
{
evdev_device = std::make_shared<evdevDevice>();
const bool was_interesting = evdev_device->AddNode (devnode, fd, dev);
if (was_interesting)
g_controller_interface.AddDevice (evdev_device);
}
s_devnode_objects.emplace (devnode, std::move (evdev_device));
}
static void HotplugThreadFunc ()
{
@@ -190,28 +313,21 @@ static void HotplugThreadFunc()
if (strcmp (action, " remove" ) == 0 )
{
const auto it = s_devnode_name_map.find (devnode);
if (it == s_devnode_name_map.end ())
{
// We don't know the name for this device, so it is probably not an evdev device.
continue ;
}
std::shared_ptr<evdevDevice> ptr;
const std::string& name = it->second ;
g_controller_interface.RemoveDevice ([&name](const auto & device) {
return device->GetSource () == " evdev" && device->GetName () == name && !device->IsValid ();
});
const auto it = s_devnode_objects.find (devnode);
if (it != s_devnode_objects.end ())
ptr = it->second .lock ();
// If we don't recognize this device, ptr will be null and no device will be removed.
s_devnode_name_map.erase (devnode);
g_controller_interface.RemoveDevice ([&ptr](const auto * device) {
return static_cast <const evdevDevice*>(device) == ptr.get ();
});
}
else if (strcmp (action, " add" ) == 0 )
{
const auto device = std::make_shared<evdevDevice>(devnode);
if (device->IsInteresting ())
{
s_devnode_name_map.emplace (devnode, device->GetName ());
g_controller_interface.AddDevice (std::move (device));
}
AddDeviceNode (devnode);
}
}
NOTICE_LOG (SERIALINTERFACE, " evdev hotplug thread stopped" );
@@ -250,7 +366,6 @@ static void StopHotplugThread()
void Init ()
{
s_devnode_name_map.clear ();
StartHotplugThread ();
}
@@ -277,19 +392,9 @@ void PopulateDevices()
udev_device* dev = udev_device_new_from_syspath (udev, path);
const char * devnode = udev_device_get_devnode (dev);
if (devnode)
{
// Unfortunately udev gives us no way to filter out the non event device interfaces.
// So we open it and see if it works with evdev ioctls or not.
const auto input = std::make_shared<evdevDevice>(devnode);
if (const char * devnode = udev_device_get_devnode (dev))
AddDeviceNode (devnode);
if (input->IsInteresting ())
{
s_devnode_name_map.emplace (devnode, input->GetName ());
g_controller_interface.AddDevice (std::move (input));
}
}
udev_device_unref (dev);
}
udev_enumerate_unref (enumerate);
@@ -301,50 +406,64 @@ void Shutdown()
StopHotplugThread ();
}
evdevDevice::evdevDevice ( const std::string& devnode) : m_devfile(devnode )
bool evdevDevice::AddNode ( std::string devnode, int fd, libevdev* dev )
{
// The device file will be read on one of the main threads, so we open in non-blocking mode.
m_fd = open (devnode.c_str (), O_RDWR | O_NONBLOCK);
if (m_fd == -1 )
{
return ;
}
m_nodes.emplace_back (Node{std::move (devnode), fd, dev});
if (libevdev_new_from_fd (m_fd, &m_dev) != 0 )
{
// This usually fails because the device node isn't an evdev device, such as /dev/input/js0
close (m_fd);
m_fd = -1 ;
return ;
}
// Take on the alphabetically first name.
const auto potential_new_name = StripSpaces (libevdev_get_name (dev));
if (m_name.empty () || potential_new_name < m_name)
m_name = potential_new_name;
m_name = StripSpaces (libevdev_get_name (m_dev));
const bool is_motion_device = libevdev_has_property (dev, INPUT_PROP_ACCELEROMETER);
const bool is_pointing_device = libevdev_has_property (dev, INPUT_PROP_BUTTONPAD);
// If a device has BTN_JOYSTICK it probably uses event codes counting up from 0x120
// which have very useless and wrong names.
const bool has_btn_joystick = libevdev_has_event_code (dev, EV_KEY, BTN_JOYSTICK);
const bool has_sensible_button_names = !has_btn_joystick;
// Buttons (and keyboard keys)
int num_buttons = 0 ;
for (int key = 0 ; key < KEY_MAX; key++ )
for (int key = 0 ; key != KEY_CNT; ++key )
{
if (libevdev_has_event_code (m_dev, EV_KEY, key))
AddInput (new Button (num_buttons++, key, m_dev));
if (libevdev_has_event_code (dev, EV_KEY, key))
{
if (is_pointing_device || is_motion_device)
{
// This node will probably be combined with another with regular buttons.
// We don't want to match "Button 0" names here as it will name clash.
AddInput (new NamedButtonWithNoBackwardsCompat (num_buttons, key, dev));
}
else if (has_sensible_button_names)
{
AddInput (new NamedButton (num_buttons, key, dev));
}
else
{
AddInput (new NumberedButton (num_buttons, key, dev));
}
++num_buttons;
}
}
int first_axis_code = 0 ;
int num_axis = 0 ;
int num_motion_axis = 0 ;
if (libevdev_has_property (m_dev, INPUT_PROP_ACCELEROMETER))
if (is_motion_device)
{
// If INPUT_PROP_ACCELEROMETER is set then X,Y,Z,RX,RY,RZ contain motion data.
auto add_motion_inputs = [&num_motion_axis , this ](int first_code, double scale) {
auto add_motion_inputs = [&num_axis, dev , this ](int first_code, double scale) {
for (int i = 0 ; i != 3 ; ++i)
{
const int code = first_code + i;
if (libevdev_has_event_code (m_dev , EV_ABS, code))
if (libevdev_has_event_code (dev , EV_ABS, code))
{
AddInput (new MotionDataInput (code, scale * -1 , m_dev ));
AddInput (new MotionDataInput (code, scale, m_dev ));
AddInput (new MotionDataInput (code, scale * -1 , dev ));
AddInput (new MotionDataInput (code, scale, dev ));
++num_motion_axis ;
++num_axis ;
}
}
};
@@ -357,61 +476,78 @@ evdevDevice::evdevDevice(const std::string& devnode) : m_devfile(devnode)
add_motion_inputs (ABS_X, accel_scale);
add_motion_inputs (ABS_RX, gyro_scale);
// evdev says regular axes should not be mixed with motion data,
// but we'll keep looking for regular axes after RZ just in case.
first_axis_code = ABS_RZ + 1 ;
return true ;
}
if (is_pointing_device)
{
auto add_cursor_input = [&num_axis, dev, this ](int code) {
if (libevdev_has_event_code (dev, EV_ABS, code))
{
AddInput (new CursorInput (num_axis, code, false , dev));
AddInput (new CursorInput (num_axis, code, true , dev));
++num_axis;
}
};
add_cursor_input (ABS_X);
add_cursor_input (ABS_Y);
return true ;
}
// Axes beyond ABS_MISC have strange behavior (for multi-touch) which we do not handle.
const int abs_axis_code_count = ABS_MISC;
// Absolute axis (thumbsticks)
int num_axis = 0 ;
for (int axis = first_axis_code; axis != ABS_CNT; ++axis)
for (int axis = 0 ; axis != abs_axis_code_count; ++axis)
{
if (libevdev_has_event_code (m_dev , EV_ABS, axis))
if (libevdev_has_event_code (dev , EV_ABS, axis))
{
AddAnalogInputs (new Axis (num_axis, axis, false , m_dev),
new Axis (num_axis, axis, true , m_dev));
AddAnalogInputs (new Axis (num_axis, axis, false , dev), new Axis (num_axis, axis, true , dev));
++num_axis;
}
}
// Disable autocenter
if (libevdev_has_event_code (m_dev , EV_FF, FF_AUTOCENTER))
if (libevdev_has_event_code (dev , EV_FF, FF_AUTOCENTER))
{
input_event ie = {};
ie.type = EV_FF;
ie.code = FF_AUTOCENTER;
ie.value = 0 ;
static_cast <void >(write (m_fd , &ie, sizeof (ie)));
static_cast <void >(write (fd , &ie, sizeof (ie)));
}
// Constant FF effect
if (libevdev_has_event_code (m_dev , EV_FF, FF_CONSTANT))
if (libevdev_has_event_code (dev , EV_FF, FF_CONSTANT))
{
AddOutput (new ConstantEffect (m_fd ));
AddOutput (new ConstantEffect (fd ));
}
// Periodic FF effects
if (libevdev_has_event_code (m_dev , EV_FF, FF_PERIODIC))
if (libevdev_has_event_code (dev , EV_FF, FF_PERIODIC))
{
for (auto wave : {FF_SINE, FF_SQUARE, FF_TRIANGLE, FF_SAW_UP, FF_SAW_DOWN})
{
if (libevdev_has_event_code (m_dev , EV_FF, wave))
AddOutput (new PeriodicEffect (m_fd , wave));
if (libevdev_has_event_code (dev , EV_FF, wave))
AddOutput (new PeriodicEffect (fd , wave));
}
}
// Rumble (i.e. Left/Right) (i.e. Strong/Weak) effect
if (libevdev_has_event_code (m_dev , EV_FF, FF_RUMBLE))
if (libevdev_has_event_code (dev , EV_FF, FF_RUMBLE))
{
AddOutput (new RumbleEffect (m_fd , RumbleEffect::Motor::Strong));
AddOutput (new RumbleEffect (m_fd , RumbleEffect::Motor::Weak));
AddOutput (new RumbleEffect (fd , RumbleEffect::Motor::Strong));
AddOutput (new RumbleEffect (fd , RumbleEffect::Motor::Weak));
}
// TODO: Add leds as output devices
// Filter out interesting devices (see description below)
m_interesting = num_motion_axis != 0 || num_axis >= 2 || num_buttons >= 8 ;
return num_axis >= 2 || num_buttons >= 8 ;
// On modern linux systems, there are a lot of event devices that aren't controllers.
// For example, the PC Speaker is an event device. Webcams sometimes show up as
@@ -434,23 +570,32 @@ evdevDevice::evdevDevice(const std::string& devnode) : m_devfile(devnode)
// with 5 or 6 special buttons, which is why the threshold is set to 8 to
// match a NES controller.
//
// --- OR ---
//
// Any Motion Axis:
// This rule is to catch any theoretical motion controllers with only a few
// buttons that the user might want to use as a controller.
//
// This heuristic is quite loose. The user may still see weird devices showing up
// as controllers, but it hopefully shouldn't filter out anything they actually
// want to use.
}
const char * evdevDevice::GetUniqueID () const
{
if (m_nodes.empty ())
return nullptr ;
const auto uniq = libevdev_get_uniq (m_nodes.front ().device );
// Some devices (e.g. Mayflash adapter) return an empty string which is not very unique.
if (uniq && std::strlen (uniq) == 0 )
return nullptr ;
return uniq;
}
evdevDevice::~evdevDevice ()
{
if (m_fd != - 1 )
for ( auto & node : m_nodes )
{
libevdev_free (m_dev);
close (m_fd);
s_devnode_objects.erase (node.devnode );
libevdev_free (node.device );
close (node.fd );
}
}
@@ -459,30 +604,39 @@ void evdevDevice::UpdateInput()
// Run through all evdev events
// libevdev will keep track of the actual controller state internally which can be queried
// later with libevdev_fetch_event_value()
int rc = LIBEVDEV_READ_STATUS_SUCCESS;
while (rc >= 0 )
for (auto & node : m_nodes)
{
input_event ev;
if (LIBEVDEV_READ_STATUS_SYNC == rc)
rc = libevdev_next_event (m_dev, LIBEVDEV_READ_FLAG_SYNC, &ev);
else
rc = libevdev_next_event (m_dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
int rc = LIBEVDEV_READ_STATUS_SUCCESS;
while (rc >= 0 )
{
input_event ev;
if (LIBEVDEV_READ_STATUS_SYNC == rc)
rc = libevdev_next_event (node.device , LIBEVDEV_READ_FLAG_SYNC, &ev);
else
rc = libevdev_next_event (node.device , LIBEVDEV_READ_FLAG_NORMAL, &ev);
}
}
}
bool evdevDevice::IsValid () const
{
int current_fd = libevdev_get_fd (m_dev);
if (current_fd == -1 )
return false ;
libevdev* device;
if (libevdev_new_from_fd (current_fd, &device) != 0 )
for (auto & node : m_nodes)
{
close (current_fd);
return false ;
const int current_fd = libevdev_get_fd (node.device );
if (current_fd == -1 )
return false ;
libevdev* device = nullptr ;
if (libevdev_new_from_fd (current_fd, &device) != 0 )
{
close (current_fd);
return false ;
}
libevdev_free (device);
}
libevdev_free (device);
return true ;
}