Support Rumble on Windows

This commit is contained in:
loki 2021-07-18 11:05:34 +02:00
commit 1fda8f6219
11 changed files with 165 additions and 43 deletions

View file

@ -46,7 +46,7 @@ void free_id(std::bitset<N> &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<short, bool> key_press {};
static std::array<std::uint8_t, 5> mouse_press {};
@ -84,10 +84,13 @@ struct gamepad_t {
};
struct input_t {
input_t(safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event)
input_t(
safe::mail_raw_t::event_t<input::touch_port_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<gamepad_t> gamepads;
safe::mail_raw_t::event_t<input::touch_port_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_t> &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_t> &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<gamepad_t> &gamepads, std::int16_t old_state, std::int16_t new_state) {
int updateGamepads(std::vector<gamepad_t> &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<gamepad_t> &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<gamepad_t> &gamepads, std::int16_t old_state, std
}
void passthrough(std::shared_ptr<input_t> &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_t> &input, std::vector<std::uint8_t> &&in
}
void reset(std::shared_ptr<input_t> &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<input_t> alloc(safe::mail_t mail) {
auto input = std::make_shared<input_t>(mail->event<input::touch_port_t>(mail::touch_port));
auto input = std::make_shared<input_t>(
mail->event<input::touch_port_t>(mail::touch_port),
mail->queue<platf::rumble_t>(mail::rumble));
// Workaround to ensure new frames will be captured when a client connects
task_pool.pushDelayed([]() {

View file

@ -5,11 +5,12 @@
#ifndef SUNSHINE_INPUT_H
#define SUNSHINE_INPUT_H
#include <functional>
#include "platform/common.h"
#include "thread_safe.h"
namespace input {
struct input_t;
void print(void *input);

View file

@ -47,6 +47,7 @@ MAIL(audio_packets);
// Local mail
MAIL(touch_port);
MAIL(idr);
MAIL(rumble);
#undef MAIL
} // namespace mail

View file

@ -7,9 +7,11 @@
#include <bitset>
#include <filesystem>
#include <functional>
#include <mutex>
#include <string>
#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<rumble_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"

View file

@ -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<target_t> x360s;
std::vector<std::pair<rumble_queue_t, target_t>> 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)) {

View file

@ -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,

View file

@ -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<bool> 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<uint8_t> 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<std::uint8_t,
sizeof(control_encrypted_t) + crypto::cipher::round_to_pkcs7_padded(sizeof(plaintext)) + crypto::cipher::tag_size>
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<session_t> 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<platf::rumble_t>(mail::rumble);
session->control.iv = iv;
session->control.cipher = crypto::cipher::gcm_t {
gcm_key, false
};

View file

@ -56,16 +56,21 @@ struct argument_type<T(U)> { 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); \

View file

@ -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;

View file

@ -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) } {}
};

@ -1 +1 @@
Subproject commit 52682b59c458388a74afbc3d7ef23de21983a86f
Subproject commit f719a1d9eb51969a685a9213d9db6dbb801404c1