diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 6e4c092e..09ddbe2f 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -46,7 +46,7 @@ void free_id(std::bitset &gamepad_mask, int id) { gamepad_mask[id] = false; } -static util::TaskPool::task_id_t task_id {}; +static util::TaskPool::task_id_t key_press_repeat_id {}; static std::unordered_map key_press {}; static std::array mouse_press {}; @@ -84,10 +84,13 @@ struct gamepad_t { }; struct input_t { - input_t(safe::mail_raw_t::event_t touch_port_event) + input_t( + safe::mail_raw_t::event_t touch_port_event, + platf::rumble_queue_t rumble_queue) : active_gamepad_state {}, gamepads(MAX_GAMEPADS), touch_port_event { std::move(touch_port_event) }, + rumble_queue { std::move(rumble_queue) }, mouse_left_button_timeout {}, touch_port { 0, 0, 0, 0, 0, 0, 1.0f } {} @@ -95,6 +98,7 @@ struct input_t { std::vector gamepads; safe::mail_raw_t::event_t touch_port_event; + platf::rumble_queue_t rumble_queue; util::ThreadPool::task_id_t mouse_left_button_timeout; @@ -314,13 +318,13 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet void repeat_key(short key_code) { // If key no longer pressed, stop repeating if(!key_press[key_code]) { - task_id = nullptr; + key_press_repeat_id = nullptr; return; } platf::keyboard(platf_input, key_code & 0x00FF, false); - task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id; + key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_period, key_code).task_id; } short map_keycode(short keycode) { @@ -345,12 +349,12 @@ void passthrough(std::shared_ptr &input, PNV_KEYBOARD_PACKET packet) { auto &pressed = key_press[packet->keyCode]; if(!pressed) { if(!release) { - if(task_id) { - task_pool.cancel(task_id); + if(key_press_repeat_id) { + task_pool.cancel(key_press_repeat_id); } if(config::input.key_repeat_delay.count() > 0) { - task_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, packet->keyCode).task_id; + key_press_repeat_id = task_pool.pushDelayed(repeat_key, config::input.key_repeat_delay, packet->keyCode).task_id; } } else { @@ -374,7 +378,7 @@ void passthrough(PNV_SCROLL_PACKET packet) { platf::scroll(platf_input, util::endian::big(packet->scrollAmt1)); } -int updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state) { +int updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state, platf::rumble_queue_t rumble_queue) { auto xorGamepadMask = old_state ^ new_state; if(!xorGamepadMask) { return 0; @@ -400,7 +404,7 @@ int updateGamepads(std::vector &gamepads, std::int16_t old_state, std return -1; } - if(platf::alloc_gamepad(platf_input, id)) { + if(platf::alloc_gamepad(platf_input, id, std::move(rumble_queue))) { free_id(gamepadMask, id); // allocating a gamepad failed: solution: ignore gamepads // The implementations of platf::alloc_gamepad already has logging @@ -416,7 +420,7 @@ int updateGamepads(std::vector &gamepads, std::int16_t old_state, std } void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { - if(updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask)) { + if(updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask, input->rumble_queue)) { return; } @@ -552,7 +556,7 @@ void passthrough(std::shared_ptr &input, std::vector &&in } void reset(std::shared_ptr &input) { - task_pool.cancel(task_id); + task_pool.cancel(key_press_repeat_id); task_pool.cancel(input->mouse_left_button_timeout); // Ensure input is synchronous, by using the task_pool @@ -576,7 +580,9 @@ void init() { } std::shared_ptr alloc(safe::mail_t mail) { - auto input = std::make_shared(mail->event(mail::touch_port)); + auto input = std::make_shared( + mail->event(mail::touch_port), + mail->queue(mail::rumble)); // Workaround to ensure new frames will be captured when a client connects task_pool.pushDelayed([]() { diff --git a/sunshine/input.h b/sunshine/input.h index bde14c6d..e45cbc75 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -5,11 +5,12 @@ #ifndef SUNSHINE_INPUT_H #define SUNSHINE_INPUT_H +#include + #include "platform/common.h" #include "thread_safe.h" namespace input { - struct input_t; void print(void *input); diff --git a/sunshine/main.h b/sunshine/main.h index e0023586..986a4be8 100644 --- a/sunshine/main.h +++ b/sunshine/main.h @@ -47,6 +47,7 @@ MAIL(audio_packets); // Local mail MAIL(touch_port); MAIL(idr); +MAIL(rumble); #undef MAIL } // namespace mail diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index 0699d886..e787a1ba 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -7,9 +7,11 @@ #include #include +#include #include #include +#include "sunshine/thread_safe.h" #include "sunshine/utility.h" struct sockaddr; @@ -34,6 +36,18 @@ constexpr std::uint16_t B = 0x2000; constexpr std::uint16_t X = 0x4000; constexpr std::uint16_t Y = 0x8000; +struct rumble_t { + KITTY_DEFAULT_CONSTR(rumble_t) + + rumble_t(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) + : id { id }, lowfreq { lowfreq }, highfreq { highfreq } {} + + std::uint16_t id; + std::uint16_t lowfreq; + std::uint16_t highfreq; +}; +using rumble_queue_t = safe::mail_raw_t::queue_t; + namespace speaker { enum speaker_e { FRONT_LEFT, @@ -243,7 +257,7 @@ void scroll(input_t &input, int distance); void keyboard(input_t &input, uint16_t modcode, bool release); void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state); -int alloc_gamepad(input_t &input, int nr); +int alloc_gamepad(input_t &input, int nr, rumble_queue_t &&rumble_queue); void free_gamepad(input_t &input, int nr); #define SERVICE_NAME "Sunshine" diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index 215a38a5..cd939707 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -18,11 +18,18 @@ constexpr touch_port_t target_touch_port { 65535, 65535 }; +using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; +using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>; + +void CALLBACK x360_notify( + client_t::pointer client, + target_t::pointer target, + std::uint8_t largeMotor, std::uint8_t smallMotor, + std::uint8_t /* led_number */, + void *userdata); + class vigem_t { public: - using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; - using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>; - int init() { VIGEM_ERROR status; @@ -40,8 +47,8 @@ public: return 0; } - int alloc_x360(int nr) { - auto &x360 = x360s[nr]; + int alloc_x360(int nr, rumble_queue_t &rumble_queue) { + auto &[rumble, x360] = x360s[nr]; assert(!x360); x360.reset(vigem_target_x360_alloc()); @@ -52,11 +59,18 @@ public: return -1; } + rumble = std::move(rumble_queue); + + status = vigem_target_x360_register_notification(client.get(), x360.get(), x360_notify, this); + if(!VIGEM_SUCCESS(status)) { + BOOST_LOG(warning) << "Couldn't register notifications for rumble support ["sv << util::hex(status).to_string_view() << ']'; + } + return 0; } void free_target(int nr) { - auto &x360 = x360s[nr]; + auto &[_, x360] = x360s[nr]; if(x360 && vigem_target_is_attached(x360.get())) { auto status = vigem_target_remove(client.get(), x360.get()); @@ -68,9 +82,21 @@ public: x360.reset(); } + void rumble(target_t::pointer target, std::uint8_t largeMotor, std::uint8_t smallMotor) { + for(int x = 0; x < x360s.size(); ++x) { + auto &[rumble_queue, x360] = x360s[x]; + + if(x360.get() == target) { + rumble_queue->raise(x, largeMotor, smallMotor); + + return; + } + } + } + ~vigem_t() { if(client) { - for(auto &x360 : x360s) { + for(auto &[_, x360] : x360s) { if(x360 && vigem_target_is_attached(x360.get())) { auto status = vigem_target_remove(client.get(), x360.get()); if(!VIGEM_SUCCESS(status)) { @@ -83,10 +109,25 @@ public: } } - std::vector x360s; + std::vector> x360s; + client_t client; }; +void CALLBACK x360_notify( + client_t::pointer client, + target_t::pointer target, + std::uint8_t largeMotor, std::uint8_t smallMotor, + std::uint8_t /* led_number */, + void *userdata) { + + BOOST_LOG(debug) + << "largeMotor: "sv << (int)largeMotor << std::endl + << "smallMotor: "sv << (int)smallMotor; + + task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, largeMotor, smallMotor); +} + input_t input() { input_t result { new vigem_t {} }; @@ -110,6 +151,7 @@ retry: BOOST_LOG(error) << "Couldn't send input"sv; } } + void abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) { INPUT i {}; @@ -242,12 +284,12 @@ void keyboard(input_t &input, uint16_t modcode, bool release) { send_input(i); } -int alloc_gamepad(input_t &input, int nr) { +int alloc_gamepad(input_t &input, int nr, rumble_queue_t &&rumble_queue) { if(!input) { return 0; } - return ((vigem_t *)input.get())->alloc_x360(nr); + return ((vigem_t *)input.get())->alloc_x360(nr, rumble_queue); } void free_gamepad(input_t &input, int nr) { @@ -257,6 +299,7 @@ void free_gamepad(input_t &input, int nr) { ((vigem_t *)input.get())->free_target(nr); } + void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { // If there is no gamepad support if(!input) { @@ -265,8 +308,8 @@ void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { auto vigem = (vigem_t *)input.get(); - auto &xusb = *(PXUSB_REPORT)&gamepad_state; - auto &x360 = vigem->x360s[nr]; + auto &xusb = *(PXUSB_REPORT)&gamepad_state; + auto &[_, x360] = vigem->x360s[nr]; auto status = vigem_target_x360_update(vigem->client.get(), x360.get(), xusb); if(!VIGEM_SUCCESS(status)) { diff --git a/sunshine/process.h b/sunshine/process.h index 39d7cff6..1339b76b 100644 --- a/sunshine/process.h +++ b/sunshine/process.h @@ -57,7 +57,7 @@ struct ctx_t { class proc_t { public: - KITTY_DEFAULT_CONSTR_THROW(proc_t) + KITTY_DEFAULT_CONSTR_MOVE_THROW(proc_t) proc_t( boost::process::environment &&env, diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index da431689..719a8fbc 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -101,6 +101,16 @@ struct control_terminate_t { std::uint32_t ec; }; +struct control_rumble_t { + control_header_v2 header; + + std::uint32_t useless; + + std::uint16_t id; + std::uint16_t lowfreq; + std::uint16_t highfreq; +}; + typedef struct control_encrypted_t { std::uint16_t encryptedHeaderType; // Always LE 0x0001 std::uint16_t length; // sizeof(seq) + 16 byte tag + secondary header and data @@ -281,6 +291,8 @@ struct session_t { net::peer_t peer; std::uint8_t seq; + + platf::rumble_queue_t rumble_queue; } control; safe::mail_raw_t::event_t shutdown_event; @@ -318,9 +330,13 @@ static inline std::string_view encode_control(session_t *session, const std::str return {}; } - packet->seq = util::endian::little(seq); + std::uint16_t packet_length = bytes + crypto::cipher::tag_size + sizeof(control_encrypted_t::seq); - return std::string_view { (char *)tagged_cipher.data(), (std::size_t)bytes }; + packet->encryptedHeaderType = util::endian::little(0x0001); + packet->length = util::endian::little(packet_length); + packet->seq = util::endian::little(seq); + + return std::string_view { (char *)tagged_cipher.data(), packet_length + sizeof(control_encrypted_t) - sizeof(control_encrypted_t::seq) }; } int start_broadcast(broadcast_ctx_t &ctx); @@ -537,6 +553,38 @@ std::vector replace(const std::string_view &original, const std::string return replaced; } +int send_rumble(session_t *session, std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) { + if(!session->control.peer) { + BOOST_LOG(warning) << "Couldn't send rumble data, still waiting for PING from Moonlight"sv; + // Still waiting for PING from Moonlight + return -1; + } + + control_rumble_t plaintext; + plaintext.header.type = packetTypes[IDX_RUMBLE_DATA]; + plaintext.header.payloadLength = sizeof(control_rumble_t) - sizeof(control_header_v2); + + plaintext.useless = 0xC0FFEE; + plaintext.id = util::endian::little(id); + plaintext.lowfreq = util::endian::little(lowfreq << 8); + plaintext.highfreq = util::endian::little(highfreq << 8); + + BOOST_LOG(fatal) << util::hex(plaintext.id).to_string_view() << " :: "sv << util::hex(plaintext.lowfreq).to_string_view() << " :: "sv << util::hex(plaintext.highfreq).to_string_view(); + std::array + encrypted_payload; + + auto payload = encode_control(session, util::view(plaintext), encrypted_payload); + if(session->broadcast_ref->control_server.send(payload, session->control.peer)) { + TUPLE_2D(port, addr, platf::from_sockaddr_ex((sockaddr *)&session->control.peer->address.address)); + BOOST_LOG(warning) << "Couldn't send termination code to ["sv << addr << ':' << port << ']'; + + return -1; + } + + return 0; +} + void controlBroadcastThread(control_server_t *server) { server->map(packetTypes[IDX_PERIODIC_PING], [](session_t *session, const std::string_view &payload) { BOOST_LOG(verbose) << "type [IDX_START_A]"sv; @@ -696,6 +744,13 @@ void controlBroadcastThread(control_server_t *server) { continue; } + auto &rumble_queue = session->control.rumble_queue; + while(rumble_queue->peek()) { + auto rumble = rumble_queue->pop(); + + send_rumble(session, rumble->id, rumble->lowfreq, rumble->highfreq); + } + ++pos; }) } @@ -706,7 +761,7 @@ void controlBroadcastThread(control_server_t *server) { break; } - server->iterate(500ms); + server->iterate(50ms); } // Let all remaining connections know the server is shutting down @@ -722,10 +777,6 @@ void controlBroadcastThread(control_server_t *server) { sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size> encrypted_payload; - auto packet = (control_encrypted_p)encrypted_payload.data(); - packet->encryptedHeaderType = util::endian::little(0x0001); - packet->length = encrypted_payload.size() - sizeof(control_encrypted_t) + 4; - auto lg = server->_map_addr_session.lock(); for(auto pos = std::begin(*server->_map_addr_session); pos != std::end(*server->_map_addr_session); ++pos) { auto session = pos->second.second; @@ -1353,8 +1404,9 @@ std::shared_ptr alloc(config_t &config, crypto::aes_t &gcm_key, crypt session->config = config; - session->control.iv = iv; - session->control.cipher = crypto::cipher::gcm_t { + session->control.rumble_queue = mail->queue(mail::rumble); + session->control.iv = iv; + session->control.cipher = crypto::cipher::gcm_t { gcm_key, false }; diff --git a/sunshine/utility.h b/sunshine/utility.h index b1e3a936..e827d441 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -56,16 +56,21 @@ struct argument_type { typedef U type; }; x &operator=(x &&) noexcept = default; \ x(); -#define KITTY_DEFAULT_CONSTR(x) \ +#define KITTY_DEFAULT_CONSTR_MOVE(x) \ x(x &&) noexcept = default; \ x &operator=(x &&) noexcept = default; \ x() = default; -#define KITTY_DEFAULT_CONSTR_THROW(x) \ - x(x &&) = default; \ - x &operator=(x &&) = default; \ +#define KITTY_DEFAULT_CONSTR_MOVE_THROW(x) \ + x(x &&) = default; \ + x &operator=(x &&) = default; \ x() = default; +#define KITTY_DEFAULT_CONSTR(x) \ + KITTY_DEFAULT_CONSTR_MOVE(x) \ + x(const x &) noexcept = default; \ + x &operator=(const x &) = default; + #define TUPLE_2D(a, b, expr) \ decltype(expr) a##_##b = expr; \ auto &a = std::get<0>(a##_##b); \ diff --git a/sunshine/video.cpp b/sunshine/video.cpp index 9d52a0b8..f221e02e 100644 --- a/sunshine/video.cpp +++ b/sunshine/video.cpp @@ -276,7 +276,7 @@ struct encoder_t { } struct option_t { - KITTY_DEFAULT_CONSTR(option_t) + KITTY_DEFAULT_CONSTR_MOVE(option_t) option_t(const option_t &) = default; std::string name; diff --git a/sunshine/video.h b/sunshine/video.h index 9f90c55a..fab4ff76 100644 --- a/sunshine/video.h +++ b/sunshine/video.h @@ -46,7 +46,7 @@ struct packet_raw_t : public AVPacket { std::string_view old; std::string_view _new; - KITTY_DEFAULT_CONSTR(replace_t) + KITTY_DEFAULT_CONSTR_MOVE(replace_t) replace_t(std::string_view old, std::string_view _new) noexcept : old { std::move(old) }, _new { std::move(_new) } {} }; diff --git a/third-party/ViGEmClient b/third-party/ViGEmClient index 52682b59..f719a1d9 160000 --- a/third-party/ViGEmClient +++ b/third-party/ViGEmClient @@ -1 +1 @@ -Subproject commit 52682b59c458388a74afbc3d7ef23de21983a86f +Subproject commit f719a1d9eb51969a685a9213d9db6dbb801404c1