Merge pull request #3890 from leoetlino/evdev-hotplug

evdev: Add hotplug support
This commit is contained in:
Pierre Bourdon 2016-07-29 17:43:49 +02:00 committed by GitHub
commit e0cbb9d1ae
18 changed files with 258 additions and 39 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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());
}

View File

@ -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);

View File

@ -623,8 +623,11 @@ void Wiimote::Update()
return;
// returns true if a report was sent
{
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);

View File

@ -186,6 +186,7 @@ void Initialize(void* const hwnd)
s_config.CreateController<HotkeyManager>();
g_controller_interface.Initialize(hwnd);
g_controller_interface.RegisterHotplugCallback(LoadConfig);
// load the saved controller config
s_config.LoadConfig(true);
@ -239,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<u32> bitmasks;

View File

@ -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;
}

View File

@ -4,7 +4,6 @@
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include <wx/bitmap.h>
@ -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,7 +232,6 @@ 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();
@ -242,7 +241,6 @@ static void DrawControlGroupBox(wxDC& dc, ControlGroupBox* g)
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

View File

@ -6,8 +6,19 @@
#include <memory>
#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<std::recursive_mutex> ControllerEmu::GetStateLock()
{
std::unique_lock<std::recursive_mutex> 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)

View File

@ -7,6 +7,7 @@
#include <algorithm>
#include <cmath>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
@ -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<std::recursive_mutex> GetStateLock();
std::vector<std::unique_ptr<ControlGroup>> groups;
ciface::Core::DeviceQualifier default_device;

View File

@ -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;
}
@ -160,6 +163,14 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
m_devices.emplace_back(std::move(device));
}
void ControllerInterface::RemoveDevice(std::function<bool(const ciface::Core::Device*)> callback)
{
std::lock_guard<std::mutex> 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
//
@ -167,9 +178,35 @@ void ControllerInterface::AddDevice(std::shared_ptr<ciface::Core::Device> device
//
void ControllerInterface::UpdateInput()
{
std::lock_guard<std::mutex> lk(m_devices_mutex);
// 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<std::mutex> lk(m_devices_mutex, std::adopt_lock);
for (const auto& d : m_devices)
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<void()> 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();
}
//

View File

@ -122,12 +122,17 @@ public:
void Reinitialize();
void Shutdown();
void AddDevice(std::shared_ptr<ciface::Core::Device> device);
void RemoveDevice(std::function<bool(const ciface::Core::Device*)> callback);
bool IsInit() const { return m_is_init; }
void UpdateReference(ControlReference* control,
const ciface::Core::DeviceQualifier& default_device) const;
void UpdateInput();
void RegisterHotplugCallback(std::function<void(void)> callback);
void InvokeHotplugCallbacks() const;
private:
std::vector<std::function<void()>> m_hotplug_callbacks;
bool m_is_init;
void* m_hwnd;
};

View File

@ -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<Input*>& Inputs() const { return m_inputs; }
const std::vector<Output*>& Outputs() const { return m_outputs; }
Input* FindInput(const std::string& name) const;

View File

@ -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,
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<Device> m_device;
};
class BinaryExpression : public ExpressionNode
@ -393,6 +396,7 @@ private:
{
case TOK_CONTROL:
{
std::shared_ptr<Device> 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> 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;
}

View File

@ -37,10 +37,10 @@ public:
: container(container_), default_device(default_), is_input(is_input_)
{
}
std::shared_ptr<Core::Device> FindDevice(ControlQualifier qualifier);
Core::Device::Control* FindControl(ControlQualifier qualifier);
private:
std::shared_ptr<Core::Device> FindDevice(ControlQualifier qualifier);
const Core::DeviceContainer& container;
const Core::DeviceQualifier& default_device;
bool is_input;

View File

@ -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

View File

@ -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();