diff --git a/sunshine/platform/linux/input.cpp b/sunshine/platform/linux/input.cpp index 1edb2777..f2e94cd0 100644 --- a/sunshine/platform/linux/input.cpp +++ b/sunshine/platform/linux/input.cpp @@ -45,28 +45,28 @@ KITTY_USING_MOVE_T(pollfd_t, pollfd, read_pollfd, { } }); -using mail_evdev_t = std::tuple; +using mail_evdev_t = std::tuple; constexpr touch_port_t target_touch_port { 0, 0, 19200, 12000 }; -static std::pair operator*(const std::pair &l, int r) { +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) { +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) { +static std::pair &operator+=(std::pair &l, const std::pair &r) { l.first += r.first; l.second += r.second; @@ -190,8 +190,8 @@ class effect_t { public: KITTY_DEFAULT_CONSTR_MOVE(effect_t) - effect_t(uinput_t::pointer dev, rumble_queue_t &&q) - : dev { dev }, rumble_queue { std::move(q) }, gain { 0xFFFFFF }, id_to_data {} {} + 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: @@ -200,7 +200,7 @@ public: data_t(const ff_effect &effect) : delay { effect.replay.delay }, length { effect.replay.length }, - end_point {}, + end_point { std::chrono::steady_clock::time_point::min() }, envelope {}, start {}, end {} { @@ -244,13 +244,13 @@ public: } } - int magnitude(std::chrono::milliseconds time_left, int start, int end) { + 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) { + std::pair rumble(std::chrono::steady_clock::time_point tp) { if(end_point < tp) { return {}; } @@ -285,6 +285,14 @@ public: }; } + 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; @@ -292,24 +300,25 @@ public: ff_envelope envelope; struct { - int weak, strong; + std::uint32_t weak, strong; } start; struct { - int weak, strong; + std::uint32_t weak, strong; } end; }; - std::pair rumble(std::chrono::steady_clock::time_point tp) { - std::pair weak_strong {}; + 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, 0xFFFFFF); - std::clamp(weak_strong.second, 0, 0xFFFFFF); + std::clamp(weak_strong.first, 0, 0xFFFF); + std::clamp(weak_strong.second, 0, 0xFFFF); - return weak_strong * gain / 0xFFFF; + old_rumble = weak_strong * gain / 0xFFFF; + return old_rumble; } void upload(const ff_effect &effect) { @@ -328,17 +337,40 @@ public: 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; }; @@ -390,7 +422,7 @@ public: auto &[dev, _] = gamepads[nr]; // Remove this gamepad from notifications - rumble_ctx->rumble_queue_queue.raise(dev.get(), nullptr, pollfd {}); + rumble_ctx->rumble_queue_queue.raise(nr, dev.get(), nullptr, pollfd {}); std::stringstream ss; @@ -453,6 +485,7 @@ public: auto dev_node = libevdev_uinput_get_devnode(input.get()); rumble_ctx->rumble_queue_queue.raise( + nr, input.get(), std::move(rumble_queue), pollfd_t { @@ -553,7 +586,23 @@ inline void rumbleIterate(std::vector &effects, std::vector 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(); + // 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) { @@ -614,7 +663,11 @@ void broadcastRumble(safe::queue_t &rumble_queue_queue) { return; } - TUPLE_3D_REF(dev, rumble_queue, pollfd, *dev_rumble_queue); + 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; @@ -639,7 +692,7 @@ void broadcastRumble(safe::queue_t &rumble_queue_queue) { } polls.emplace_back(std::move(pollfd)); - effects.emplace_back(dev, std::move(rumble_queue)); + effects.emplace_back(gamepadnr, dev, std::move(rumble_queue)); BOOST_LOG(debug) << "Added Gamepad device to notifications"sv; } @@ -649,6 +702,18 @@ void broadcastRumble(safe::queue_t &rumble_queue_queue) { } 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); + } + } } } } @@ -1064,6 +1129,7 @@ evdev_t x360() { 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); diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 6522405d..b637b0d6 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -110,12 +110,12 @@ public: gp.reset(); } - void rumble(target_t::pointer target, std::uint8_t largeMotor, std::uint8_t smallMotor) { + void rumble(target_t::pointer target, std::uint8_t smallMotor, std::uint8_t largeMotor) { for(int x = 0; x < gamepads.size(); ++x) { auto &[rumble_queue, gp] = gamepads[x]; if(gp.get() == target) { - rumble_queue->raise(x, largeMotor, smallMotor); + rumble_queue->raise(x, ((std::uint16_t)smallMotor) << 8, ((std::uint16_t)largeMotor) << 8); return; } diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index c0f29b3f..3ae10cd9 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -566,8 +566,8 @@ int send_rumble(session_t *session, std::uint16_t id, std::uint16_t lowfreq, std plaintext.useless = 0xC0FFEE; plaintext.id = util::endian::little(id); - plaintext.lowfreq = util::endian::little(lowfreq << 8); - plaintext.highfreq = util::endian::little(highfreq << 8); + plaintext.lowfreq = util::endian::little(lowfreq); + plaintext.highfreq = util::endian::little(highfreq); BOOST_LOG(verbose) << id << " :: "sv << util::hex(lowfreq).to_string_view() << " :: "sv << util::hex(highfreq).to_string_view(); std::array