From 2cd47c0415034a7210c80dccaf0f294e43fb0e66 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 11 Feb 2024 12:35:33 +0100 Subject: [PATCH] gui/input: use uinput for linux in gui_pad_thread --- rpcs3/Input/gui_pad_thread.cpp | 171 ++++++++++++++++++++++++++---- rpcs3/Input/gui_pad_thread.h | 15 ++- rpcs3/rpcs3qt/main_window.cpp | 2 +- rpcs3/rpcs3qt/settings_dialog.cpp | 2 +- 4 files changed, 160 insertions(+), 30 deletions(-) diff --git a/rpcs3/Input/gui_pad_thread.cpp b/rpcs3/Input/gui_pad_thread.cpp index 8817c78f2a..91e5a5b094 100644 --- a/rpcs3/Input/gui_pad_thread.cpp +++ b/rpcs3/Input/gui_pad_thread.cpp @@ -20,6 +20,12 @@ #include "Utilities/Thread.h" #include "rpcs3qt/gui_settings.h" +#ifdef __linux__ +#include +#include +#define CHECK_IOCTRL_RET(res) if (res == -1) { gui_log.error("gui_pad_thread: ioctl failed (errno=%d=%s)", res, strerror(errno)); } +#endif + #include LOG_CHANNEL(gui_log, "GUI"); @@ -38,6 +44,20 @@ gui_pad_thread::~gui_pad_thread() m_thread->join(); 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& settings) @@ -47,7 +67,7 @@ void gui_pad_thread::update_settings(const std::shared_ptr& settin m_allow_global_input = settings->GetValue(gui::nav_global).toBool(); } -void gui_pad_thread::Init() +bool gui_pad_thread::init() { m_handler.reset(); m_pad.reset(); @@ -151,6 +171,52 @@ void gui_pad_thread::Init() // We only use one pad 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 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"); - Init(); + if (!init()) + { + gui_log.warning("gui_pad_thread: Pad thread stopped (init failed)"); + return; + } while (!m_terminate) { @@ -270,6 +340,15 @@ void gui_pad_thread::process_input() case pad_button::cross: key = VK_RETURN; break; case pad_button::square: key = VK_BACK; 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 case pad_button::L1: btn = mouse_button::left; 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) { 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}); } +#elif defined(__linux__) + emit_event(EV_KEY, key, pressed ? 1 : 0); + emit_event(EV_SYN, SYN_REPORT, 0); #endif } @@ -529,30 +627,41 @@ void gui_pad_thread::send_mouse_button_event(mouse_button btn, bool pressed) switch (btn) { - case mouse_button::none: - return; - case mouse_button::left: - input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; - 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; + case mouse_button::none: return; + case mouse_button::left: input.mi.dwFlags = pressed ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; 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) { 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 } -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(wheel), delta); + if (!delta) + { + return; + } + #ifdef _WIN32 INPUT input{}; input.type = INPUT_MOUSE; @@ -560,27 +669,39 @@ void gui_pad_thread::send_mouse_wheel_event(mouse_wheel wheel, float delta) switch (wheel) { - case mouse_wheel::none: - return; - case mouse_wheel::vertical: - input.mi.dwFlags = MOUSEEVENTF_WHEEL; - break; - case mouse_wheel::horizontal: - input.mi.dwFlags = MOUSEEVENTF_HWHEEL; - break; + case mouse_wheel::none: return; + case mouse_wheel::vertical: input.mi.dwFlags = MOUSEEVENTF_WHEEL; break; + case mouse_wheel::horizontal: input.mi.dwFlags = MOUSEEVENTF_HWHEEL; break; } if (SendInput(1, &input, sizeof(INPUT)) != 1) { 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 } -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); + if (!delta_x && !delta_y) + { + return; + } + #ifdef _WIN32 INPUT input{}; 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}); } +#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 } diff --git a/rpcs3/Input/gui_pad_thread.h b/rpcs3/Input/gui_pad_thread.h index 375d50c3dc..be2c4c293b 100644 --- a/rpcs3/Input/gui_pad_thread.h +++ b/rpcs3/Input/gui_pad_thread.h @@ -24,7 +24,7 @@ public: static void InitPadConfig(cfg_pad& cfg, pad_handler type, std::shared_ptr& handler); protected: - void Init(); + bool init(); void run(); void process_input(); @@ -44,10 +44,15 @@ protected: horizontal }; - static void send_key_event(u32 key, bool pressed); - static void send_mouse_button_event(mouse_button btn, bool pressed); - static void send_mouse_wheel_event(mouse_wheel wheel, float delta); - static void send_mouse_move_event(float delta_x, float delta_y); + void send_key_event(u32 key, bool pressed); + void send_mouse_button_event(mouse_button btn, bool pressed); + void send_mouse_wheel_event(mouse_wheel wheel, int delta); + 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 m_handler; std::shared_ptr m_pad; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index b3dcc239bc..51c2d7f5e7 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -272,7 +272,7 @@ void main_window::update_gui_pad_thread() if (enabled && Emu.IsStopped()) { -#ifdef _WIN32 +#if defined(_WIN32) || defined(__linux__) if (!m_gui_pad_thread) { m_gui_pad_thread = std::make_unique(); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 7a2f7e39cf..c517ab4e89 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -2081,7 +2081,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std 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_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) { m_gui_settings->SetValue(gui::nav_enabled, checked);