gui/input: use uinput for linux in gui_pad_thread

This commit is contained in:
Megamouse 2024-02-11 12:35:33 +01:00
parent b032f2dd87
commit 2cd47c0415
4 changed files with 160 additions and 30 deletions

View file

@ -20,6 +20,12 @@
#include "Utilities/Thread.h" #include "Utilities/Thread.h"
#include "rpcs3qt/gui_settings.h" #include "rpcs3qt/gui_settings.h"
#ifdef __linux__
#include <linux/uinput.h>
#include <fcntl.h>
#define CHECK_IOCTRL_RET(res) if (res == -1) { gui_log.error("gui_pad_thread: ioctl failed (errno=%d=%s)", res, strerror(errno)); }
#endif
#include <QApplication> #include <QApplication>
LOG_CHANNEL(gui_log, "GUI"); LOG_CHANNEL(gui_log, "GUI");
@ -38,6 +44,20 @@ gui_pad_thread::~gui_pad_thread()
m_thread->join(); m_thread->join();
m_thread.reset(); m_thread.reset();
} }
#ifdef __linux__
if (m_uinput_fd != 1)
{
gui_log.notice("gui_pad_thread: closing /dev/uinput");
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_DEV_DESTROY));
int res = close(m_uinput_fd);
if (res == -1)
{
gui_log.error("gui_pad_thread: Failed to close /dev/uinput (errno=%d=%s)", res, strerror(errno));
}
m_uinput_fd = -1;
}
#endif
} }
void gui_pad_thread::update_settings(const std::shared_ptr<gui_settings>& settings) void gui_pad_thread::update_settings(const std::shared_ptr<gui_settings>& settings)
@ -47,7 +67,7 @@ void gui_pad_thread::update_settings(const std::shared_ptr<gui_settings>& settin
m_allow_global_input = settings->GetValue(gui::nav_global).toBool(); m_allow_global_input = settings->GetValue(gui::nav_global).toBool();
} }
void gui_pad_thread::Init() bool gui_pad_thread::init()
{ {
m_handler.reset(); m_handler.reset();
m_pad.reset(); m_pad.reset();
@ -151,6 +171,52 @@ void gui_pad_thread::Init()
// We only use one pad // We only use one pad
break; break;
} }
if (!m_handler || !m_pad)
{
gui_log.notice("gui_pad_thread: No devices configured.");
return false;
}
#ifdef __linux__
gui_log.notice("gui_pad_thread: opening /dev/uinput");
m_uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (m_uinput_fd == -1)
{
gui_log.error("gui_pad_thread: Failed to open /dev/uinput (errno=%d=%s)", m_uinput_fd, strerror(errno));
return false;
}
struct uinput_setup usetup{};
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234;
usetup.id.product = 0x1234;
std::strcpy(usetup.name, "RPCS3 GUI Input Device");
// The ioctls below will enable the device that is about to be created to pass events.
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_EVBIT, EV_KEY));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_ESC));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_ENTER));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_BACKSPACE));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_TAB));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_LEFT));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_RIGHT));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_UP));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, KEY_DOWN));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, BTN_LEFT));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, BTN_RIGHT));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_KEYBIT, BTN_MIDDLE));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_EVBIT, EV_REL));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_RELBIT, REL_X));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_RELBIT, REL_Y));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_RELBIT, REL_WHEEL));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_SET_RELBIT, REL_HWHEEL));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_DEV_SETUP, &usetup));
CHECK_IOCTRL_RET(ioctl(m_uinput_fd, UI_DEV_CREATE));
#endif
return true;
} }
std::shared_ptr<PadHandlerBase> gui_pad_thread::GetHandler(pad_handler type) std::shared_ptr<PadHandlerBase> gui_pad_thread::GetHandler(pad_handler type)
@ -207,7 +273,11 @@ void gui_pad_thread::run()
gui_log.notice("gui_pad_thread: Pad thread started"); gui_log.notice("gui_pad_thread: Pad thread started");
Init(); if (!init())
{
gui_log.warning("gui_pad_thread: Pad thread stopped (init failed)");
return;
}
while (!m_terminate) while (!m_terminate)
{ {
@ -270,6 +340,15 @@ void gui_pad_thread::process_input()
case pad_button::cross: key = VK_RETURN; break; case pad_button::cross: key = VK_RETURN; break;
case pad_button::square: key = VK_BACK; break; case pad_button::square: key = VK_BACK; break;
case pad_button::triangle: key = VK_TAB; break; case pad_button::triangle: key = VK_TAB; break;
#elif defined(__linux__)
case pad_button::dpad_up: key = KEY_UP; break;
case pad_button::dpad_down: key = KEY_DOWN; break;
case pad_button::dpad_left: key = KEY_LEFT; break;
case pad_button::dpad_right: key = KEY_RIGHT; break;
case pad_button::circle: key = KEY_ESC; break;
case pad_button::cross: key = KEY_ENTER; break;
case pad_button::square: key = KEY_BACKSPACE; break;
case pad_button::triangle: key = KEY_TAB; break;
#endif #endif
case pad_button::L1: btn = mouse_button::left; break; case pad_button::L1: btn = mouse_button::left; break;
case pad_button::R1: btn = mouse_button::right; break; case pad_button::R1: btn = mouse_button::right; break;
@ -498,6 +577,22 @@ void gui_pad_thread::process_input()
} }
} }
#ifdef __linux__
void gui_pad_thread::emit_event(int type, int code, int val)
{
struct input_event ie{};
ie.type = type;
ie.code = code;
ie.value = val;
int res = write(m_uinput_fd, &ie, sizeof(ie));
if (res == -1)
{
gui_log.error("gui_pad_thread::emit_event: write failed (errno=%d=%s)", res, strerror(errno));
}
}
#endif
void gui_pad_thread::send_key_event(u32 key, bool pressed) void gui_pad_thread::send_key_event(u32 key, bool pressed)
{ {
gui_log.trace("gui_pad_thread::send_key_event: key=%d, pressed=%d", key, pressed); gui_log.trace("gui_pad_thread::send_key_event: key=%d, pressed=%d", key, pressed);
@ -516,6 +611,9 @@ void gui_pad_thread::send_key_event(u32 key, bool pressed)
{ {
gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr}); gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr});
} }
#elif defined(__linux__)
emit_event(EV_KEY, key, pressed ? 1 : 0);
emit_event(EV_SYN, SYN_REPORT, 0);
#endif #endif
} }
@ -529,30 +627,41 @@ void gui_pad_thread::send_mouse_button_event(mouse_button btn, bool pressed)
switch (btn) switch (btn)
{ {
case mouse_button::none: case mouse_button::none: return;
return; case mouse_button::left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; break;
case mouse_button::left: case mouse_button::right: input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; break;
input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; case mouse_button::middle: input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; break;
break;
case mouse_button::right:
input.mi.dwFlags = pressed ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
break;
case mouse_button::middle:
input.mi.dwFlags = pressed ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP;
break;
} }
if (SendInput(1, &input, sizeof(INPUT)) != 1) if (SendInput(1, &input, sizeof(INPUT)) != 1)
{ {
gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr}); gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr});
} }
#elif defined(__linux__)
int key = 0;
switch (btn)
{
case mouse_button::none: return;
case mouse_button::left: key = BTN_LEFT; break;
case mouse_button::right: key = BTN_RIGHT; break;
case mouse_button::middle: key = BTN_MIDDLE; break;
}
emit_event(EV_KEY, key, pressed ? 1 : 0);
emit_event(EV_SYN, SYN_REPORT, 0);
#endif #endif
} }
void gui_pad_thread::send_mouse_wheel_event(mouse_wheel wheel, float delta) void gui_pad_thread::send_mouse_wheel_event(mouse_wheel wheel, int delta)
{ {
gui_log.trace("gui_pad_thread::send_mouse_wheel_event: wheel=%d, delta=%f", static_cast<int>(wheel), delta); gui_log.trace("gui_pad_thread::send_mouse_wheel_event: wheel=%d, delta=%f", static_cast<int>(wheel), delta);
if (!delta)
{
return;
}
#ifdef _WIN32 #ifdef _WIN32
INPUT input{}; INPUT input{};
input.type = INPUT_MOUSE; input.type = INPUT_MOUSE;
@ -560,27 +669,39 @@ void gui_pad_thread::send_mouse_wheel_event(mouse_wheel wheel, float delta)
switch (wheel) switch (wheel)
{ {
case mouse_wheel::none: case mouse_wheel::none: return;
return; case mouse_wheel::vertical: input.mi.dwFlags = MOUSEEVENTF_WHEEL; break;
case mouse_wheel::vertical: case mouse_wheel::horizontal: input.mi.dwFlags = MOUSEEVENTF_HWHEEL; break;
input.mi.dwFlags = MOUSEEVENTF_WHEEL;
break;
case mouse_wheel::horizontal:
input.mi.dwFlags = MOUSEEVENTF_HWHEEL;
break;
} }
if (SendInput(1, &input, sizeof(INPUT)) != 1) if (SendInput(1, &input, sizeof(INPUT)) != 1)
{ {
gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr}); gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr});
} }
#elif defined(__linux__)
int axis = 0;
switch (wheel)
{
case mouse_wheel::none: return;
case mouse_wheel::vertical: axis = REL_WHEEL; break;
case mouse_wheel::horizontal: axis = REL_HWHEEL; break;
}
emit_event(EV_REL, axis, delta);
emit_event(EV_SYN, SYN_REPORT, 0);
#endif #endif
} }
void gui_pad_thread::send_mouse_move_event(float delta_x, float delta_y) void gui_pad_thread::send_mouse_move_event(int delta_x, int delta_y)
{ {
gui_log.trace("gui_pad_thread::send_mouse_move_event: delta_x=%f, delta_y=%f", delta_x, delta_y); gui_log.trace("gui_pad_thread::send_mouse_move_event: delta_x=%f, delta_y=%f", delta_x, delta_y);
if (!delta_x && !delta_y)
{
return;
}
#ifdef _WIN32 #ifdef _WIN32
INPUT input{}; INPUT input{};
input.type = INPUT_MOUSE; input.type = INPUT_MOUSE;
@ -592,5 +713,9 @@ void gui_pad_thread::send_mouse_move_event(float delta_x, float delta_y)
{ {
gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr}); gui_log.error("gui_pad_thread: SendInput() failed: %s", fmt::win_error{GetLastError(), nullptr});
} }
#elif defined(__linux__)
if (delta_x) emit_event(EV_REL, REL_X, delta_x);
if (delta_y) emit_event(EV_REL, REL_Y, delta_y);
emit_event(EV_SYN, SYN_REPORT, 0);
#endif #endif
} }

