Support ds4 controller on Windows

This commit is contained in:
loki 2021-07-18 15:32:26 +02:00
parent f65882e42a
commit 4b043e31fe
4 changed files with 200 additions and 26 deletions

View File

@ -211,9 +211,14 @@ nvhttp_t nvhttp {
};
input_t input {
2s, // back_button_timeout
500ms, // key_repeat_delay
std::chrono::duration<double> { 1 / 24.9 } // key_repeat_period
2s, // back_button_timeout
500ms, // key_repeat_delay
std::chrono::duration<double> { 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<std::string, std::string> &&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;

View File

@ -77,6 +77,8 @@ struct input_t {
std::chrono::milliseconds back_button_timeout;
std::chrono::milliseconds key_repeat_delay;
std::chrono::duration<double> key_repeat_period;
std::string gamepad;
};
namespace flag {

View File

@ -268,6 +268,8 @@ namespace publish {
}
[[nodiscard]] std::unique_ptr<deinit_t> init();
std::vector<std::string_view> &supported_gamepads();
} // namespace platf
#endif //SUNSHINE_COMMON_H

View File

@ -5,6 +5,7 @@
#include <ViGEm/Client.h>
#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<std::pair<rumble_queue_t, target_t>> x360s;
std::vector<std::pair<rumble_queue_t, target_t>> 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<std::uint16_t>::max() / 2 + 1) / 257;
}
static std::uint8_t to_ds4_triggerY(std::int16_t v) {
auto new_v = -((std::numeric_limits<std::uint16_t>::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<std::string_view> &supported_gamepads() {
// ds4 == ps4
static std::vector<std::string_view> gps {
"x360"sv, "ds4"sv, "ps4"sv
};
return gps;
}
} // namespace platf