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

View file

@ -24,7 +24,7 @@ public:
static void InitPadConfig(cfg_pad& cfg, pad_handler type, std::shared_ptr<PadHandlerBase>& 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<PadHandlerBase> m_handler;
std::shared_ptr<Pad> m_pad;

View file

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