summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerInterface
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface')
-rw-r--r--Source/Core/InputCommon/ControllerInterface/ControllerInterface.cpp25
-rw-r--r--Source/Core/InputCommon/ControllerInterface/evdev/evdev.cpp140
-rw-r--r--Source/Core/InputCommon/ControllerInterface/evdev/evdev.h4
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<std::mutex> 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<std::mutex> 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 <fcntl.h>
#include <libudev.h>
#include <map>
+#include <memory>
#include <unistd.h>
+#include <sys/eventfd.h>
+
#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<std::string, std::string> 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<evdevDevice>(devnode);
+ if (device->IsInteresting())
+ {
+ g_controller_interface.AddDevice(std::move(device));
+ s_devnode_name_map.insert(std::pair<std::string, std::string>(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<std::string, std::string>(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 <string>
#include <vector>
+#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();