diff --git a/sunshine/config.cpp b/sunshine/config.cpp index f8cc735d..2fcca9ba 100644 --- a/sunshine/config.cpp +++ b/sunshine/config.cpp @@ -211,9 +211,14 @@ nvhttp_t nvhttp { }; input_t input { - 2s, // back_button_timeout - 500ms, // key_repeat_delay - std::chrono::duration { 1 / 24.9 } // key_repeat_period + 2s, // back_button_timeout + 500ms, // key_repeat_delay + std::chrono::duration { 1 / 24.9 }, // key_repeat_period + + { + platf::supported_gamepads().front().data(), + platf::supported_gamepads().front().size(), + }, // Default gamepad }; sunshine_t sunshine { @@ -646,6 +651,8 @@ void apply_config(std::unordered_map &&vars) { input.key_repeat_delay = std::chrono::milliseconds { to }; } + string_restricted_f(vars, "gamepad"s, input.gamepad, platf::supported_gamepads()); + int port = sunshine.port; int_f(vars, "port"s, port); sunshine.port = (std::uint16_t)port; diff --git a/sunshine/config.h b/sunshine/config.h index bae3178a..ba914819 100644 --- a/sunshine/config.h +++ b/sunshine/config.h @@ -77,6 +77,8 @@ struct input_t { std::chrono::milliseconds back_button_timeout; std::chrono::milliseconds key_repeat_delay; std::chrono::duration key_repeat_period; + + std::string gamepad; }; namespace flag { diff --git a/sunshine/platform/common.h b/sunshine/platform/common.h index e787a1ba..92d7136a 100644 --- a/sunshine/platform/common.h +++ b/sunshine/platform/common.h @@ -268,6 +268,8 @@ namespace publish { } [[nodiscard]] std::unique_ptr init(); + +std::vector &supported_gamepads(); } // namespace platf #endif //SUNSHINE_COMMON_H diff --git a/sunshine/platform/windows/input.cpp b/sunshine/platform/windows/input.cpp index cd939707..6522405d 100755 --- a/sunshine/platform/windows/input.cpp +++ b/sunshine/platform/windows/input.cpp @@ -5,6 +5,7 @@ #include #include "misc.h" +#include "sunshine/config.h" #include "sunshine/main.h" #include "sunshine/platform/common.h" @@ -21,6 +22,14 @@ constexpr touch_port_t target_touch_port { using client_t = util::safe_ptr<_VIGEM_CLIENT_T, vigem_free>; using target_t = util::safe_ptr<_VIGEM_TARGET_T, vigem_target_free>; +static VIGEM_TARGET_TYPE map(const std::string_view &gp) { + if(gp == "x360"sv) { + return Xbox360Wired; + } + + return DualShock4Wired; +} + void CALLBACK x360_notify( client_t::pointer client, target_t::pointer target, @@ -28,6 +37,13 @@ void CALLBACK x360_notify( std::uint8_t /* led_number */, void *userdata); +void CALLBACK ds4_notify( + client_t::pointer client, + target_t::pointer target, + std::uint8_t largeMotor, std::uint8_t smallMotor, + DS4_LIGHTBAR_COLOR /* led_color */, + void *userdata); + class vigem_t { public: int init() { @@ -42,17 +58,23 @@ public: return -1; } - x360s.resize(MAX_GAMEPADS); + gamepads.resize(MAX_GAMEPADS); return 0; } - int alloc_x360(int nr, rumble_queue_t &rumble_queue) { - auto &[rumble, x360] = x360s[nr]; - assert(!x360); + int alloc_gamepad_interal(int nr, rumble_queue_t &rumble_queue, VIGEM_TARGET_TYPE gp_type) { + auto &[rumble, gp] = gamepads[nr]; + assert(!gp); - x360.reset(vigem_target_x360_alloc()); - auto status = vigem_target_add(client.get(), x360.get()); + if(gp_type == Xbox360Wired) { + gp.reset(vigem_target_x360_alloc()); + } + else { + gp.reset(vigem_target_ds4_alloc()); + } + + auto status = vigem_target_add(client.get(), gp.get()); if(!VIGEM_SUCCESS(status)) { BOOST_LOG(error) << "Couldn't add Gamepad to ViGEm connection ["sv << util::hex(status).to_string_view() << ']'; @@ -61,7 +83,13 @@ public: rumble = std::move(rumble_queue); - status = vigem_target_x360_register_notification(client.get(), x360.get(), x360_notify, this); + if(gp_type == Xbox360Wired) { + status = vigem_target_x360_register_notification(client.get(), gp.get(), x360_notify, this); + } + else { + status = vigem_target_ds4_register_notification(client.get(), gp.get(), ds4_notify, this); + } + if(!VIGEM_SUCCESS(status)) { BOOST_LOG(warning) << "Couldn't register notifications for rumble support ["sv << util::hex(status).to_string_view() << ']'; } @@ -70,23 +98,23 @@ public: } void free_target(int nr) { - auto &[_, x360] = x360s[nr]; + auto &[_, gp] = gamepads[nr]; - if(x360 && vigem_target_is_attached(x360.get())) { - auto status = vigem_target_remove(client.get(), x360.get()); + if(gp && vigem_target_is_attached(gp.get())) { + auto status = vigem_target_remove(client.get(), gp.get()); if(!VIGEM_SUCCESS(status)) { BOOST_LOG(warning) << "Couldn't detach gamepad from ViGEm ["sv << util::hex(status).to_string_view() << ']'; } } - x360.reset(); + gp.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]; + for(int x = 0; x < gamepads.size(); ++x) { + auto &[rumble_queue, gp] = gamepads[x]; - if(x360.get() == target) { + if(gp.get() == target) { rumble_queue->raise(x, largeMotor, smallMotor); return; @@ -96,9 +124,9 @@ public: ~vigem_t() { if(client) { - for(auto &[_, x360] : x360s) { - if(x360 && vigem_target_is_attached(x360.get())) { - auto status = vigem_target_remove(client.get(), x360.get()); + for(auto &[_, gp] : gamepads) { + if(gp && vigem_target_is_attached(gp.get())) { + auto status = vigem_target_remove(client.get(), gp.get()); if(!VIGEM_SUCCESS(status)) { BOOST_LOG(warning) << "Couldn't detach gamepad from ViGEm ["sv << util::hex(status).to_string_view() << ']'; } @@ -109,7 +137,7 @@ public: } } - std::vector> x360s; + std::vector> gamepads; client_t client; }; @@ -125,7 +153,21 @@ void CALLBACK x360_notify( << "largeMotor: "sv << (int)largeMotor << std::endl << "smallMotor: "sv << (int)smallMotor; - task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, largeMotor, smallMotor); + task_pool.push(&vigem_t::rumble, (vigem_t *)userdata, target, smallMotor, largeMotor); +} + +void CALLBACK ds4_notify( + client_t::pointer client, + target_t::pointer target, + std::uint8_t largeMotor, std::uint8_t smallMotor, + DS4_LIGHTBAR_COLOR /* led_color */, + 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, smallMotor, largeMotor); } input_t input() { @@ -289,7 +331,7 @@ int alloc_gamepad(input_t &input, int nr, rumble_queue_t &&rumble_queue) { return 0; } - return ((vigem_t *)input.get())->alloc_x360(nr, rumble_queue); + return ((vigem_t *)input.get())->alloc_gamepad_interal(nr, rumble_queue, map(config::input.gamepad)); } void free_gamepad(input_t &input, int nr) { @@ -300,6 +342,111 @@ void free_gamepad(input_t &input, int nr) { ((vigem_t *)input.get())->free_target(nr); } +static VIGEM_ERROR x360_update(client_t::pointer client, target_t::pointer gp, const gamepad_state_t &gamepad_state) { + auto &xusb = *(PXUSB_REPORT)&gamepad_state; + + return vigem_target_x360_update(client, gp, xusb); +} + +static DS4_DPAD_DIRECTIONS ds4_dpad(const gamepad_state_t &gamepad_state) { + auto flags = gamepad_state.buttonFlags; + if(flags & DPAD_UP) { + if(flags & DPAD_RIGHT) { + return DS4_BUTTON_DPAD_NORTHEAST; + } + else if(flags & DPAD_LEFT) { + return DS4_BUTTON_DPAD_NORTHWEST; + } + else { + return DS4_BUTTON_DPAD_NORTH; + } + } + + else if(flags & DPAD_DOWN) { + if(flags & DPAD_RIGHT) { + return DS4_BUTTON_DPAD_SOUTHEAST; + } + else if(flags & DPAD_LEFT) { + return DS4_BUTTON_DPAD_SOUTHWEST; + } + else { + return DS4_BUTTON_DPAD_SOUTH; + } + } + + else if(flags & DPAD_RIGHT) { + return DS4_BUTTON_DPAD_EAST; + } + + else if(flags & DPAD_LEFT) { + return DS4_BUTTON_DPAD_WEST; + } + + return DS4_BUTTON_DPAD_NONE; +} + +static DS4_BUTTONS ds4_buttons(const gamepad_state_t &gamepad_state) { + int buttons {}; + + auto flags = gamepad_state.buttonFlags; + // clang-format off + if(flags & LEFT_STICK) buttons |= DS4_BUTTON_THUMB_LEFT; + if(flags & RIGHT_STICK) buttons |= DS4_BUTTON_THUMB_RIGHT; + if(flags & LEFT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_LEFT; + if(flags & RIGHT_BUTTON) buttons |= DS4_BUTTON_SHOULDER_RIGHT; + if(flags & START) buttons |= DS4_BUTTON_OPTIONS; + if(flags & A) buttons |= DS4_BUTTON_CROSS; + if(flags & B) buttons |= DS4_BUTTON_CIRCLE; + if(flags & X) buttons |= DS4_BUTTON_SQUARE; + if(flags & Y) buttons |= DS4_BUTTON_TRIANGLE; + + if(gamepad_state.lt > 0) buttons |= DS4_BUTTON_TRIGGER_LEFT; + if(gamepad_state.rt > 0) buttons |= DS4_BUTTON_TRIGGER_RIGHT; + // clang-format on + + return (DS4_BUTTONS)buttons; +} + +static DS4_SPECIAL_BUTTONS ds4_special_buttons(const gamepad_state_t &gamepad_state) { + int buttons {}; + + if(gamepad_state.buttonFlags & BACK) buttons |= DS4_SPECIAL_BUTTON_TOUCHPAD; + if(gamepad_state.buttonFlags & HOME) buttons |= DS4_SPECIAL_BUTTON_PS; + + return (DS4_SPECIAL_BUTTONS)buttons; +} + +static std::uint8_t to_ds4_triggerX(std::int16_t v) { + return (v + std::numeric_limits::max() / 2 + 1) / 257; +} + +static std::uint8_t to_ds4_triggerY(std::int16_t v) { + auto new_v = -((std::numeric_limits::max() / 2 + v - 1)) / 257; + + return new_v == 0 ? 0xFF : (std::uint8_t)new_v; +} + +static VIGEM_ERROR ds4_update(client_t::pointer client, target_t::pointer gp, const gamepad_state_t &gamepad_state) { + DS4_REPORT report; + + DS4_REPORT_INIT(&report); + DS4_SET_DPAD(&report, ds4_dpad(gamepad_state)); + report.wButtons |= ds4_buttons(gamepad_state); + report.bSpecial = ds4_special_buttons(gamepad_state); + + report.bTriggerL = gamepad_state.lt; + report.bTriggerR = gamepad_state.rt; + + report.bThumbLX = to_ds4_triggerX(gamepad_state.lsX); + report.bThumbLY = to_ds4_triggerY(gamepad_state.lsY); + + report.bThumbRX = to_ds4_triggerX(gamepad_state.rsX); + report.bThumbRY = to_ds4_triggerY(gamepad_state.rsY); + + return vigem_target_ds4_update(client, gp, report); +} + + void gamepad(input_t &input, int nr, const gamepad_state_t &gamepad_state) { // If there is no gamepad support if(!input) { @@ -308,10 +455,17 @@ 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 &[_, gp] = vigem->gamepads[nr]; + + VIGEM_ERROR status; + + if(vigem_target_get_type(gp.get()) == Xbox360Wired) { + status = x360_update(vigem->client.get(), gp.get(), gamepad_state); + } + else { + status = ds4_update(vigem->client.get(), gp.get(), gamepad_state); + } - auto status = vigem_target_x360_update(vigem->client.get(), x360.get(), xusb); if(!VIGEM_SUCCESS(status)) { BOOST_LOG(fatal) << "Couldn't send gamepad input to ViGEm ["sv << util::hex(status).to_string_view() << ']'; @@ -325,4 +479,13 @@ void freeInput(void *p) { delete vigem; } + +std::vector &supported_gamepads() { + // ds4 == ps4 + static std::vector gps { + "x360"sv, "ds4"sv, "ps4"sv + }; + + return gps; +} } // namespace platf