diff --git a/sunshine/input.cpp b/sunshine/input.cpp index a9aee2c4..81a4920a 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -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 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, 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, 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, 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, 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 &gamepads, std::int16_t old_state, std::int16_t new_state) { @@ -390,7 +433,10 @@ void passthrough_helper(std::shared_ptr input, std::vector input, std::vector &input, std::vector &&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) { + 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; diff --git a/sunshine/input.h b/sunshine/input.h index e1d4c82c..71e1805c 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -11,7 +11,7 @@ namespace input { struct input_t; void print(void *input); -void reset(); +void reset(std::shared_ptr &input); void passthrough(std::shared_ptr &input, std::vector &&input_data); void init(); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index fa6b3be7..c8675bfc 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -146,6 +146,7 @@ std::shared_ptr 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); diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index 6bb5d495..df34e6e8 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -31,6 +31,15 @@ using keyboard_t = util::safe_ptr_v2; 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> 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(); } diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 284f3c83..d0af736e 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -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 {}; diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index a86f4cca..c505fc0a 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -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; }