ControllerInterface: evdev InputBackend implementation.

This commit is contained in:
Jordan Woyak 2022-10-22 17:10:38 -05:00
parent 44a4573303
commit 2e5cd5d519
3 changed files with 93 additions and 58 deletions

View File

@ -70,7 +70,7 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
// nothing needed // nothing needed
#endif #endif
#ifdef CIFACE_USE_EVDEV #ifdef CIFACE_USE_EVDEV
ciface::evdev::Init(); m_input_backends.emplace_back(ciface::evdev::CreateInputBackend(this));
#endif #endif
#ifdef CIFACE_USE_PIPES #ifdef CIFACE_USE_PIPES
// nothing needed // nothing needed
@ -184,9 +184,6 @@ void ControllerInterface::RefreshDevices(RefreshReason reason)
#ifdef CIFACE_USE_ANDROID #ifdef CIFACE_USE_ANDROID
ciface::Android::PopulateDevices(); ciface::Android::PopulateDevices();
#endif #endif
#ifdef CIFACE_USE_EVDEV
ciface::evdev::PopulateDevices();
#endif
#ifdef CIFACE_USE_PIPES #ifdef CIFACE_USE_PIPES
ciface::Pipes::PopulateDevices(); ciface::Pipes::PopulateDevices();
#endif #endif
@ -245,13 +242,11 @@ void ControllerInterface::Shutdown()
#ifdef CIFACE_USE_ANDROID #ifdef CIFACE_USE_ANDROID
// nothing needed // nothing needed
#endif #endif
#ifdef CIFACE_USE_EVDEV
ciface::evdev::Shutdown();
#endif
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT #ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
ciface::DualShockUDPClient::DeInit(); ciface::DualShockUDPClient::DeInit();
#endif #endif
// Empty the container of input backends to deconstruct and deinitialize them.
m_input_backends.clear(); m_input_backends.clear();
// Make sure no devices had been added within Shutdown() in the time // Make sure no devices had been added within Shutdown() in the time

View File

