mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-31 00:32:53 +00:00
ControllerInterface: evdev InputBackend implementation.
This commit is contained in:
parent
44a4573303
commit
2e5cd5d519
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user