View file

@ -24,7 +24,7 @@ public:
static void InitPadConfig(cfg_pad& cfg, pad_handler type, std::shared_ptr<PadHandlerBase>& handler); static void InitPadConfig(cfg_pad& cfg, pad_handler type, std::shared_ptr<PadHandlerBase>& handler);
protected: protected:
void Init(); bool init();
void run(); void run();
void process_input(); void process_input();
@ -44,10 +44,15 @@ protected:
horizontal horizontal
}; };
static void send_key_event(u32 key, bool pressed); void send_key_event(u32 key, bool pressed);
static void send_mouse_button_event(mouse_button btn, bool pressed); void send_mouse_button_event(mouse_button btn, bool pressed);
static void send_mouse_wheel_event(mouse_wheel wheel, float delta); void send_mouse_wheel_event(mouse_wheel wheel, int delta);
static void send_mouse_move_event(float delta_x, float delta_y); void send_mouse_move_event(int delta_x, int delta_y);
#ifdef __linux__
int m_uinput_fd = -1;
void emit_event(int type, int code, int val);
#endif
std::shared_ptr<PadHandlerBase> m_handler; std::shared_ptr<PadHandlerBase> m_handler;
std::shared_ptr<Pad> m_pad; std::shared_ptr<Pad> m_pad;

