From 0d783f0869eaef163a318ab4d79744f592ed1cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Mon, 13 Jun 2016 11:11:47 +0200 Subject: [PATCH 1/4] ControllerInterface: Add a way to register callbacks This adds RegisterHotplugCallback() to register a callback which will be invoked by the input backends' hotplug threads when there is a new device, so that Core (GCKeyboard, GCPad, Wiimote, Hotkey) can reload the configuration without adding a dependency to Core from InputCommon. --- Source/Core/Core/HW/GCKeyboard.cpp | 1 + Source/Core/Core/HW/GCPad.cpp | 1 + Source/Core/Core/HW/Wiimote.cpp | 1 + Source/Core/Core/HotkeyManager.cpp | 1 + .../ControllerInterface.cpp | 22 +++++++++++++++++++ .../ControllerInterface/ControllerInterface.h | 4 ++++ 6 files changed, 30 insertions(+) diff --git a/Source/Core/Core/HW/GCKeyboard.cpp b/Source/Core/Core/HW/GCKeyboard.cpp index a306dc06a0..1b818bee84 100644 --- a/Source/Core/Core/HW/GCKeyboard.cpp +++ b/Source/Core/Core/HW/GCKeyboard.cpp @@ -36,6 +36,7 @@ void Initialize(void* const hwnd) } g_controller_interface.Initialize(hwnd); + g_controller_interface.RegisterHotplugCallback(LoadConfig); // Load the saved controller config s_config.LoadConfig(true); diff --git a/Source/Core/Core/HW/GCPad.cpp b/Source/Core/Core/HW/GCPad.cpp index 70af354560..d88fd53be2 100644 --- a/Source/Core/Core/HW/GCPad.cpp +++ b/Source/Core/Core/HW/GCPad.cpp @@ -35,6 +35,7 @@ void Initialize(void* const hwnd) } g_controller_interface.Initialize(hwnd); + g_controller_interface.RegisterHotplugCallback(LoadConfig); // Load the saved controller config s_config.LoadConfig(true); diff --git a/Source/Core/Core/HW/Wiimote.cpp b/Source/Core/Core/HW/Wiimote.cpp index 69c78448d2..ea63468a31 100644 --- a/Source/Core/Core/HW/Wiimote.cpp +++ b/Source/Core/Core/HW/Wiimote.cpp @@ -37,6 +37,7 @@ void Initialize(void* const hwnd, InitializeMode init_mode) } g_controller_interface.Initialize(hwnd); + g_controller_interface.RegisterHotplugCallback(LoadConfig); s_config.LoadConfig(false); diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index 707c4c05c4..cad32b65b9 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -186,6 +186,7 @@ void Initialize(void* const hwnd) s_config.CreateController(); g_controller_interface.Initialize(hwnd); + g_controller_interface.RegisterHotplugCallback(LoadConfig); // load the saved controller config s_config.LoadConfig(true); diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 700d390b92..cc80b7b234 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -172,6 +172,28 @@ void ControllerInterface::UpdateInput() d->UpdateInput(); } +// +// RegisterHotplugCallback +// +// Register a callback to be called from the input backends' hotplug thread +// when there is a new device +// +void ControllerInterface::RegisterHotplugCallback(std::function callback) +{ + m_hotplug_callbacks.emplace_back(std::move(callback)); +} + +// +// InvokeHotplugCallbacks +// +// Invoke all callbacks that were registered +// +void ControllerInterface::InvokeHotplugCallbacks() const +{ + for (const auto& callback : m_hotplug_callbacks) + callback(); +} + // // InputReference :: State // diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 2f4212ba85..24f92fd3c1 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -127,7 +127,11 @@ public: const ciface::Core::DeviceQualifier& default_device) const; void UpdateInput(); + void RegisterHotplugCallback(std::function callback); + void InvokeHotplugCallbacks() const; + private: + std::vector> m_hotplug_callbacks; bool m_is_init; void* m_hwnd; }; From 93f5df419521171c11e16d6a8ff84b9d0d8fd6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 14 Jul 2016 17:45:59 +0200 Subject: [PATCH 2/4] ControllerInterface: Add RemoveDevice() This adds RemoveDevice() to ControllerInterface, fixes ExpressionParser and some other code to support device removals without crashing, and adds an IsValid() method to Device, to prepare for hotplugging. --- Source/Core/Core/HW/GCKeyboardEmu.cpp | 1 + Source/Core/Core/HW/GCPadEmu.cpp | 4 ++++ Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp | 10 +++++++-- Source/Core/Core/HotkeyManager.cpp | 1 + Source/Core/DolphinWX/InputConfigDiag.cpp | 7 +++++- .../Core/DolphinWX/InputConfigDiagBitmaps.cpp | 22 +++++++++---------- Source/Core/InputCommon/ControllerEmu.cpp | 11 ++++++++++ Source/Core/InputCommon/ControllerEmu.h | 7 ++++++ .../ControllerInterface.cpp | 8 +++++++ .../ControllerInterface/ControllerInterface.h | 1 + .../InputCommon/ControllerInterface/Device.h | 1 + .../ControllerInterface/ExpressionParser.cpp | 13 +++++++---- .../ControllerInterface/ExpressionParser.h | 2 +- 13 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Source/Core/Core/HW/GCKeyboardEmu.cpp b/Source/Core/Core/HW/GCKeyboardEmu.cpp index 23e49898fc..b022527a0d 100644 --- a/Source/Core/Core/HW/GCKeyboardEmu.cpp +++ b/Source/Core/Core/HW/GCKeyboardEmu.cpp @@ -86,6 +86,7 @@ std::string GCKeyboard::GetName() const void GCKeyboard::GetInput(KeyboardStatus* const kb) { + auto lock = ControllerEmu::GetStateLock(); m_keys0x->GetState(&kb->key0x, keys0_bitmasks); m_keys1x->GetState(&kb->key1x, keys1_bitmasks); m_keys2x->GetState(&kb->key2x, keys2_bitmasks); diff --git a/Source/Core/Core/HW/GCPadEmu.cpp b/Source/Core/Core/HW/GCPadEmu.cpp index f985342b62..36ab385486 100644 --- a/Source/Core/Core/HW/GCPadEmu.cpp +++ b/Source/Core/Core/HW/GCPadEmu.cpp @@ -81,6 +81,8 @@ std::string GCPad::GetName() const void GCPad::GetInput(GCPadStatus* const pad) { + auto lock = ControllerEmu::GetStateLock(); + ControlState x, y, triggers[2]; // buttons @@ -116,6 +118,7 @@ void GCPad::GetInput(GCPadStatus* const pad) void GCPad::SetOutput(const ControlState strength) { + auto lock = ControllerEmu::GetStateLock(); m_rumble->controls[0]->control_ref->State(strength); } @@ -190,5 +193,6 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface) bool GCPad::GetMicButton() const { + auto lock = ControllerEmu::GetStateLock(); return (0.0f != m_buttons->controls.back()->control_ref->State()); } diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp index 276943b578..b9dec6f554 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp @@ -623,8 +623,11 @@ void Wiimote::Update() return; // returns true if a report was sent - if (Step()) - return; + { + auto lock = ControllerEmu::GetStateLock(); + if (Step()) + return; + } u8 data[MAX_PAYLOAD]; memset(data, 0, sizeof(data)); @@ -646,6 +649,8 @@ void Wiimote::Update() data[0] = 0xA1; data[1] = m_reporting_mode; + auto lock = ControllerEmu::GetStateLock(); + // core buttons if (rptf.core) GetButtonData(data + rptf.core); @@ -876,6 +881,7 @@ void Wiimote::ConnectOnInput() } u16 buttons = 0; + auto lock = ControllerEmu::GetStateLock(); m_buttons->GetState(&buttons, button_bitmasks); m_dpad->GetState(&buttons, dpad_bitmasks); diff --git a/Source/Core/Core/HotkeyManager.cpp b/Source/Core/Core/HotkeyManager.cpp index cad32b65b9..b8fc387b3e 100644 --- a/Source/Core/Core/HotkeyManager.cpp +++ b/Source/Core/Core/HotkeyManager.cpp @@ -240,6 +240,7 @@ std::string HotkeyManager::GetName() const void HotkeyManager::GetInput(HotkeyStatus* const kb) { + auto lock = ControllerEmu::GetStateLock(); for (int set = 0; set < (NUM_HOTKEYS + 31) / 32; set++) { std::vector bitmasks; diff --git a/Source/Core/DolphinWX/InputConfigDiag.cpp b/Source/Core/DolphinWX/InputConfigDiag.cpp index 2a5aa997e3..cad5d3496a 100644 --- a/Source/Core/DolphinWX/InputConfigDiag.cpp +++ b/Source/Core/DolphinWX/InputConfigDiag.cpp @@ -315,11 +315,12 @@ bool ControlDialog::Validate() { control_reference->expression = WxStrToStr(textctrl->GetValue()); + auto lock = ControllerEmu::GetStateLock(); g_controller_interface.UpdateReference(control_reference, m_parent->controller->default_device); UpdateGUI(); - return (control_reference->parse_error == EXPRESSION_PARSE_SUCCESS); + return control_reference->parse_error == EXPRESSION_PARSE_SUCCESS; } void GamepadPage::SetDevice(wxCommandEvent&) @@ -351,6 +352,7 @@ void ControlDialog::ClearControl(wxCommandEvent&) { control_reference->expression.clear(); + auto lock = ControllerEmu::GetStateLock(); g_controller_interface.UpdateReference(control_reference, m_parent->controller->default_device); UpdateGUI(); @@ -408,6 +410,7 @@ void ControlDialog::SetSelectedControl(wxCommandEvent&) textctrl->WriteText(expr); control_reference->expression = textctrl->GetValue(); + auto lock = ControllerEmu::GetStateLock(); g_controller_interface.UpdateReference(control_reference, m_parent->controller->default_device); UpdateGUI(); @@ -442,6 +445,7 @@ void ControlDialog::AppendControl(wxCommandEvent& event) textctrl->WriteText(expr); control_reference->expression = textctrl->GetValue(); + auto lock = ControllerEmu::GetStateLock(); g_controller_interface.UpdateReference(control_reference, m_parent->controller->default_device); UpdateGUI(); @@ -556,6 +560,7 @@ bool GamepadPage::DetectButton(ControlButton* button) wxString expr; GetExpressionForControl(expr, control_name); button->control_reference->expression = expr; + auto lock = ControllerEmu::GetStateLock(); g_controller_interface.UpdateReference(button->control_reference, controller->default_device); success = true; } diff --git a/Source/Core/DolphinWX/InputConfigDiagBitmaps.cpp b/Source/Core/DolphinWX/InputConfigDiagBitmaps.cpp index 13dcea4e2f..8f972c9091 100644 --- a/Source/Core/DolphinWX/InputConfigDiagBitmaps.cpp +++ b/Source/Core/DolphinWX/InputConfigDiagBitmaps.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -125,6 +124,7 @@ static void DrawButton(unsigned int* const bitmasks, unsigned int buttons, unsig } else { + auto lock = ControllerEmu::GetStateLock(); unsigned char amt = 255 - g->control_group->controls[(row * 8) + n]->control_ref->State() * 128; dc.SetBrush(wxBrush(wxColour(amt, amt, amt))); } @@ -232,17 +232,15 @@ static void DrawControlGroupBox(wxDC& dc, ControlGroupBox* g) } // raw dot - { - ControlState xx, yy; - xx = g->control_group->controls[3]->control_ref->State(); - xx -= g->control_group->controls[2]->control_ref->State(); - yy = g->control_group->controls[1]->control_ref->State(); - yy -= g->control_group->controls[0]->control_ref->State(); + ControlState xx, yy; + xx = g->control_group->controls[3]->control_ref->State(); + xx -= g->control_group->controls[2]->control_ref->State(); + yy = g->control_group->controls[1]->control_ref->State(); + yy -= g->control_group->controls[0]->control_ref->State(); - dc.SetPen(*wxGREY_PEN); - dc.SetBrush(*wxGREY_BRUSH); - DrawCoordinate(dc, xx, yy); - } + dc.SetPen(*wxGREY_PEN); + dc.SetBrush(*wxGREY_BRUSH); + DrawCoordinate(dc, xx, yy); // adjusted dot if (x != 0 || y != 0) @@ -403,6 +401,7 @@ static void DrawControlGroupBox(wxDC& dc, ControlGroupBox* g) for (unsigned int n = 0; n < trigger_count; ++n) { dc.SetBrush(*wxRED_BRUSH); + ControlState trig_d = g->control_group->controls[n]->control_ref->State(); ControlState trig_a = @@ -465,6 +464,7 @@ void InputConfigDialog::UpdateBitmaps(wxTimerEvent& WXUNUSED(event)) GamepadPage* const current_page = (GamepadPage*)m_pad_notebook->GetPage(m_pad_notebook->GetSelection()); + auto lock = ControllerEmu::GetStateLock(); for (ControlGroupBox* g : current_page->control_groups) { // if this control group has a bitmap diff --git a/Source/Core/InputCommon/ControllerEmu.cpp b/Source/Core/InputCommon/ControllerEmu.cpp index 117184c8db..b3b13b796a 100644 --- a/Source/Core/InputCommon/ControllerEmu.cpp +++ b/Source/Core/InputCommon/ControllerEmu.cpp @@ -6,8 +6,19 @@ #include #include "Common/Common.h" +// This should be called before calling GetState() or State() on a control reference +// to prevent a race condition. +// This is a recursive mutex because UpdateReferences is recursive. +static std::recursive_mutex s_get_state_mutex; +std::unique_lock ControllerEmu::GetStateLock() +{ + std::unique_lock lock(s_get_state_mutex); + return lock; +} + void ControllerEmu::UpdateReferences(ControllerInterface& devi) { + auto lock = ControllerEmu::GetStateLock(); for (auto& ctrlGroup : groups) { for (auto& control : ctrlGroup->controls) diff --git a/Source/Core/InputCommon/ControllerEmu.h b/Source/Core/InputCommon/ControllerEmu.h index d9470ca8a9..9416b5f0de 100644 --- a/Source/Core/InputCommon/ControllerEmu.h +++ b/Source/Core/InputCommon/ControllerEmu.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -444,6 +445,12 @@ public: void UpdateReferences(ControllerInterface& devi); + // This returns a lock that should be held before calling State() on any control + // references and GetState(), by extension. This prevents a race condition + // which happens while handling a hotplug event because a control reference's State() + // could be called before we have finished updating the reference. + static std::unique_lock GetStateLock(); + std::vector> groups; ciface::Core::DeviceQualifier default_device; diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index cc80b7b234..cf726698f1 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -160,6 +160,14 @@ void ControllerInterface::AddDevice(std::shared_ptr device m_devices.emplace_back(std::move(device)); } +void ControllerInterface::RemoveDevice(std::function callback) +{ + std::lock_guard lk(m_devices_mutex); + m_devices.erase(std::remove_if(m_devices.begin(), m_devices.end(), + [&callback](const auto& dev) { return callback(dev.get()); }), + m_devices.end()); +} + // // UpdateInput // diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h index 24f92fd3c1..198a2c180b 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.h @@ -122,6 +122,7 @@ public: void Reinitialize(); void Shutdown(); void AddDevice(std::shared_ptr device); + void RemoveDevice(std::function callback); bool IsInit() const { return m_is_init; } void UpdateReference(ControlReference* control, const ciface::Core::DeviceQualifier& default_device) const; diff --git a/Source/Core/InputCommon/ControllerInterface/Device.h b/Source/Core/InputCommon/ControllerInterface/Device.h index e3fe4c11cd..657ece5016 100644 --- a/Source/Core/InputCommon/ControllerInterface/Device.h +++ b/Source/Core/InputCommon/ControllerInterface/Device.h @@ -98,6 +98,7 @@ public: virtual std::string GetName() const = 0; virtual std::string GetSource() const = 0; virtual void UpdateInput() {} + virtual bool IsValid() const { return true; } const std::vector& Inputs() const { return m_inputs; } const std::vector& Outputs() const { return m_outputs; } Input* FindInput(const std::string& name) const; diff --git a/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp index 6e9b8b77ab..2960888148 100644 --- a/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.cpp @@ -235,8 +235,9 @@ public: ControlQualifier qualifier; Device::Control* control; - ControlExpression(ControlQualifier qualifier_, Device::Control* control_) - : qualifier(qualifier_), control(control_) + ControlExpression(ControlQualifier qualifier_, std::shared_ptr device, + Device::Control* control_) + : qualifier(qualifier_), control(control_), m_device(device) { } @@ -244,6 +245,8 @@ public: void SetValue(ControlState value) override { control->ToOutput()->SetGatedState(value); } int CountNumControls() override { return 1; } operator std::string() override { return "`" + (std::string)qualifier + "`"; } +private: + std::shared_ptr m_device; }; class BinaryExpression : public ExpressionNode @@ -393,6 +396,7 @@ private: { case TOK_CONTROL: { + std::shared_ptr device = finder.FindDevice(tok.qualifier); Device::Control* control = finder.FindControl(tok.qualifier); if (control == nullptr) { @@ -400,7 +404,7 @@ private: return EXPRESSION_PARSE_SUCCESS; } - *expr_out = new ControlExpression(tok.qualifier, control); + *expr_out = new ControlExpression(tok.qualifier, device, control); return EXPRESSION_PARSE_SUCCESS; } case TOK_LPAREN: @@ -550,10 +554,11 @@ ExpressionParseStatus ParseExpression(const std::string& str, ControlFinder& fin qualifier.control_name = str; qualifier.has_device = false; + std::shared_ptr device = finder.FindDevice(qualifier); Device::Control* control = finder.FindControl(qualifier); if (control) { - *expr_out = new Expression(new ControlExpression(qualifier, control)); + *expr_out = new Expression(new ControlExpression(qualifier, device, control)); return EXPRESSION_PARSE_SUCCESS; } diff --git a/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h index 992ca65f03..2c8f5a793f 100644 --- a/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h +++ b/Source/Core/InputCommon/ControllerInterface/ExpressionParser.h @@ -37,10 +37,10 @@ public: : container(container_), default_device(default_), is_input(is_input_) { } + std::shared_ptr FindDevice(ControlQualifier qualifier); Core::Device::Control* FindControl(ControlQualifier qualifier); private: - std::shared_ptr FindDevice(ControlQualifier qualifier); const Core::DeviceContainer& container; const Core::DeviceQualifier& default_device; bool is_input; From 3926db624d5ab2f959e67eb2369f1fe5f0f9543a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Fri, 15 Jul 2016 11:34:18 +0200 Subject: [PATCH 3/4] ControllerInterface: Don't block on UpdateInput() Changes UpdateInput() to skip if we can't lock the mutex, instead of potentially blocking the CPU thread and causing a short but noticeable frame drop. --- .../ControllerInterface/ControllerInterface.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index cf726698f1..9c7a08428e 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -175,9 +175,13 @@ void ControllerInterface::RemoveDevice(std::function lk(m_devices_mutex); - for (const auto& d : m_devices) - d->UpdateInput(); + // Don't block the UI or CPU thread (to avoid a short but noticeable frame drop) + if (m_devices_mutex.try_lock()) + { + std::lock_guard lk(m_devices_mutex, std::adopt_lock); + for (const auto& d : m_devices) + d->UpdateInput(); + } } // From 135641404af9c52e433ef28dba4f86fa208f4365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Thu, 14 Jul 2016 17:50:35 +0200 Subject: [PATCH 4/4] evdev: Add hotplugging support This adds hotplugging support to the evdev input backend. We use libudev to monitor changes to input devices in a separate thread. Removed devices are removed from the devices list, and new devices are added to the list. The effect is that controllers are usable immediately after plugging them without having to manually refresh devices (if they were configured to be used, of course). --- .../ControllerInterface.cpp | 25 ++-- .../ControllerInterface/evdev/evdev.cpp | 140 +++++++++++++++++- .../ControllerInterface/evdev/evdev.h | 4 + 3 files changed, 152 insertions(+), 17 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp index 9c7a08428e..f1e62e73cf 100644 --- a/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp @@ -106,17 +106,6 @@ void ControllerInterface::Shutdown() if (!m_is_init) return; - std::lock_guard lk(m_devices_mutex); - - for (const auto& d : m_devices) - { - // Set outputs to ZERO before destroying device - for (ciface::Core::Device::Output* o : d->Outputs()) - o->SetState(0); - } - - m_devices.clear(); - #ifdef CIFACE_USE_XINPUT ciface::XInput::DeInit(); #endif @@ -136,6 +125,20 @@ void ControllerInterface::Shutdown() #ifdef CIFACE_USE_ANDROID // nothing needed #endif +#ifdef CIFACE_USE_EVDEV + ciface::evdev::Shutdown(); +#endif + + std::lock_guard lk(m_devices_mutex); + + for (const auto& d : m_devices) + { + // Set outputs to ZERO before destroying device + for (ciface::Core::Device::Output* o : d->Outputs()) + o->SetState(0); + } + + m_devices.clear(); m_is_init = false; } diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp index 16d0d108b2..08687332fb 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp @@ -5,12 +5,17 @@ #include #include #include +#include #include +#include + #include "Common/Assert.h" +#include "Common/Flag.h" #include "Common/Logging/Log.h" #include "Common/MathUtil.h" #include "Common/StringUtil.h" +#include "Common/Thread.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/ControllerInterface/evdev/evdev.h" @@ -18,6 +23,15 @@ namespace ciface { namespace evdev { +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 s_devnode_name_map; + static std::string GetName(const std::string& devnode) { int fd = open(devnode.c_str(), O_RDWR | O_NONBLOCK); @@ -28,20 +42,110 @@ static std::string GetName(const std::string& devnode) close(fd); return std::string(); } - std::string res = libevdev_get_name(dev); + std::string res = StripSpaces(libevdev_get_name(dev)); libevdev_free(dev); close(fd); return res; } +static void HotplugThreadFunc() +{ + Common::SetCurrentThreadName("evdev Hotplug Thread"); + NOTICE_LOG(SERIALINTERFACE, "evdev hotplug thread started"); + + udev* udev = udev_new(); + _assert_msg_(PAD, udev != nullptr, "Couldn't initialize libudev."); + + // Set up monitoring + udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev"); + udev_monitor_filter_add_match_subsystem_devtype(monitor, "input", nullptr); + udev_monitor_enable_receiving(monitor); + const int monitor_fd = udev_monitor_get_fd(monitor); + + while (s_hotplug_thread_running.IsSet()) + { + fd_set fds; + + FD_ZERO(&fds); + FD_SET(monitor_fd, &fds); + FD_SET(s_wakeup_eventfd, &fds); + + int ret = select(monitor_fd + 1, &fds, nullptr, nullptr, nullptr); + if (ret < 1 || !FD_ISSET(monitor_fd, &fds)) + continue; + + udev_device* dev = udev_monitor_receive_device(monitor); + + const char* action = udev_device_get_action(dev); + const char* devnode = udev_device_get_devnode(dev); + if (!devnode) + continue; + + if (strcmp(action, "remove") == 0) + { + const auto it = s_devnode_name_map.find(devnode); + if (it == s_devnode_name_map.end()) + continue; // we don't know the name for this device, so it is probably not an evdev device + const std::string& name = it->second; + g_controller_interface.RemoveDevice([&name](const auto& device) { + return device->GetSource() == "evdev" && device->GetName() == name && !device->IsValid(); + }); + NOTICE_LOG(SERIALINTERFACE, "Removed device: %s", name.c_str()); + s_devnode_name_map.erase(devnode); + g_controller_interface.InvokeHotplugCallbacks(); + } + // Only react to "device added" events for evdev devices that we can access. + else if (strcmp(action, "add") == 0 && access(devnode, W_OK) == 0) + { + const std::string name = GetName(devnode); + if (name.empty()) + continue; // probably not an evdev device + auto device = std::make_shared(devnode); + if (device->IsInteresting()) + { + g_controller_interface.AddDevice(std::move(device)); + s_devnode_name_map.insert(std::pair(devnode, name)); + NOTICE_LOG(SERIALINTERFACE, "Added new device: %s", name.c_str()); + g_controller_interface.InvokeHotplugCallbacks(); + } + } + udev_device_unref(dev); + } + NOTICE_LOG(SERIALINTERFACE, "evdev hotplug thread stopped"); +} + +static void StartHotplugThread() +{ + if (s_hotplug_thread_running.IsSet()) + return; + + s_wakeup_eventfd = eventfd(0, 0); + _assert_msg_(PAD, s_wakeup_eventfd != -1, "Couldn't create eventfd."); + s_hotplug_thread_running.Set(true); + s_hotplug_thread = std::thread(HotplugThreadFunc); +} + +static void StopHotplugThread() +{ + if (s_hotplug_thread_running.TestAndClear()) + { + // Write something to efd so that select() stops blocking. + uint64_t value = 1; + write(s_wakeup_eventfd, &value, sizeof(uint64_t)); + s_hotplug_thread.join(); + } +} + void Init() { - // We use Udev to find any devices. In the future this will allow for hotplugging. - // But for now it is essentially iterating over /dev/input/event0 to event31. However if the - // naming scheme is ever updated in the future, this *should* be forwards compatable. + s_devnode_name_map.clear(); - struct udev* udev = udev_new(); - _assert_msg_(PAD, udev != 0, "Couldn't initilize libudev."); + // During initialization we use udev to iterate over all /dev/input/event* devices. + // Note: the Linux kernel is currently limited to just 32 event devices. If this ever + // changes, hopefully udev will take care of this. + + udev* udev = udev_new(); + _assert_msg_(PAD, udev != nullptr, "Couldn't initialize libudev."); // List all input devices udev_enumerate* enumerate = udev_enumerate_new(udev); @@ -69,12 +173,20 @@ void Init() if (input->IsInteresting()) { g_controller_interface.AddDevice(std::move(input)); + s_devnode_name_map.insert(std::pair(devnode, name)); } } udev_device_unref(dev); } udev_enumerate_unref(enumerate); udev_unref(udev); + + StartHotplugThread(); +} + +void Shutdown() +{ + StopHotplugThread(); } evdevDevice::evdevDevice(const std::string& devnode) : m_devfile(devnode) @@ -152,6 +264,22 @@ void evdevDevice::UpdateInput() } while (rc >= 0); } +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) + { + close(current_fd); + return false; + } + libevdev_free(device); + return true; +} + std::string evdevDevice::Button::GetName() const { // Buttons below 0x100 are mostly keyboard keys, and the names make sense diff --git a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h index 081913fa8e..dc8e917e23 100644 --- a/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h +++ b/Source/Core/InputCommon/ControllerInterface/evdev/evdev.h @@ -8,11 +8,14 @@ #include #include +#include "InputCommon/ControllerInterface/ControllerInterface.h" + namespace ciface { namespace evdev { void Init(); +void Shutdown(); class evdevDevice : public Core::Device { @@ -62,6 +65,7 @@ private: public: void UpdateInput() override; + bool IsValid() const override; evdevDevice(const std::string& devnode); ~evdevDevice();