From ac907ef977a1dc2ae4a7acc1ca9f56f1ea21d9cf Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 16 Nov 2019 15:40:43 -0600 Subject: [PATCH] ControllerInterface: Combine evdev devices with the same unique ID. This works around Linux drivers for DS4 (Playstation 4) controllers splitting the device into three separate event nodes which makes configuration difficult. To prevent collisions of input names in combined devices more descriptive names are now used when possible. --- .../ControllerInterface/evdev/evdev.cpp | 390 ++++++++++++------ .../ControllerInterface/evdev/evdev.h | 20 +- 2 files changed, 279 insertions(+), 131 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp index d6a2f7224f..850fce24f7 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -4,14 +4,14 @@ #include #include -#include -#include #include #include #include -#include +#include +#include #include +#include #include "Common/Assert.h" #include "Common/Flag.h" @@ -35,42 +35,80 @@ protected: 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 - { - // Buttons below 0x100 are mostly keyboard keys, and the names make sense - if (m_code < 0x100) - { - const char* name = libevdev_event_code_get_name(EV_KEY, m_code); - if (name) - return std::string(StripSpaces(name)); - } - // 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 + ControlState GetState() const final override { int value = 0; libevdev_fetch_event_value(m_dev, EV_KEY, m_code, &value); return value; } -private: +protected: + std::optional 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 + { + return std::nullopt; + } + } + + 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 + { + // Match either the "START" naming provided by evdev or the "Button 0"-like naming. + return name == GetEventCodeName() || name == GetIndexedName(); + } + + 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 @@ protected: 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 @@ public: 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,6 +181,20 @@ public: 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; @@ -147,7 +202,67 @@ 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 s_devnode_name_map; +static std::map> s_devnode_objects; + +std::shared_ptr 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; + } + + auto evdev_device = FindDeviceWithUniqueID(libevdev_get_uniq(dev)); + if (evdev_device) + { + evdev_device->AddNode(devnode, fd, dev); + + // Callbacks must be invoked as the device name and available inputs may change. + g_controller_interface.InvokeDevicesChangedCallbacks(); + } + else + { + evdev_device = std::make_shared(); + + 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 +305,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 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. + + g_controller_interface.RemoveDevice([&ptr](const auto* device) { + return static_cast(device) == ptr.get(); }); - - s_devnode_name_map.erase(devnode); } else if (strcmp(action, "add") == 0) { - const auto device = std::make_shared(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 +358,6 @@ static void StopHotplugThread() void Init() { - s_devnode_name_map.clear(); StartHotplugThread(); } @@ -277,19 +384,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(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 +398,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 +468,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; } - // Absolute axis (thumbsticks) - int num_axis = 0; - for (int axis = first_axis_code; axis != ABS_CNT; ++axis) + if (is_pointing_device) { - if (libevdev_has_event_code(m_dev, EV_ABS, axis)) + 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) + for (int axis = 0; axis != abs_axis_code_count; ++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(write(m_fd, &ie, sizeof(ie))); + static_cast(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 +562,26 @@ 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; + + return libevdev_get_uniq(m_nodes.front().device); +} + 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 +590,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; } diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h index f4b095558b..3c60e2cb57 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h @@ -76,18 +76,26 @@ public: void UpdateInput() override; bool IsValid() const override; - evdevDevice(const std::string& devnode); ~evdevDevice(); + // Return true if node was "interesting". + bool AddNode(std::string devnode, int fd, libevdev* dev); + + const char* GetUniqueID() const; + std::string GetName() const override { return m_name; } std::string GetSource() const override { return "evdev"; } - bool IsInteresting() const { return m_interesting; } private: - const std::string m_devfile; - int m_fd; - libevdev* m_dev; std::string m_name; - bool m_interesting = false; + + struct Node + { + std::string devnode; + int fd; + libevdev* device; + }; + + std::vector m_nodes; }; } // namespace ciface::evdev