#include #include #include #include #include #include #include #include #include "sunshine/main.h" #include "sunshine/platform/common.h" #include "sunshine/utility.h" #include "sunshine/platform/common.h" // Support older versions #ifndef REL_HWHEEL_HI_RES #define REL_HWHEEL_HI_RES 0x0c #endif #ifndef REL_WHEEL_HI_RES #define REL_WHEEL_HI_RES 0x0b #endif using namespace std::literals; namespace platf { constexpr auto mail_evdev = "platf::evdev"sv; using evdev_t = util::safe_ptr; using uinput_t = util::safe_ptr; constexpr pollfd read_pollfd { -1, 0, 0 }; KITTY_USING_MOVE_T(pollfd_t, pollfd, read_pollfd, { if(el.fd >= 0) { ioctl(el.fd, EVIOCGRAB, (void *)0); close(el.fd); } }); using mail_evdev_t = std::tuple; struct keycode_t { std::uint32_t keycode; std::uint32_t scancode; int pressed; }; constexpr auto UNKNOWN = 0; static constexpr std::array init_keycodes() { std::array keycodes {}; #define __CONVERT(wincode, linuxcode, scancode) \ static_assert(wincode < keycodes.size(), "Keycode doesn't fit into keycode array"); \ static_assert(wincode >= 0, "Are you mad?, keycode needs to be greater than zero"); \ keycodes[wincode] = keycode_t { linuxcode, scancode }; constexpr auto VK_NUMPAD = 0x60; constexpr auto VK_F1 = 0x70; constexpr auto VK_F13 = 0x7C; constexpr auto VK_0 = 0x30; for(auto x = 0; x < 9; ++x) { keycodes[x + VK_NUMPAD + 1] = keycode_t { (std::uint32_t)KEY_KP1 + x, (std::uint32_t)0x70059 + x }; keycodes[x + VK_0 + 1] = keycode_t { (std::uint32_t)KEY_1 + x, (std::uint32_t)0x7001E + x }; } for(auto x = 0; x < 10; ++x) { keycodes[x + VK_F1] = keycode_t { (std::uint32_t)KEY_F1 + x, (std::uint32_t)0x70046 + x }; } for(auto x = 0; x < 12; ++x) { keycodes[x + VK_F13] = keycode_t { (std::uint32_t)KEY_F13 + x, (std::uint32_t)0x7003A + x }; } __CONVERT(0x08 /* VKEY_BACK */, KEY_BACKSPACE, 0x7002A); __CONVERT(0x09 /* VKEY_TAB */, KEY_TAB, 0x7002B); __CONVERT(0x0C /* VKEY_CLEAR */, KEY_CLEAR, UNKNOWN); __CONVERT(0x0D /* VKEY_RETURN */, KEY_ENTER, 0x70028); __CONVERT(0x10 /* VKEY_SHIFT */, KEY_LEFTSHIFT, 0x700E1); __CONVERT(0x11 /* VKEY_CONTROL */, KEY_LEFTCTRL, 0x700E0); __CONVERT(0x12 /* VKEY_MENU */, KEY_LEFTALT, UNKNOWN); __CONVERT(0x13 /* VKEY_PAUSE */, KEY_PAUSE, UNKNOWN); __CONVERT(0x14 /* VKEY_CAPITAL */, KEY_CAPSLOCK, 0x70039); __CONVERT(0x15 /* VKEY_KANA */, KEY_KATAKANAHIRAGANA, UNKNOWN); __CONVERT(0x16 /* VKEY_HANGUL */, KEY_HANGEUL, UNKNOWN); __CONVERT(0x17 /* VKEY_JUNJA */, KEY_HANJA, UNKNOWN); __CONVERT(0x19 /* VKEY_KANJI */, KEY_KATAKANA, UNKNOWN); __CONVERT(0x1B /* VKEY_ESCAPE */, KEY_ESC, 0x70029); __CONVERT(0x20 /* VKEY_SPACE */, KEY_SPACE, 0x7002C); __CONVERT(0x21 /* VKEY_PRIOR */, KEY_PAGEUP, 0x7004B); __CONVERT(0x22 /* VKEY_NEXT */, KEY_PAGEDOWN, 0x7004E); __CONVERT(0x23 /* VKEY_END */, KEY_END, 0x7004D); __CONVERT(0x24 /* VKEY_HOME */, KEY_HOME, 0x7004A); __CONVERT(0x25 /* VKEY_LEFT */, KEY_LEFT, 0x70050); __CONVERT(0x26 /* VKEY_UP */, KEY_UP, 0x70052); __CONVERT(0x27 /* VKEY_RIGHT */, KEY_RIGHT, 0x7004F); __CONVERT(0x28 /* VKEY_DOWN */, KEY_DOWN, 0x70051); __CONVERT(0x29 /* VKEY_SELECT */, KEY_SELECT, UNKNOWN); __CONVERT(0x2A /* VKEY_PRINT */, KEY_PRINT, UNKNOWN); __CONVERT(0x2C /* VKEY_SNAPSHOT */, KEY_SYSRQ, 0x70046); __CONVERT(0x2D /* VKEY_INSERT */, KEY_INSERT, 0x70049); __CONVERT(0x2E /* VKEY_DELETE */, KEY_DELETE, 0x7004C); __CONVERT(0x2F /* VKEY_HELP */, KEY_HELP, UNKNOWN); __CONVERT(0x30 /* VKEY_0 */, KEY_0, 0x70027); __CONVERT(0x31 /* VKEY_1 */, KEY_1, 0x7001E); __CONVERT(0x32 /* VKEY_2 */, KEY_2, 0x7001F); __CONVERT(0x33 /* VKEY_3 */, KEY_3, 0x70020); __CONVERT(0x34 /* VKEY_4 */, KEY_4, 0x70021); __CONVERT(0x35 /* VKEY_5 */, KEY_5, 0x70022); __CONVERT(0x36 /* VKEY_6 */, KEY_6, 0x70023); __CONVERT(0x37 /* VKEY_7 */, KEY_7, 0x70024); __CONVERT(0x38 /* VKEY_8 */, KEY_8, 0x70025); __CONVERT(0x39 /* VKEY_9 */, KEY_9, 0x70026); __CONVERT(0x41 /* VKEY_A */, KEY_A, 0x70004); __CONVERT(0x42 /* VKEY_B */, KEY_B, 0x70005); __CONVERT(0x43 /* VKEY_C */, KEY_C, 0x70006); __CONVERT(0x44 /* VKEY_D */, KEY_D, 0x70007); __CONVERT(0x45 /* VKEY_E */, KEY_E, 0x70008); __CONVERT(0x46 /* VKEY_F */, KEY_F, 0x70009); __CONVERT(0x47 /* VKEY_G */, KEY_G, 0x7000A); __CONVERT(0x48 /* VKEY_H */, KEY_H, 0x7000B); __CONVERT(0x49 /* VKEY_I */, KEY_I, 0x7000C); __CONVERT(0x4A /* VKEY_J */, KEY_J, 0x7000D); __CONVERT(0x4B /* VKEY_K */, KEY_K, 0x7000E); __CONVERT(0x4C /* VKEY_L */, KEY_L, 0x7000F); __CONVERT(0x4D /* VKEY_M */, KEY_M, 0x70010); __CONVERT(0x4E /* VKEY_N */, KEY_N, 0x70011); __CONVERT(0x4F /* VKEY_O */, KEY_O, 0x70012); __CONVERT(0x50 /* VKEY_P */, KEY_P, 0x70013); __CONVERT(0x51 /* VKEY_Q */, KEY_Q, 0x70014); __CONVERT(0x52 /* VKEY_R */, KEY_R, 0x70015); __CONVERT(0x53 /* VKEY_S */, KEY_S, 0x70016); __CONVERT(0x54 /* VKEY_T */, KEY_T, 0x70017); __CONVERT(0x55 /* VKEY_U */, KEY_U, 0x70018); __CONVERT(0x56 /* VKEY_V */, KEY_V, 0x70019); __CONVERT(0x57 /* VKEY_W */, KEY_W, 0x7001A); __CONVERT(0x58 /* VKEY_X */, KEY_X, 0x7001B); __CONVERT(0x59 /* VKEY_Y */, KEY_Y, 0x7001C); __CONVERT(0x5A /* VKEY_Z */, KEY_Z, 0x7001D); __CONVERT(0x5B /* VKEY_LWIN */, KEY_LEFTMETA, 0x700E3); __CONVERT(0x5C /* VKEY_RWIN */, KEY_RIGHTMETA, 0x700E7); __CONVERT(0x5F /* VKEY_SLEEP */, KEY_SLEEP, UNKNOWN); __CONVERT(0x60 /* VKEY_NUMPAD0 */, KEY_KP0, 0x70062); __CONVERT(0x61 /* VKEY_NUMPAD1 */, KEY_KP1, 0x70059); __CONVERT(0x62 /* VKEY_NUMPAD2 */, KEY_KP2, 0x7005A); __CONVERT(0x63 /* VKEY_NUMPAD3 */, KEY_KP3, 0x7005B); __CONVERT(0x64 /* VKEY_NUMPAD4 */, KEY_KP4, 0x7005C); __CONVERT(0x65 /* VKEY_NUMPAD5 */, KEY_KP5, 0x7005D); __CONVERT(0x66 /* VKEY_NUMPAD6 */, KEY_KP6, 0x7005E); __CONVERT(0x67 /* VKEY_NUMPAD7 */, KEY_KP7, 0x7005F); __CONVERT(0x68 /* VKEY_NUMPAD8 */, KEY_KP8, 0x70060); __CONVERT(0x69 /* VKEY_NUMPAD9 */, KEY_KP9, 0x70061); __CONVERT(0x6A /* VKEY_MULTIPLY */, KEY_KPASTERISK, 0x70055); __CONVERT(0x6B /* VKEY_ADD */, KEY_KPPLUS, 0x70057); __CONVERT(0x6C /* VKEY_SEPARATOR */, KEY_KPCOMMA, UNKNOWN); __CONVERT(0x6D /* VKEY_SUBTRACT */, KEY_KPMINUS, 0x70056); __CONVERT(0x6E /* VKEY_DECIMAL */, KEY_KPDOT, 0x70063); __CONVERT(0x6F /* VKEY_DIVIDE */, KEY_KPSLASH, 0x70054); __CONVERT(0x7A /* VKEY_F11 */, KEY_F11, 70044); __CONVERT(0x7B /* VKEY_F12 */, KEY_F12, 70045); __CONVERT(0x90 /* VKEY_NUMLOCK */, KEY_NUMLOCK, 0x70053); __CONVERT(0x91 /* VKEY_SCROLL */, KEY_SCROLLLOCK, 0x70047); __CONVERT(0xA0 /* VKEY_LSHIFT */, KEY_LEFTSHIFT, 0x700E1); __CONVERT(0xA1 /* VKEY_RSHIFT */, KEY_RIGHTSHIFT, 0x700E5); __CONVERT(0xA2 /* VKEY_LCONTROL */, KEY_LEFTCTRL, 0x700E0); __CONVERT(0xA3 /* VKEY_RCONTROL */, KEY_RIGHTCTRL, 0x700E4); __CONVERT(0xA4 /* VKEY_LMENU */, KEY_LEFTALT, 0x7002E); __CONVERT(0xA5 /* VKEY_RMENU */, KEY_RIGHTALT, 0x700E6); __CONVERT(0xBA /* VKEY_OEM_1 */, KEY_SEMICOLON, 0x70033); __CONVERT(0xBB /* VKEY_OEM_PLUS */, KEY_EQUAL, 0x7002E); __CONVERT(0xBC /* VKEY_OEM_COMMA */, KEY_COMMA, 0x70036); __CONVERT(0xBD /* VKEY_OEM_MINUS */, KEY_MINUS, 0x7002D); __CONVERT(0xBE /* VKEY_OEM_PERIOD */, KEY_DOT, 0x70037); __CONVERT(0xBF /* VKEY_OEM_2 */, KEY_SLASH, 0x70038); __CONVERT(0xC0 /* VKEY_OEM_3 */, KEY_GRAVE, 0x70035); __CONVERT(0xDB /* VKEY_OEM_4 */, KEY_LEFTBRACE, 0x7002F); __CONVERT(0xDC /* VKEY_OEM_5 */, KEY_BACKSLASH, 0x70031); __CONVERT(0xDD /* VKEY_OEM_6 */, KEY_RIGHTBRACE, 0x70030); __CONVERT(0xDE /* VKEY_OEM_7 */, KEY_APOSTROPHE, 0x70034); __CONVERT(0xE2 /* VKEY_NON_US_BACKSLASH */, KEY_102ND, 0x70064); #undef __CONVERT return keycodes; } static constexpr auto keycodes = init_keycodes(); constexpr touch_port_t target_touch_port { 0, 0, 19200, 12000 }; static std::pair operator*(const std::pair &l, int r) { return { l.first * r, l.second * r, }; } static std::pair operator/(const std::pair &l, int r) { return { l.first / r, l.second / r, }; } static std::pair &operator+=(std::pair &l, const std::pair &r) { l.first += r.first; l.second += r.second; return l; } static inline void print(const ff_envelope &envelope) { BOOST_LOG(debug) << "Envelope:"sv << std::endl << " attack_length: " << envelope.attack_length << std::endl << " attack_level: " << envelope.attack_level << std::endl << " fade_length: " << envelope.fade_length << std::endl << " fade_level: " << envelope.fade_level; } static inline void print(const ff_replay &replay) { BOOST_LOG(debug) << "Replay:"sv << std::endl << " length: "sv << replay.length << std::endl << " delay: "sv << replay.delay; } static inline void print(const ff_trigger &trigger) { BOOST_LOG(debug) << "Trigger:"sv << std::endl << " button: "sv << trigger.button << std::endl << " interval: "sv << trigger.interval; } static inline void print(const ff_effect &effect) { BOOST_LOG(debug) << std::endl << std::endl << "Received rumble effect with id: ["sv << effect.id << ']'; switch(effect.type) { case FF_CONSTANT: BOOST_LOG(debug) << "FF_CONSTANT:"sv << std::endl << " direction: "sv << effect.direction << std::endl << " level: "sv << effect.u.constant.level; print(effect.u.constant.envelope); break; case FF_PERIODIC: BOOST_LOG(debug) << "FF_CONSTANT:"sv << std::endl << " direction: "sv << effect.direction << std::endl << " waveform: "sv << effect.u.periodic.waveform << std::endl << " period: "sv << effect.u.periodic.period << std::endl << " magnitude: "sv << effect.u.periodic.magnitude << std::endl << " offset: "sv << effect.u.periodic.offset << std::endl << " phase: "sv << effect.u.periodic.phase; print(effect.u.periodic.envelope); break; case FF_RAMP: BOOST_LOG(debug) << "FF_RAMP:"sv << std::endl << " direction: "sv << effect.direction << std::endl << " start_level:" << effect.u.ramp.start_level << std::endl << " end_level:" << effect.u.ramp.end_level; print(effect.u.ramp.envelope); break; case FF_RUMBLE: BOOST_LOG(debug) << "FF_RUMBLE:" << std::endl << " direction: "sv << effect.direction << std::endl << " strong_magnitude: " << effect.u.rumble.strong_magnitude << std::endl << " weak_magnitude: " << effect.u.rumble.weak_magnitude; break; case FF_SPRING: BOOST_LOG(debug) << "FF_SPRING:" << std::endl << " direction: "sv << effect.direction; break; case FF_FRICTION: BOOST_LOG(debug) << "FF_FRICTION:" << std::endl << " direction: "sv << effect.direction; break; case FF_DAMPER: BOOST_LOG(debug) << "FF_DAMPER:" << std::endl << " direction: "sv << effect.direction; break; case FF_INERTIA: BOOST_LOG(debug) << "FF_INERTIA:" << std::endl << " direction: "sv << effect.direction; break; case FF_CUSTOM: BOOST_LOG(debug) << "FF_CUSTOM:" << std::endl << " direction: "sv << effect.direction; break; default: BOOST_LOG(debug) << "FF_UNKNOWN:" << std::endl << " direction: "sv << effect.direction; break; } print(effect.replay); print(effect.trigger); } // Emulate rumble effects class effect_t { public: KITTY_DEFAULT_CONSTR_MOVE(effect_t) effect_t(int gamepadnr, uinput_t::pointer dev, rumble_queue_t &&q) : gamepadnr { gamepadnr }, dev { dev }, rumble_queue { std::move(q) }, gain { 0xFFFF }, id_to_data {} {} class data_t { public: KITTY_DEFAULT_CONSTR(data_t) data_t(const ff_effect &effect) : delay { effect.replay.delay }, length { effect.replay.length }, end_point { std::chrono::steady_clock::time_point::min() }, envelope {}, start {}, end {} { switch(effect.type) { case FF_CONSTANT: start.weak = effect.u.constant.level; start.strong = effect.u.constant.level; end.weak = effect.u.constant.level; end.strong = effect.u.constant.level; envelope = effect.u.constant.envelope; break; case FF_PERIODIC: start.weak = effect.u.periodic.magnitude; start.strong = effect.u.periodic.magnitude; end.weak = effect.u.periodic.magnitude; end.strong = effect.u.periodic.magnitude; envelope = effect.u.periodic.envelope; break; case FF_RAMP: start.weak = effect.u.ramp.start_level; start.strong = effect.u.ramp.start_level; end.weak = effect.u.ramp.end_level; end.strong = effect.u.ramp.end_level; envelope = effect.u.ramp.envelope; break; case FF_RUMBLE: start.weak = effect.u.rumble.weak_magnitude; start.strong = effect.u.rumble.strong_magnitude; end.weak = effect.u.rumble.weak_magnitude; end.strong = effect.u.rumble.strong_magnitude; break; default: BOOST_LOG(warning) << "Effect type ["sv << effect.id << "] not implemented"sv; } } std::uint32_t magnitude(std::chrono::milliseconds time_left, std::uint32_t start, std::uint32_t end) { auto rel = end - start; return start + (rel * time_left.count() / length.count()); } std::pair rumble(std::chrono::steady_clock::time_point tp) { if(end_point < tp) { return {}; } auto time_left = std::chrono::duration_cast( end_point - tp); // If it needs to be delayed' if(time_left > length) { return {}; } auto t = length - time_left; auto weak = magnitude(t, start.weak, end.weak); auto strong = magnitude(t, start.strong, end.strong); if(t.count() < envelope.attack_length) { weak = (envelope.attack_level * t.count() + weak * (envelope.attack_length - t.count())) / envelope.attack_length; strong = (envelope.attack_level * t.count() + strong * (envelope.attack_length - t.count())) / envelope.attack_length; } else if(time_left.count() < envelope.fade_length) { auto dt = (t - length).count() + envelope.fade_length; weak = (envelope.fade_level * dt + weak * (envelope.fade_length - dt)) / envelope.fade_length; strong = (envelope.fade_level * dt + strong * (envelope.fade_length - dt)) / envelope.fade_length; } return { weak, strong }; } void activate() { end_point = std::chrono::steady_clock::now() + delay + length; } void deactivate() { end_point = std::chrono::steady_clock::time_point::min(); } std::chrono::milliseconds delay; std::chrono::milliseconds length; std::chrono::steady_clock::time_point end_point; ff_envelope envelope; struct { std::uint32_t weak, strong; } start; struct { std::uint32_t weak, strong; } end; }; std::pair rumble(std::chrono::steady_clock::time_point tp) { std::pair weak_strong {}; for(auto &[_, data] : id_to_data) { weak_strong += data.rumble(tp); } std::clamp(weak_strong.first, 0, 0xFFFF); std::clamp(weak_strong.second, 0, 0xFFFF); old_rumble = weak_strong * gain / 0xFFFF; return old_rumble; } void upload(const ff_effect &effect) { print(effect); auto it = id_to_data.find(effect.id); if(it == std::end(id_to_data)) { id_to_data.emplace(effect.id, effect); return; } data_t data { effect }; data.end_point = it->second.end_point; it->second = data; } void activate(int id) { auto it = id_to_data.find(id); if(it != std::end(id_to_data)) { it->second.activate(); } } void deactivate(int id) { auto it = id_to_data.find(id); if(it != std::end(id_to_data)) { it->second.deactivate(); } } void erase(int id) { id_to_data.erase(id); BOOST_LOG(debug) << "Removed rumble effect id ["sv << id << ']'; } // Used as ID for rumble notifications int gamepadnr; // Used as ID for adding/removinf devices from evdev notifications uinput_t::pointer dev; rumble_queue_t rumble_queue; int gain; // No need to send rumble data when old values equals the new values std::pair old_rumble; std::unordered_map id_to_data; }; struct rumble_ctx_t { std::thread rumble_thread; safe::queue_t rumble_queue_queue; }; void broadcastRumble(safe::queue_t &ctx); int startRumble(rumble_ctx_t &ctx) { ctx.rumble_thread = std::thread { broadcastRumble, std::ref(ctx.rumble_queue_queue) }; return 0; } void stopRumble(rumble_ctx_t &ctx) { ctx.rumble_queue_queue.stop(); BOOST_LOG(debug) << "Waiting for Gamepad notifications to stop..."sv; ctx.rumble_thread.join(); BOOST_LOG(debug) << "Gamepad notifications stopped"sv; } static auto notifications = safe::make_shared(startRumble, stopRumble); struct input_raw_t { public: void clear_touchscreen() { std::filesystem::path touch_path { appdata() / "sunshine_touchscreen"sv }; if(std::filesystem::is_symlink(touch_path)) { std::filesystem::remove(touch_path); } touch_input.reset(); } void clear_keyboard() { std::filesystem::path key_path { appdata() / "sunshine_keyboard"sv }; if(std::filesystem::is_symlink(key_path)) { std::filesystem::remove(key_path); } keyboard_input.reset(); } void clear_mouse() { std::filesystem::path mouse_path { appdata() / "sunshine_mouse"sv }; if(std::filesystem::is_symlink(mouse_path)) { std::filesystem::remove(mouse_path); } mouse_input.reset(); } void clear_gamepad(int nr) { auto &[dev, _] = gamepads[nr]; if(!dev) { return; } // Remove this gamepad from notifications rumble_ctx->rumble_queue_queue.raise(nr, dev.get(), nullptr, pollfd_t {}); std::stringstream ss; ss << "sunshine_gamepad_"sv << nr; auto gamepad_path = platf::appdata() / ss.str(); if(std::filesystem::is_symlink(gamepad_path)) { std::filesystem::remove(gamepad_path); } gamepads[nr] = std::make_pair(uinput_t {}, gamepad_state_t {}); } int create_mouse() { 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); return -1; } std::filesystem::create_symlink(libevdev_uinput_get_devnode(mouse_input.get()), appdata() / "sunshine_mouse"sv); 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()), appdata() / "sunshine_touchscreen"sv); return 0; } int create_keyboard() { int err = libevdev_uinput_create_from_device(keyboard_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &keyboard_input); if(err) { BOOST_LOG(error) << "Could not create Sunshine Keyboard: "sv << strerror(-err); return -1; } std::filesystem::create_symlink(libevdev_uinput_get_devnode(keyboard_input.get()), appdata() / "sunshine_keyboard"sv); return 0; } int alloc_gamepad(int nr, rumble_queue_t &&rumble_queue) { TUPLE_2D_REF(input, gamepad_state, gamepads[nr]); int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input); gamepad_state = gamepad_state_t {}; if(err) { BOOST_LOG(error) << "Could not create Sunshine Gamepad: "sv << strerror(-err); return -1; } std::stringstream ss; ss << "sunshine_gamepad_"sv << nr; auto gamepad_path = platf::appdata() / ss.str(); if(std::filesystem::is_symlink(gamepad_path)) { std::filesystem::remove(gamepad_path); } auto dev_node = libevdev_uinput_get_devnode(input.get()); rumble_ctx->rumble_queue_queue.raise( nr, input.get(), std::move(rumble_queue), pollfd_t { dup(libevdev_uinput_get_fd(input.get())), (std::int16_t)POLLIN, (std::int16_t)0, }); std::filesystem::create_symlink(dev_node, gamepad_path); return 0; } void clear() { clear_touchscreen(); clear_keyboard(); clear_mouse(); for(int x = 0; x < gamepads.size(); ++x) { clear_gamepad(x); } } ~input_raw_t() { clear(); } safe::shared_t::ptr_t rumble_ctx; std::vector> gamepads; uinput_t mouse_input; uinput_t touch_input; uinput_t keyboard_input; evdev_t gamepad_dev; evdev_t touch_dev; evdev_t mouse_dev; evdev_t keyboard_dev; }; inline void rumbleIterate(std::vector &effects, std::vector &polls, std::chrono::milliseconds to) { std::vector polls_tmp; polls_tmp.reserve(polls.size()); for(auto &poll : polls) { polls_tmp.emplace_back(poll.el); } auto res = poll(polls_tmp.data(), polls.size(), to.count()); // If timed out if(!res) { return; } if(res < 0) { char err_str[1024]; BOOST_LOG(error) << "Couldn't poll Gamepad file descriptors: "sv << strerror_r(errno, err_str, 1024); return; } for(int x = 0; x < polls.size(); ++x) { auto poll = std::begin(polls) + x; auto effect_it = std::begin(effects) + x; auto fd = (*poll)->fd; // TUPLE_2D_REF(dev, q, *dev_q_it); // on error if((*poll)->revents & (POLLHUP | POLLRDHUP | POLLERR)) { BOOST_LOG(warning) << "Gamepad ["sv << x << "] file discriptor closed unexpectedly"sv; polls.erase(poll); effects.erase(effect_it); continue; } if(!((*poll)->revents & POLLIN)) { continue; } input_event events[64]; // Read all available events auto bytes = read(fd, &events, sizeof(events)); if(bytes < 0) { char err_str[1024]; BOOST_LOG(error) << "Couldn't read evdev input ["sv << errno << "]: "sv << strerror_r(errno, err_str, 1024); polls.erase(poll); effects.erase(effect_it); continue; } if(bytes < sizeof(input_event)) { BOOST_LOG(warning) << "Reading evdev input: Expected at least "sv << sizeof(input_event) << " bytes, got "sv << bytes << " instead"sv; continue; } auto event_count = bytes / sizeof(input_event); for(auto event = events; event != (events + event_count); ++event) { switch(event->type) { case EV_FF: // BOOST_LOG(debug) << "EV_FF: "sv << event->value << " aka "sv << util::hex(event->value).to_string_view(); if(event->code == FF_GAIN) { BOOST_LOG(debug) << "EV_FF: code [FF_GAIN]: value: "sv << event->value << " aka "sv << util::hex(event->value).to_string_view(); effect_it->gain = std::clamp(event->value, 0, 0xFFFF); break; } BOOST_LOG(debug) << "EV_FF: id ["sv << event->code << "]: value: "sv << event->value << " aka "sv << util::hex(event->value).to_string_view(); if(event->value) { effect_it->activate(event->code); } else { effect_it->deactivate(event->code); } break; case EV_UINPUT: switch(event->code) { case UI_FF_UPLOAD: { uinput_ff_upload upload {}; // *VERY* important, without this you break // the kernel and have to reboot due to dead // hanging process upload.request_id = event->value; ioctl(fd, UI_BEGIN_FF_UPLOAD, &upload); auto fg = util::fail_guard([&]() { upload.retval = 0; ioctl(fd, UI_END_FF_UPLOAD, &upload); }); effect_it->upload(upload.effect); } break; case UI_FF_ERASE: { uinput_ff_erase erase {}; // *VERY* important, without this you break // the kernel and have to reboot due to dead // hanging process erase.request_id = event->value; ioctl(fd, UI_BEGIN_FF_ERASE, &erase); auto fg = util::fail_guard([&]() { erase.retval = 0; ioctl(fd, UI_END_FF_ERASE, &erase); }); effect_it->erase(erase.effect_id); } break; } break; default: BOOST_LOG(debug) << util::hex(event->type).to_string_view() << ": "sv << util::hex(event->code).to_string_view() << ": "sv << event->value << " aka "sv << util::hex(event->value).to_string_view(); } } } } void broadcastRumble(safe::queue_t &rumble_queue_queue) { std::vector effects; std::vector polls; while(rumble_queue_queue.running()) { while(rumble_queue_queue.peek()) { auto dev_rumble_queue = rumble_queue_queue.pop(); if(!dev_rumble_queue) { // rumble_queue_queue is no longer running return; } auto gamepadnr = std::get<0>(*dev_rumble_queue); auto dev = std::get<1>(*dev_rumble_queue); auto &rumble_queue = std::get<2>(*dev_rumble_queue); auto &pollfd = std::get<3>(*dev_rumble_queue); { auto effect_it = std::find_if(std::begin(effects), std::end(effects), [dev](auto &curr_effect) { return dev == curr_effect.dev; }); if(effect_it != std::end(effects)) { auto poll_it = std::begin(polls) + (effect_it - std::begin(effects)); polls.erase(poll_it); effects.erase(effect_it); BOOST_LOG(debug) << "Removed Gamepad device from notifications"sv; continue; } // There may be an attepmt to remove, that which not exists if(!rumble_queue) { BOOST_LOG(warning) << "Attempting to remove a gamepad device from notifications that isn't already registered"sv; continue; } } polls.emplace_back(std::move(pollfd)); effects.emplace_back(gamepadnr, dev, std::move(rumble_queue)); BOOST_LOG(debug) << "Added Gamepad device to notifications"sv; } if(polls.empty()) { std::this_thread::sleep_for(250ms); } else { rumbleIterate(effects, polls, 100ms); auto now = std::chrono::steady_clock::now(); for(auto &effect : effects) { TUPLE_2D(old_weak, old_strong, effect.old_rumble); TUPLE_2D(weak, strong, effect.rumble(now)); if(old_weak != weak || old_strong != strong) { BOOST_LOG(debug) << "Sending haptic feedback: lowfreq [0x"sv << util::hex(weak).to_string_view() << "]: highfreq [0x"sv << util::hex(strong).to_string_view() << ']'; effect.rumble_queue->raise(effect.gamepadnr, weak, strong); } } } } } void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) { auto touchscreen = ((input_raw_t *)input.get())->touch_input.get(); auto scaled_x = (int)std::lround((x + touch_port.offset_x) * ((float)target_touch_port.width / (float)touch_port.width)); auto scaled_y = (int)std::lround((y + touch_port.offset_y) * ((float)target_touch_port.height / (float)touch_port.height)); libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_X, scaled_x); libevdev_uinput_write_event(touchscreen, EV_ABS, ABS_Y, scaled_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(); if(deltaX) { libevdev_uinput_write_event(mouse, EV_REL, REL_X, deltaX); } if(deltaY) { libevdev_uinput_write_event(mouse, EV_REL, REL_Y, deltaY); } libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); } void button_mouse(input_t &input, int button, bool release) { int btn_type; int scan; if(button == 1) { btn_type = BTN_LEFT; scan = 90001; } else if(button == 2) { btn_type = BTN_MIDDLE; scan = 90003; } else if(button == 3) { btn_type = BTN_RIGHT; scan = 90002; } else if(button == 4) { btn_type = BTN_SIDE; scan = 90004; } else { btn_type = BTN_EXTRA; scan = 90005; } auto mouse = ((input_raw_t *)input.get())->mouse_input.get(); libevdev_uinput_write_event(mouse, EV_MSC, MSC_SCAN, scan); libevdev_uinput_write_event(mouse, EV_KEY, btn_type, release ? 0 : 1); libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); } void scroll(input_t &input, int high_res_distance) { int distance = high_res_distance / 120; auto mouse = ((input_raw_t *)input.get())->mouse_input.get(); libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL, distance); libevdev_uinput_write_event(mouse, EV_REL, REL_WHEEL_HI_RES, high_res_distance); libevdev_uinput_write_event(mouse, EV_SYN, SYN_REPORT, 0); } static keycode_t keysym(std::uint16_t modcode) { if(modcode <= keycodes.size()) { return keycodes[modcode]; } return {}; } void keyboard(input_t &input, uint16_t modcode, bool release) { auto keyboard = ((input_raw_t *)input.get())->keyboard_input.get(); auto keycode = keysym(modcode); if(keycode.keycode == UNKNOWN) { return; } if(keycode.scancode != UNKNOWN && (release || !keycode.pressed)) { libevdev_uinput_write_event(keyboard, EV_MSC, MSC_SCAN, keycode.scancode); } libevdev_uinput_write_event(keyboard, EV_KEY, keycode.keycode, release ? 0 : (1 + keycode.pressed)); libevdev_uinput_write_event(keyboard, EV_SYN, SYN_REPORT, 0); keycode.pressed = 1; } int alloc_gamepad(input_t &input, int nr, rumble_queue_t rumble_queue) { return ((input_raw_t *)input.get())->alloc_gamepad(nr, std::move(rumble_queue)); } void free_gamepad(input_t &input, int nr) { ((input_raw_t *)input.get())->clear_gamepad(nr); } void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { TUPLE_2D_REF(uinput, gamepad_state_old, ((input_raw_t *)input.get())->gamepads[nr]); auto bf = gamepad_state.buttonFlags ^ gamepad_state_old.buttonFlags; auto bf_new = gamepad_state.buttonFlags; if(bf) { // up pressed == -1, down pressed == 1, else 0 if((DPAD_UP | DPAD_DOWN) & bf) { int button_state = bf_new & DPAD_UP ? -1 : (bf_new & DPAD_DOWN ? 1 : 0); libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0Y, button_state); } if((DPAD_LEFT | DPAD_RIGHT) & bf) { int button_state = bf_new & DPAD_LEFT ? -1 : (bf_new & DPAD_RIGHT ? 1 : 0); libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_HAT0X, button_state); } if(START & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_START, bf_new & START ? 1 : 0); if(BACK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SELECT, bf_new & BACK ? 1 : 0); if(LEFT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBL, bf_new & LEFT_STICK ? 1 : 0); if(RIGHT_STICK & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_THUMBR, bf_new & RIGHT_STICK ? 1 : 0); if(LEFT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TL, bf_new & LEFT_BUTTON ? 1 : 0); if(RIGHT_BUTTON & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_TR, bf_new & RIGHT_BUTTON ? 1 : 0); if(HOME & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_MODE, bf_new & HOME ? 1 : 0); if(A & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_SOUTH, bf_new & A ? 1 : 0); if(B & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_EAST, bf_new & B ? 1 : 0); if(X & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_NORTH, bf_new & X ? 1 : 0); if(Y & bf) libevdev_uinput_write_event(uinput.get(), EV_KEY, BTN_WEST, bf_new & Y ? 1 : 0); } if(gamepad_state_old.lt != gamepad_state.lt) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_Z, gamepad_state.lt); } if(gamepad_state_old.rt != gamepad_state.rt) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_RZ, gamepad_state.rt); } if(gamepad_state_old.lsX != gamepad_state.lsX) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_X, gamepad_state.lsX); } if(gamepad_state_old.lsY != gamepad_state.lsY) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_Y, -gamepad_state.lsY); } if(gamepad_state_old.rsX != gamepad_state.rsX) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_RX, gamepad_state.rsX); } if(gamepad_state_old.rsY != gamepad_state.rsY) { libevdev_uinput_write_event(uinput.get(), EV_ABS, ABS_RY, -gamepad_state.rsY); } gamepad_state_old = gamepad_state; libevdev_uinput_write_event(uinput.get(), EV_SYN, SYN_REPORT, 0); } evdev_t keyboard() { evdev_t dev { libevdev_new() }; libevdev_set_uniq(dev.get(), "Sunshine Keyboard"); 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(), "Keyboard passthrough"); libevdev_enable_event_type(dev.get(), EV_KEY); for(const auto &keycode : keycodes) { libevdev_enable_event_code(dev.get(), EV_KEY, keycode.keycode, nullptr); } libevdev_enable_event_type(dev.get(), EV_MSC); libevdev_enable_event_code(dev.get(), EV_MSC, MSC_SCAN, nullptr); return dev; } evdev_t mouse() { evdev_t dev { libevdev_new() }; libevdev_set_uniq(dev.get(), "Sunshine Mouse"); libevdev_set_id_product(dev.get(), 0x4038); libevdev_set_id_vendor(dev.get(), 0x46D); libevdev_set_id_bustype(dev.get(), 0x3); libevdev_set_id_version(dev.get(), 0x111); libevdev_set_name(dev.get(), "Logitech Wireless Mouse PID:4038"); libevdev_enable_event_type(dev.get(), EV_KEY); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_LEFT, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_RIGHT, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_MIDDLE, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_SIDE, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_EXTRA, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_FORWARD, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_BACK, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TASK, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 280, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 281, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 282, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 283, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 284, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 285, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 286, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, 287, nullptr); libevdev_enable_event_type(dev.get(), EV_REL); libevdev_enable_event_code(dev.get(), EV_REL, REL_X, nullptr); libevdev_enable_event_code(dev.get(), EV_REL, REL_Y, nullptr); libevdev_enable_event_code(dev.get(), EV_REL, REL_WHEEL, nullptr); libevdev_enable_event_code(dev.get(), EV_REL, REL_WHEEL_HI_RES, nullptr); libevdev_enable_event_code(dev.get(), EV_REL, REL_HWHEEL, nullptr); libevdev_enable_event_code(dev.get(), EV_REL, REL_HWHEEL_HI_RES, nullptr); libevdev_enable_event_type(dev.get(), EV_MSC); libevdev_enable_event_code(dev.get(), EV_MSC, MSC_SCAN, nullptr); 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, target_touch_port.width, 1, 0, 28 }; input_absinfo absy { 0, 0, target_touch_port.height, 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() }; input_absinfo stick { 0, -32768, 32767, 16, 128, 0 }; input_absinfo trigger { 0, 0, 255, 0, 0, 0 }; input_absinfo dpad { 0, -1, 1, 0, 0, 0 }; libevdev_set_uniq(dev.get(), "Sunshine Gamepad"); libevdev_set_id_product(dev.get(), 0x28E); libevdev_set_id_vendor(dev.get(), 0x45E); libevdev_set_id_bustype(dev.get(), 0x3); libevdev_set_id_version(dev.get(), 0x110); libevdev_set_name(dev.get(), "Microsoft X-Box 360 pad"); libevdev_enable_event_type(dev.get(), EV_KEY); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_WEST, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_EAST, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_NORTH, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_SOUTH, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_THUMBL, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_THUMBR, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TR, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_TL, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_SELECT, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_MODE, nullptr); libevdev_enable_event_code(dev.get(), EV_KEY, BTN_START, nullptr); libevdev_enable_event_type(dev.get(), EV_ABS); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_HAT0Y, &dpad); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_HAT0X, &dpad); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Z, &trigger); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_RZ, &trigger); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_X, &stick); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_RX, &stick); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_Y, &stick); libevdev_enable_event_code(dev.get(), EV_ABS, ABS_RY, &stick); libevdev_enable_event_type(dev.get(), EV_FF); libevdev_enable_event_code(dev.get(), EV_FF, FF_RUMBLE, nullptr); libevdev_enable_event_code(dev.get(), EV_FF, FF_CONSTANT, nullptr); libevdev_enable_event_code(dev.get(), EV_FF, FF_PERIODIC, nullptr); libevdev_enable_event_code(dev.get(), EV_FF, FF_SINE, nullptr); libevdev_enable_event_code(dev.get(), EV_FF, FF_RAMP, nullptr); libevdev_enable_event_code(dev.get(), EV_FF, FF_GAIN, nullptr); return dev; } input_t input() { input_t result { new input_raw_t() }; auto &gp = *(input_raw_t *)result.get(); gp.rumble_ctx = notifications.ref(); gp.gamepads.resize(MAX_GAMEPADS); // Ensure starting from clean slate gp.clear(); gp.keyboard_dev = keyboard(); gp.touch_dev = touchscreen(); gp.mouse_dev = mouse(); gp.gamepad_dev = x360(); // If we do not have a keyboard, gamepad or mouse, no input is possible and we should abort if(gp.create_mouse() || gp.create_touchscreen() || gp.create_keyboard()) { log_flush(); std::abort(); } return result; } void freeInput(void *p) { auto *input = (input_raw_t *)p; delete input; } std::vector &supported_gamepads() { static std::vector gamepads { "x360"sv }; return gamepads; } } // namespace platf