View file

@ -272,7 +272,7 @@ void main_window::update_gui_pad_thread()
if (enabled && Emu.IsStopped()) if (enabled && Emu.IsStopped())
{ {
#ifdef _WIN32 #if defined(_WIN32) || defined(__linux__)
if (!m_gui_pad_thread) if (!m_gui_pad_thread)
{ {
m_gui_pad_thread = std::make_unique<gui_pad_thread>(); m_gui_pad_thread = std::make_unique<gui_pad_thread>();

View file

@ -2081,7 +2081,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
SubscribeTooltip(ui->cb_global_pad_navigation, tooltips.settings.global_navigation); SubscribeTooltip(ui->cb_global_pad_navigation, tooltips.settings.global_navigation);
ui->cb_pad_navigation->setChecked(m_gui_settings->GetValue(gui::nav_enabled).toBool()); ui->cb_pad_navigation->setChecked(m_gui_settings->GetValue(gui::nav_enabled).toBool());
ui->cb_global_pad_navigation->setChecked(m_gui_settings->GetValue(gui::nav_global).toBool()); ui->cb_global_pad_navigation->setChecked(m_gui_settings->GetValue(gui::nav_global).toBool());
#ifdef _WIN32 #if defined(_WIN32) || defined(__linux__)
connect(ui->cb_pad_navigation, &QCheckBox::toggled, [this](bool checked) connect(ui->cb_pad_navigation, &QCheckBox::toggled, [this](bool checked)
{ {
m_gui_settings->SetValue(gui::nav_enabled, checked); m_gui_settings->SetValue(gui::nav_enabled, checked);