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