@ -25,6 +25,43 @@
namespace ciface::evdev namespace ciface::evdev
{ {
class InputBackend final : public ciface::InputBackend
{
public:
InputBackend(ControllerInterface* controller_interface);
~InputBackend();
void PopulateDevices() override;
void RemoveDevnodeObject(const std::string&);
private:
std::shared_ptr<evdevDevice>
FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* physical_location);
void AddDeviceNode(const char* devnode);
void StartHotplugThread();
void StopHotplugThread();
void HotplugThreadFunc();
std::thread m_hotplug_thread;
Common::Flag m_hotplug_thread_running;
int m_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.
// This can, and will be modified by different thread, possibly concurrently,
// as devices can be destroyed by any thread at any time. As of now it's protected
// by ControllerInterface::m_devices_population_mutex.
std::map<std::string, std::weak_ptr<evdevDevice>> m_devnode_objects;
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
return std::make_unique<InputBackend>(controller_interface);
}
class Input : public Core::Device::Input class Input : public Core::Device::Input
{ {
public: public:
@ -195,25 +232,14 @@ public:
bool IsDetectable() const override { return false; } bool IsDetectable() const override { return false; }
}; };
static std::thread s_hotplug_thread; std::shared_ptr<evdevDevice>
static Common::Flag s_hotplug_thread_running; InputBackend::FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id,
static int s_wakeup_eventfd; const char* physical_location)
// 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.
// This can, and will be modified by different thread, possibly concurrently,
// as devices can be destroyed by any thread at any time. As of now it's protected
// by ControllerInterface::m_devices_population_mutex.
static std::map<std::string, std::weak_ptr<evdevDevice>> s_devnode_objects;
static std::shared_ptr<evdevDevice>
FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* physical_location)
{ {
if (!unique_id || !physical_location) if (!unique_id || !physical_location)
return nullptr; return nullptr;
for (auto& [node, dev] : s_devnode_objects) for (auto& [node, dev] : m_devnode_objects)
{ {
if (const auto device = dev.lock()) if (const auto device = dev.lock())
{ {
@ -231,7 +257,7 @@ FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* phy
return nullptr; return nullptr;
} }
static void AddDeviceNode(const char* devnode) void InputBackend::AddDeviceNode(const char* devnode)
{ {
// Unfortunately udev gives us no way to filter out the non event device interfaces. // 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. // So we open it and see if it works with evdev ioctls or not.
@ -265,30 +291,30 @@ static void AddDeviceNode(const char* devnode)
// This will also give it the correct index and invoke device change callbacks. // This will also give it the correct index and invoke device change callbacks.
// Make sure to force the device removal immediately (as they are shared ptrs and // Make sure to force the device removal immediately (as they are shared ptrs and
// they could be kept alive, preventing us from re-creating the device) // they could be kept alive, preventing us from re-creating the device)
g_controller_interface.RemoveDevice( GetControllerInterface().RemoveDevice(
[&evdev_device](const auto* device) { [&evdev_device](const auto* device) {
return static_cast<const evdevDevice*>(device) == evdev_device.get(); return static_cast<const evdevDevice*>(device) == evdev_device.get();
}, },
true); true);
g_controller_interface.AddDevice(evdev_device); GetControllerInterface().AddDevice(evdev_device);
} }
else else
{ {
evdev_device = std::make_shared<evdevDevice>(); evdev_device = std::make_shared<evdevDevice>(this);
const bool was_interesting = evdev_device->AddNode(devnode, fd, dev); const bool was_interesting = evdev_device->AddNode(devnode, fd, dev);
if (was_interesting) if (was_interesting)
g_controller_interface.AddDevice(evdev_device); GetControllerInterface().AddDevice(evdev_device);
} }
// If the devices failed to be added to g_controller_interface, it will be added here but then // If the devices failed to be added to ControllerInterface, it will be added here but then
// immediately removed in its destructor due to the shared ptr not having any references left // immediately removed in its destructor due to the shared ptr not having any references left
s_devnode_objects.emplace(devnode, std::move(evdev_device)); m_devnode_objects.emplace(devnode, std::move(evdev_device));
} }
static void HotplugThreadFunc() void InputBackend::HotplugThreadFunc()
{ {
Common::SetCurrentThreadName("evdev Hotplug Thread"); Common::SetCurrentThreadName("evdev Hotplug Thread");
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread started"); NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread started");
@ -306,16 +332,16 @@ static void HotplugThreadFunc()
udev_monitor_enable_receiving(monitor); udev_monitor_enable_receiving(monitor);
const int monitor_fd = udev_monitor_get_fd(monitor); const int monitor_fd = udev_monitor_get_fd(monitor);
while (s_hotplug_thread_running.IsSet()) while (m_hotplug_thread_running.IsSet())
{ {
fd_set fds; fd_set fds;
FD_ZERO(&fds); FD_ZERO(&fds);
FD_SET(monitor_fd, &fds); FD_SET(monitor_fd, &fds);
FD_SET(s_wakeup_eventfd, &fds); FD_SET(m_wakeup_eventfd, &fds);
const int ret = const int ret =
select(std::max(monitor_fd, s_wakeup_eventfd) + 1, &fds, nullptr, nullptr, nullptr); select(std::max(monitor_fd, m_wakeup_eventfd) + 1, &fds, nullptr, nullptr, nullptr);
if (ret < 1 || !FD_ISSET(monitor_fd, &fds)) if (ret < 1 || !FD_ISSET(monitor_fd, &fds))
continue; continue;
@ -327,53 +353,54 @@ static void HotplugThreadFunc()
if (!devnode) if (!devnode)
continue; continue;
// Use g_controller_interface.PlatformPopulateDevices() to protect access around // Use GetControllerInterface().PlatformPopulateDevices() to protect access around
// s_devnode_objects. Note that even if we get these events at the same time as a // m_devnode_objects. Note that even if we get these events at the same time as a
// a PopulateDevices() request (e.g. on start up, we might get all the add events // a PopulateDevices() request (e.g. on start up, we might get all the add events
// for connected devices), this won't ever cause duplicate devices as AddDeviceNode() // for connected devices), this won't ever cause duplicate devices as AddDeviceNode()
// automatically removes the old one if it already existed // automatically removes the old one if it already existed
if (strcmp(action, "remove") == 0) if (strcmp(action, "remove") == 0)
{ {
g_controller_interface.PlatformPopulateDevices([&devnode] { GetControllerInterface().PlatformPopulateDevices([&devnode, this] {
std::shared_ptr<evdevDevice> ptr; std::shared_ptr<evdevDevice> ptr;
const auto it = s_devnode_objects.find(devnode); const auto it = m_devnode_objects.find(devnode);
if (it != s_devnode_objects.end()) if (it != m_devnode_objects.end())
ptr = it->second.lock(); ptr = it->second.lock();
// If we don't recognize this device, ptr will be null and no device will be removed. // 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) { GetControllerInterface().RemoveDevice([&ptr](const auto* device) {
return static_cast<const evdevDevice*>(device) == ptr.get(); return static_cast<const evdevDevice*>(device) == ptr.get();
}); });
}); });
} }
else if (strcmp(action, "add") == 0) else if (strcmp(action, "add") == 0)
{ {
g_controller_interface.PlatformPopulateDevices([&devnode] { AddDeviceNode(devnode); }); GetControllerInterface().PlatformPopulateDevices(
[&devnode, this] { AddDeviceNode(devnode); });
} }
} }
NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread stopped"); NOTICE_LOG_FMT(CONTROLLERINTERFACE, "evdev hotplug thread stopped");
} }
static void StartHotplugThread() void InputBackend::StartHotplugThread()
{ {
// Mark the thread as running. // Mark the thread as running.
if (!s_hotplug_thread_running.TestAndSet()) if (!m_hotplug_thread_running.TestAndSet())
{ {
// It was already running. // It was already running.
return; return;
} }
s_wakeup_eventfd = eventfd(0, 0); m_wakeup_eventfd = eventfd(0, 0);
ASSERT_MSG(CONTROLLERINTERFACE, s_wakeup_eventfd != -1, "Couldn't create eventfd."); ASSERT_MSG(CONTROLLERINTERFACE, m_wakeup_eventfd != -1, "Couldn't create eventfd.");
s_hotplug_thread = std::thread(HotplugThreadFunc); m_hotplug_thread = std::thread(&InputBackend::HotplugThreadFunc, this);
} }
static void StopHotplugThread() void InputBackend::StopHotplugThread()
{ {
// Tell the hotplug thread to stop. // Tell the hotplug thread to stop.
if (!s_hotplug_thread_running.TestAndClear()) if (!m_hotplug_thread_running.TestAndClear())
{ {
// It wasn't running, we're done. // It wasn't running, we're done.
return; return;
@ -381,22 +408,23 @@ static void StopHotplugThread()
// Write something to efd so that select() stops blocking. // Write something to efd so that select() stops blocking.
const uint64_t value = 1; const uint64_t value = 1;
static_cast<void>(!write(s_wakeup_eventfd, &value, sizeof(uint64_t))); static_cast<void>(!write(m_wakeup_eventfd, &value, sizeof(uint64_t)));
s_hotplug_thread.join(); m_hotplug_thread.join();
close(s_wakeup_eventfd); close(m_wakeup_eventfd);
} }
void Init() InputBackend::InputBackend(ControllerInterface* controller_interface)
: ciface::InputBackend(controller_interface)
{ {
StartHotplugThread(); StartHotplugThread();
} }
// Only call this when ControllerInterface::m_devices_population_mutex is locked // Only call this when ControllerInterface::m_devices_population_mutex is locked
void PopulateDevices() void InputBackend::PopulateDevices()
{ {
// Don't run if we are not initialized // Don't run if we are not initialized
if (!s_hotplug_thread_running.IsSet()) if (!m_hotplug_thread_running.IsSet())
{ {
return; return;
} }
@ -431,7 +459,7 @@ void PopulateDevices()
udev_unref(udev); udev_unref(udev);
} }
void Shutdown() InputBackend::~InputBackend()
{ {
StopHotplugThread(); StopHotplugThread();
} }
@ -627,16 +655,25 @@ const char* evdevDevice::GetPhysicalLocation() const
return libevdev_get_phys(m_nodes.front().device); return libevdev_get_phys(m_nodes.front().device);
} }
evdevDevice::evdevDevice(InputBackend* input_backend) : m_input_backend(*input_backend)
{
}
evdevDevice::~evdevDevice() evdevDevice::~evdevDevice()
{ {
for (auto& node : m_nodes) for (auto& node : m_nodes)
{ {
s_devnode_objects.erase(node.devnode); m_input_backend.RemoveDevnodeObject(node.devnode);
libevdev_free(node.device); libevdev_free(node.device);
close(node.fd); close(node.fd);
} }
} }
void InputBackend::RemoveDevnodeObject(const std::string& node)
{
m_devnode_objects.erase(node);
}
void evdevDevice::UpdateInput() void evdevDevice::UpdateInput()
{ {
// Run through all evdev events // Run through all evdev events

View File

@ -11,9 +11,9 @@
namespace ciface::evdev namespace ciface::evdev
{ {
void Init(); class InputBackend;
void PopulateDevices();
void Shutdown(); std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
class evdevDevice : public Core::Device class evdevDevice : public Core::Device
{ {
@ -75,6 +75,7 @@ public:
void UpdateInput() override; void UpdateInput() override;
bool IsValid() const override; bool IsValid() const override;
evdevDevice(InputBackend* input_backend);
~evdevDevice(); ~evdevDevice();
// Return true if node was "interesting". // Return true if node was "interesting".
@ -97,5 +98,7 @@ private:
}; };
std::vector<Node> m_nodes; std::vector<Node> m_nodes;
InputBackend& m_input_backend;
}; };
} // namespace ciface::evdev } // namespace ciface::evdev