From 92f51622cc021172da3e86dad2adc6aaf9a1bbcf Mon Sep 17 00:00:00 2001 From: loki Date: Sun, 15 Mar 2020 21:22:42 +0100 Subject: [PATCH] Map session to gamepads --- sunshine/input.cpp | 175 +++++++++++++++++++++++++++---------- sunshine/input.h | 38 ++------ sunshine/main.cpp | 2 + sunshine/platform/common.h | 2 +- sunshine/rtsp.cpp | 5 -- sunshine/stream.cpp | 6 +- sunshine/stream.h | 5 -- 7 files changed, 139 insertions(+), 94 deletions(-) diff --git a/sunshine/input.cpp b/sunshine/input.cpp index 4cf79030..05d47a36 100644 --- a/sunshine/input.cpp +++ b/sunshine/input.cpp @@ -6,12 +6,68 @@ extern "C" { #include } +#include + #include "main.h" #include "config.h" -#include "input.h" #include "utility.h" +#include "platform/common.h" +#include "thread_pool.h" namespace input { + +constexpr auto MAX_GAMEPADS = std::min((std::size_t)platf::MAX_GAMEPADS, sizeof(std::int16_t)*8); +enum class button_state_e { + NONE, + DOWN, + UP +}; + +template +int alloc_id(std::bitset &gamepad_mask) { + for(int x = 0; x < gamepad_mask.size(); ++x) { + if(!gamepad_mask[x]) { + gamepad_mask[x] = true; + return x; + } + } + + return -1; +} + +template +void free_id(std::bitset &gamepad_mask, int id) { + gamepad_mask[id] = false; +} + +struct gamepad_t { + gamepad_t() : gamepad_state {}, back_timeout_id {}, id { -1 }, back_button_state { button_state_e::NONE } {} + platf::gamepad_state_t gamepad_state; + + util::ThreadPool::task_id_t back_timeout_id; + + int id; + + // When emulating the HOME button, we may need to artificially release the back button. + // Afterwards, the gamepad state on sunshine won't match the state on Moonlight. + // To prevent Sunshine from sending erronious input data to the active application, + // Sunshine forces the button to be in a specific state until the gamepad state matches that of + // Moonlight once more. + button_state_e back_button_state; +}; + +struct input_t { + input_t() : active_gamepad_state {}, gamepads (MAX_GAMEPADS) { } + + std::uint16_t active_gamepad_state; + std::vector gamepads; +}; + +static std::unordered_map key_press {}; +static std::array mouse_press {}; + +static platf::input_t platf_input; + using namespace std::literals; void print(PNV_MOUSE_MOVE_PACKET packet) { @@ -102,18 +158,18 @@ void passthrough(std::shared_ptr &input, PNV_MOUSE_BUTTON_PACKET packet display_cursor = true; auto button = util::endian::big(packet->button); - if(button > 0 && button < input->mouse_press.size()) { - input->mouse_press[button] = packet->action != BUTTON_RELEASED; + if(button > 0 && button < mouse_press.size()) { + mouse_press[button] = packet->action != BUTTON_RELEASED; } - platf::button_mouse(input->input, button, packet->action == BUTTON_RELEASED); + platf::button_mouse(platf_input, button, packet->action == BUTTON_RELEASED); } void passthrough(std::shared_ptr &input, PNV_KEYBOARD_PACKET packet) { auto constexpr BUTTON_RELEASED = 0x04; - input->key_press[packet->keyCode] = packet->keyAction != BUTTON_RELEASED; - platf::keyboard(input->input, packet->keyCode & 0x00FF, packet->keyAction == BUTTON_RELEASED); + key_press[packet->keyCode] = packet->keyAction != BUTTON_RELEASED; + platf::keyboard(platf_input, packet->keyCode & 0x00FF, packet->keyAction == BUTTON_RELEASED); } void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) { @@ -122,22 +178,58 @@ void passthrough(platf::input_t &input, PNV_SCROLL_PACKET packet) { platf::scroll(input, util::endian::big(packet->scrollAmt1)); } -void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { - auto xorGamepadMask = input->active_gamepad_state ^ packet->activeGamepadMask; +int updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state) { + static std::bitset gamepadMask {}; - for(int x = 0; x < platf::MAX_GAMEPADS; ++x) { + auto xorGamepadMask = old_state ^ new_state; + if (!xorGamepadMask) { + return 0; + } + + for(int x = 0; x < sizeof(std::int16_t) * 8; ++x) { if((xorGamepadMask >> x) & 1) { - if((input->active_gamepad_state >> x) & 1) { - platf::gamepad(input->input, x, platf::gamepad_state_t {}); - platf::free_gamepad(input->input, x); + auto &gamepad = gamepads[x]; + + if((old_state >> x) & 1) { + if (gamepad.id < 0) { + return -1; + } + + platf::gamepad(platf_input, gamepad.id, platf::gamepad_state_t{}); + platf::free_gamepad(platf_input, gamepad.id); + + free_id(gamepadMask, gamepad.id); + + gamepad.id = -1; } - else if(platf::alloc_gamepad(input->input, x)) { - // allocating a gamepad failed: solution: ignore gamepads - // The implementations of platf::alloc_gamepad already have logging - return; + else { + auto id = alloc_id(gamepadMask); + + if(id < 0) { + // Out of gamepads + return -1; + } + + if(platf::alloc_gamepad(platf_input, id)) { + free_id(gamepadMask, id); + // allocating a gamepad failed: solution: ignore gamepads + // The implementations of platf::alloc_gamepad already has logging + return -1; + } + + gamepad.id = id; } } } + + return 0; +} + +void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { + if(updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask)) { + return; + } + input->active_gamepad_state = packet->activeGamepadMask; if(packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) { @@ -147,11 +239,19 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa } if(!((input->active_gamepad_state >> packet->controllerNumber) & 1)) { + BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv; + return; } auto &gamepad = input->gamepads[packet->controllerNumber]; + // If this gamepad has not been initialized, ignore it. + // This could happen when platf::alloc_gamepad fails + if(gamepad.id < 0) { + return; + } + display_cursor = false; std::uint16_t bf = packet->buttonFlags; @@ -188,7 +288,6 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa if (platf::BACK & bf) { if (platf::BACK & bf_new) { - // Don't emulate home button if timeout < 0 if(config::input.back_button_timeout >= 0ms) { gamepad.back_timeout_id = task_pool.pushDelayed([input, controller=packet->controllerNumber]() { @@ -199,15 +298,15 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa // Force the back button up gamepad.back_button_state = button_state_e::UP; state.buttonFlags &= ~platf::BACK; - platf::gamepad(input->input, controller, state); + platf::gamepad(platf_input, gamepad.id, state); // Press Home button state.buttonFlags |= platf::HOME; - platf::gamepad(input->input, controller, state); + platf::gamepad(platf_input, gamepad.id, state); // Release Home button state.buttonFlags &= ~platf::HOME; - platf::gamepad(input->input, controller, state); + platf::gamepad(platf_input, gamepad.id, state); gamepad.back_timeout_id = nullptr; }, config::input.back_button_timeout).task_id; @@ -219,7 +318,7 @@ void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET pa } } - platf::gamepad(input->input, packet->controllerNumber, gamepad_state); + platf::gamepad(platf_input, gamepad.id, gamepad_state); gamepad.gamepad_state = gamepad_state; } @@ -231,7 +330,7 @@ void passthrough_helper(std::shared_ptr input, std::vectorinput, (PNV_MOUSE_MOVE_PACKET)payload); + passthrough(platf_input, (PNV_MOUSE_MOVE_PACKET)payload); break; case PACKET_TYPE_MOUSE_BUTTON: passthrough(input, (PNV_MOUSE_BUTTON_PACKET)payload); @@ -240,7 +339,7 @@ void passthrough_helper(std::shared_ptr input, std::vectorinput, (PNV_SCROLL_PACKET)payload); + passthrough(platf_input, (PNV_SCROLL_PACKET)payload); } else { passthrough(input, (PNV_KEYBOARD_PACKET)payload); @@ -254,35 +353,15 @@ void passthrough_helper(std::shared_ptr input, std::vector input) { - for(auto &[key_press, key_down] : input->key_press) { - if(key_down) { - key_down = false; - platf::keyboard(input->input, key_press & 0x00FF, true); - } - } - - auto &mouse_press = input->mouse_press; - for(int x = 0; x < mouse_press.size(); ++x) { - if(mouse_press[x]) { - mouse_press[x] = false; - - platf::button_mouse(input->input, x + 1, true); - } - } - - NV_MULTI_CONTROLLER_PACKET fake_packet {}; - passthrough(input, &fake_packet); -} - void passthrough(std::shared_ptr &input, std::vector &&input_data) { task_pool.push(passthrough_helper, input, util::cmove(input_data)); } -void reset(std::shared_ptr &input) { - task_pool.push(reset_helper, input); +void init() { + platf_input = platf::input(); } -input_t::input_t() : mouse_press {}, input { platf::input() }, active_gamepad_state {}, gamepads (platf::MAX_GAMEPADS) {} -gamepad_t::gamepad_t() : gamepad_state {}, back_timeout_id {}, back_button_state { button_state_e::NONE } {} +std::shared_ptr alloc() { + return std::make_shared(); +} } diff --git a/sunshine/input.h b/sunshine/input.h index f07f925e..ce5b9161 100644 --- a/sunshine/input.h +++ b/sunshine/input.h @@ -5,45 +5,17 @@ #ifndef SUNSHINE_INPUT_H #define SUNSHINE_INPUT_H -#include "platform/common.h" -#include "thread_pool.h" namespace input { -enum class button_state_e { - NONE, - DOWN, - UP -}; -struct gamepad_t { - gamepad_t(); - platf::gamepad_state_t gamepad_state; - - util::ThreadPool::task_id_t back_timeout_id; - - - // When emulating the HOME button, we may need to artificially release the back button. - // Afterwards, the gamepad state on sunshine won't match the state on Moonlight. - // To prevent Sunshine from sending erronious input data to the active application, - // Sunshine forces the button to be in a specific state until the gamepad state matches that of - // Moonlight once more. - button_state_e back_button_state; -}; -struct input_t { - input_t(); - - std::unordered_map key_press; - std::array mouse_press; - - platf::input_t input; - - std::uint16_t active_gamepad_state; - std::vector gamepads; -}; +struct input_t; void print(void *input); void passthrough(std::shared_ptr &input, std::vector &&input_data); -void reset(std::shared_ptr &input); + +void init(); + +std::shared_ptr alloc(); } #endif //SUNSHINE_INPUT_H diff --git a/sunshine/main.cpp b/sunshine/main.cpp index 78a79748..1d58b88c 100644 --- a/sunshine/main.cpp +++ b/sunshine/main.cpp @@ -14,6 +14,7 @@ #include #include +#include "input.h" #include "nvhttp.h" #include "rtsp.h" #include "config.h" @@ -133,6 +134,7 @@ int main(int argc, char *argv[]) { proc::proc = std::move(*proc_opt); auto deinit_guard = platf::init(); + input::init(); reed_solomon_init(); task_pool.start(1); diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index b60741b7..f3399e98 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -10,7 +10,7 @@ struct sockaddr; namespace platf { -constexpr auto MAX_GAMEPADS = 2; +constexpr auto MAX_GAMEPADS = 32; constexpr std::uint16_t DPAD_UP = 0x0001; constexpr std::uint16_t DPAD_DOWN = 0x0002; diff --git a/sunshine/rtsp.cpp b/sunshine/rtsp.cpp index 549e342b..b98a5432 100644 --- a/sunshine/rtsp.cpp +++ b/sunshine/rtsp.cpp @@ -401,11 +401,6 @@ void cmd_play(rtsp_server_t *server, net::peer_t peer, msg_t &&req) { } void rtpThread(std::shared_ptr shutdown_event) { - input = std::make_shared(); - auto fg = util::fail_guard([&]() { - input.reset(); - }); - rtsp_server_t server(RTSP_SETUP_PORT); server.map("OPTIONS"sv, &cmd_option); diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 5b60abc0..6573c22e 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -167,6 +167,7 @@ struct broadcast_ctx_t { struct session_t { config_t config; + std::shared_ptr input; std::thread audioThread; std::thread videoThread; @@ -202,7 +203,6 @@ struct session_t { int start_broadcast(broadcast_ctx_t &ctx); void end_broadcast(broadcast_ctx_t &ctx); -std::shared_ptr input; static auto broadcast = safe::make_shared(start_broadcast, end_broadcast); safe::signal_t broadcast_shutdown_event; @@ -453,7 +453,7 @@ void controlBroadcastThread(safe::signal_t *shutdown_event, control_server_t *se } input::print(plaintext.data()); - input::passthrough(input, std::move(plaintext)); + input::passthrough(session->input, std::move(plaintext)); }); while(!shutdown_event->peek()) { @@ -855,6 +855,8 @@ void join(session_t &session) { } void start(session_t &session, const std::string &addr_string) { + session.input = input::alloc(); + session.broadcast_ref = broadcast.ref(); session.broadcast_ref->control_server.emplace_addr_to_session(addr_string, session); diff --git a/sunshine/stream.h b/sunshine/stream.h index a92eb33b..8e2e183a 100644 --- a/sunshine/stream.h +++ b/sunshine/stream.h @@ -11,10 +11,6 @@ #include "audio.h" #include "crypto.h" -namespace input { -struct input_t; -} - namespace stream { struct session_t; struct config_t { @@ -41,7 +37,6 @@ void join(session_t &session); state_e state(session_t &session); } -extern std::shared_ptr input; extern safe::signal_t broadcast_shutdown_event; }