mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-06 06:51:26 +12:00
gui/input: use uinput for linux in gui_pad_thread
This commit is contained in:
parent
b032f2dd87
commit
2cd47c0415
4 changed files with 160 additions and 30 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue