absolute mouse coordinate support for single monitor on Linux

This commit is contained in:
loki 2021-05-11 18:01:56 +02:00
parent b97c902d10
commit 41cc9a3e80
6 changed files with 152 additions and 27 deletions

View File

@ -19,6 +19,9 @@ extern "C" {
namespace input {
constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t)*8);
#define DISABLE_LEFT_BUTTON_DELAY ((util::ThreadPool::task_id_t)0x01)
#define ENABLE_LEFT_BUTTON_DELAY nullptr
enum class button_state_e {
NONE,
DOWN,
@ -80,10 +83,12 @@ struct gamepad_t {
};
struct input_t {
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS) { }
input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS), mouse_left_button_timeout {} { }
std::uint16_t active_gamepad_state;
std::vector<gamepad_t> gamepads;
util::ThreadPool::task_id_t mouse_left_button_timeout;
};
using namespace std::literals;
@ -177,23 +182,61 @@ void print(void *input) {
}
}
void passthrough(platf::input_t &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
void passthrough(std::shared_ptr<input_t> &input, PNV_REL_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
platf::move_mouse(input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
input->mouse_left_button_timeout = DISABLE_LEFT_BUTTON_DELAY;
platf::move_mouse(platf_input, util::endian::big(packet->deltaX), util::endian::big(packet->deltaY));
}
void passthrough(std::shared_ptr<input_t> &input, PNV_ABS_MOUSE_MOVE_PACKET packet) {
display_cursor = true;
if(input->mouse_left_button_timeout == DISABLE_LEFT_BUTTON_DELAY) {
input->mouse_left_button_timeout = ENABLE_LEFT_BUTTON_DELAY;
}
platf::abs_mouse(platf_input, util::endian::big(packet->x), util::endian::big(packet->y));
}
void passthrough(std::shared_ptr<input_t> &input, PNV_MOUSE_BUTTON_PACKET packet) {
auto constexpr BUTTON_RELEASED = 0x09;
auto constexpr BUTTON_LEFT = 0x01;
display_cursor = true;
auto release = packet->action == BUTTON_RELEASED;
auto button = util::endian::big(packet->button);
if(button > 0 && button < mouse_press.size()) {
mouse_press[button] = packet->action != BUTTON_RELEASED;
mouse_press[button] = !release;
}
platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED);
/*/
* When Moonlight sends mouse input through absolute coordinates,
* it's possible that BUTTON_RIGHT is pressed down immediately after releasing BUTTON_LEFT.
* As a result, Sunshine will left click on hyperlinks in the browser before right clicking
*
* This can be solved by delaying BUTTON_LEFT, however, any delay on input is undesirable during gaming
* As a compromise, Sunshine will only put delays on BUTTON_LEFT when
* absolute mouse coordinates have been send.
*
* Try to make sure BUTTON_RIGHT gets called before BUTTON_LEFT is released.
*
* input->mouse_left_button_timeout can only be nullptr
* when the last mouse coordinates were absolute
/*/
if(button == BUTTON_LEFT && release && !input->mouse_left_button_timeout) {
input->mouse_left_button_timeout = task_pool.pushDelayed([=]() {
platf::button_mouse(platf_input, button, release);
input->mouse_left_button_timeout = nullptr;
}, 10ms).task_id;
return;
}
platf::button_mouse(platf_input, button, release);
}
void repeat_key(short key_code) {
@ -238,10 +281,10 @@ void passthrough(std::shared_ptr<input_t> &input, PNV_KEYBOARD_PACKET packet) {
platf::keyboard(platf_input, packet->keyCode & 0x00FF, release);
}
void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) {
void passthrough(PNV_SCROLL_PACKET packet) {
display_cursor = true;
platf::scroll(input, util::endian::big(packet->scrollAmt1));
platf::scroll(platf_input, util::endian::big(packet->scrollAmt1));
}
int updateGamepads(std::vector<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state) {
@ -390,7 +433,10 @@ void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t
switch(input_type) {
case PACKET_TYPE_REL_MOUSE_MOVE:
passthrough(platf_input, (PNV_REL_MOUSE_MOVE_PACKET)payload);
passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_ABS_MOUSE_MOVE:
passthrough(input, (PNV_ABS_MOUSE_MOVE_PACKET)payload);
break;
case PACKET_TYPE_MOUSE_BUTTON:
passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload);
@ -399,7 +445,7 @@ void passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t
{
char *tmp_input = (char*)payload + 4;
if(tmp_input[0] == 0x0A) {
passthrough(platf_input, (PNV_SCROLL_PACKET)payload);
passthrough((PNV_SCROLL_PACKET)payload);
}
else {
passthrough(input, (PNV_KEYBOARD_PACKET)payload);
@ -417,13 +463,16 @@ void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&in
task_pool.push(passthrough_helper, input, util::cmove(input_data));
}
void reset() {
if(task_id) {
task_pool.cancel(task_id);
}
void reset(std::shared_ptr<input_t> &input) {
task_pool.cancel(task_id);
task_pool.cancel(input->mouse_left_button_timeout);
// Ensure input is synchronous
// Ensure input is synchronous, by using the task_pool
task_pool.push([]() {
for(int x = 0; x < mouse_press.size(); ++x) {
platf::button_mouse(platf_input, x, true);
}
for(auto& kp : key_press) {
platf::keyboard(platf_input, kp.first & 0x00FF, true);
key_press[kp.first] = false;

View File

@ -11,7 +11,7 @@ namespace input {
struct input_t;
void print(void *input);
void reset();
void reset(std::shared_ptr<input_t> &input);
void passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data);
void init();

View File

@ -146,6 +146,7 @@ std::shared_ptr<display_t> display(dev_type_e hwdevice_type);
input_t input();
void move_mouse(input_t &input, int deltaX, int deltaY);
void abs_mouse(input_t &input, int x, int y);
void button_mouse(input_t &input, int button, bool release);
void scroll(input_t &input, int distance);
void keyboard(input_t &input, uint16_t modcode, bool release);

View File

@ -31,6 +31,15 @@ using keyboard_t = util::safe_ptr_v2<Display, int, XCloseDisplay>;
struct input_raw_t {
public:
void clear_touchscreen() {
std::filesystem::path touch_path { "sunshine_touchscreen"sv };
if(std::filesystem::is_symlink(touch_path)) {
std::filesystem::remove(touch_path);
}
touch_input.reset();
}
void clear_mouse() {
std::filesystem::path mouse_path { "sunshine_mouse"sv };
@ -55,9 +64,7 @@ public:
}
int create_mouse() {
libevdev_uinput *buf {};
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
mouse_input.reset(buf);
int err = libevdev_uinput_create_from_device(mouse_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &mouse_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Mouse: "sv << strerror(-err);
@ -69,13 +76,24 @@ public:
return 0;
}
int create_touchscreen() {
int err = libevdev_uinput_create_from_device(touch_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &touch_input);
if(err) {
BOOST_LOG(error) << "Could not create Sunshine Touchscreen: "sv << strerror(-err);
return -1;
}
std::filesystem::create_symlink(libevdev_uinput_get_devnode(touch_input.get()), "sunshine_touchscreen"sv);
return 0;
}
int alloc_gamepad(int nr) {
TUPLE_2D_REF(input, gamepad_state, gamepads[nr]);
libevdev_uinput *buf;
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &buf);
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input);
input.reset(buf);
gamepad_state = gamepad_state_t {};
if(err) {
@ -96,6 +114,7 @@ public:
}
void clear() {
clear_touchscreen();
clear_mouse();
for(int x = 0; x < gamepads.size(); ++x) {
clear_gamepad(x);
@ -106,16 +125,28 @@ public:
clear();
}
evdev_t gamepad_dev;
std::vector<std::pair<uinput_t, gamepad_state_t>> gamepads;
evdev_t mouse_dev;
uinput_t mouse_input;
uinput_t touch_input;
evdev_t gamepad_dev;
evdev_t touch_dev;
evdev_t mouse_dev;
keyboard_t keyboard;
};
void abs_mouse(input_t &input, int x, int y) {
auto touchscreen = ((input_raw_t*)input.get())->touch_input.get();
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, x);
libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, y);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 1);
libevdev_uinput_write_event(touchscreen, EV_KEY, BTN_TOOL_FINGER, 0);
libevdev_uinput_write_event(touchscreen, EV_SYN, SYN_REPORT, 0);
}
void move_mouse(input_t &input, int deltaX, int deltaY) {
auto mouse = ((input_raw_t*)input.get())->mouse_input.get();
@ -409,6 +440,48 @@ evdev_t mouse() {
return dev;
}
evdev_t touchscreen() {
evdev_t dev { libevdev_new() };
libevdev_set_uniq(dev.get(), "Sunshine Touch");
libevdev_set_id_product(dev.get(), 0xDEAD);
libevdev_set_id_vendor(dev.get(), 0xBEEF);
libevdev_set_id_bustype(dev.get(), 0x3);
libevdev_set_id_version(dev.get(), 0x111);
libevdev_set_name(dev.get(), "Touchscreen passthrough");
libevdev_enable_property(dev.get(), INPUT_PROP_DIRECT);
libevdev_enable_event_type(dev.get(), EV_KEY);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOUCH, nullptr);
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_PEN, nullptr); // Needed to be enabled for BTN_TOOL_FINGER to work.
libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TOOL_FINGER, nullptr);
input_absinfo absx {
0,
0,
1919,
1,
0,
28
};
input_absinfo absy {
0,
0,
1199,
1,
0,
28
};
libevdev_enable_event_type(dev.get(), EV_ABS);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &absx);
libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &absy);
return dev;
}
evdev_t x360() {
evdev_t dev { libevdev_new() };
@ -486,10 +559,11 @@ input_t input() {
// Ensure starting from clean slate
gp.clear();
gp.touch_dev = touchscreen();
gp.mouse_dev = mouse();
gp.gamepad_dev = x360();
if(gp.create_mouse()) {
if(gp.create_mouse() || gp.create_touchscreen()) {
log_flush();
std::abort();
}

View File

@ -165,6 +165,7 @@ input_t input() {
return result;
}
void abs_mouse(input_t &input, int x, int y) {}
void move_mouse(input_t &input, int deltaX, int deltaY) {
INPUT i {};

View File

@ -902,7 +902,7 @@ void join(session_t &session) {
session.controlEnd.view();
//Reset input on session stop to avoid stuck repeated keys
BOOST_LOG(debug) << "Resetting Input..."sv;
input::reset();
input::reset(session.input);
BOOST_LOG(debug) << "Session ended"sv;
}