Fix handling of gamepad feedback with multiple clients connected

We need to use the client-relative index rather than the global index
when sending feedback to the client.
This commit is contained in:
Cameron Gutman
2023-07-10 19:43:09 -05:00
parent 65b2e19b07
commit 50f353d183
5 changed files with 64 additions and 46 deletions

View File

@@ -722,7 +722,6 @@ namespace input {
} }
platf::gamepad_arrival_t arrival { platf::gamepad_arrival_t arrival {
packet->controllerNumber,
packet->type, packet->type,
util::endian::little(packet->capabilities), util::endian::little(packet->capabilities),
util::endian::little(packet->supportedButtonFlags), util::endian::little(packet->supportedButtonFlags),
@@ -734,7 +733,7 @@ namespace input {
} }
// Allocate a new gamepad // Allocate a new gamepad
if (platf::alloc_gamepad(platf_input, id, arrival, input->feedback_queue)) { if (platf::alloc_gamepad(platf_input, { id, packet->controllerNumber }, arrival, input->feedback_queue)) {
free_id(gamepadMask, id); free_id(gamepadMask, id);
return; return;
} }
@@ -765,7 +764,7 @@ namespace input {
} }
platf::gamepad_touch_t touch { platf::gamepad_touch_t touch {
packet->controllerNumber, { gamepad.id, packet->controllerNumber },
packet->eventType, packet->eventType,
util::endian::little(packet->pointerId), util::endian::little(packet->pointerId),
from_netfloat(packet->x), from_netfloat(packet->x),
@@ -799,7 +798,7 @@ namespace input {
} }
platf::gamepad_motion_t motion { platf::gamepad_motion_t motion {
packet->controllerNumber, { gamepad.id, packet->controllerNumber },
packet->motionType, packet->motionType,
from_netfloat(packet->x), from_netfloat(packet->x),
from_netfloat(packet->y), from_netfloat(packet->y),
@@ -832,7 +831,7 @@ namespace input {
} }
platf::gamepad_battery_t battery { platf::gamepad_battery_t battery {
packet->controllerNumber, { gamepad.id, packet->controllerNumber },
packet->batteryState, packet->batteryState,
packet->batteryPercentage packet->batteryPercentage
}; };
@@ -862,7 +861,7 @@ namespace input {
return; return;
} }
if (platf::alloc_gamepad(platf_input, id, {}, input->feedback_queue)) { if (platf::alloc_gamepad(platf_input, { id, (uint8_t) packet->controllerNumber }, {}, input->feedback_queue)) {
free_id(gamepadMask, id); free_id(gamepadMask, id);
return; return;
} }

View File

@@ -236,15 +236,25 @@ namespace platf {
std::int16_t rsY; std::int16_t rsY;
}; };
struct gamepad_id_t {
// The global index is used when looking up gamepads in the platform's
// gamepad array. It identifies gamepads uniquely among all clients.
int globalIndex;
// The client-relative index is the controller number as reported by the
// client. It must be used when communicating back to the client via
// the input feedback queue.
std::uint8_t clientRelativeIndex;
};
struct gamepad_arrival_t { struct gamepad_arrival_t {
std::uint8_t gamepadNumber;
std::uint8_t type; std::uint8_t type;
std::uint16_t capabilities; std::uint16_t capabilities;
std::uint32_t supportedButtons; std::uint32_t supportedButtons;
}; };
struct gamepad_touch_t { struct gamepad_touch_t {
std::uint8_t gamepadNumber; gamepad_id_t id;
std::uint8_t eventType; std::uint8_t eventType;
std::uint32_t pointerId; std::uint32_t pointerId;
float x; float x;
@@ -253,7 +263,7 @@ namespace platf {
}; };
struct gamepad_motion_t { struct gamepad_motion_t {
std::uint8_t gamepadNumber; gamepad_id_t id;
std::uint8_t motionType; std::uint8_t motionType;
// Accel: m/s^2 // Accel: m/s^2
@@ -264,7 +274,7 @@ namespace platf {
}; };
struct gamepad_battery_t { struct gamepad_battery_t {
std::uint8_t gamepadNumber; gamepad_id_t id;
std::uint8_t state; std::uint8_t state;
std::uint8_t percentage; std::uint8_t percentage;
}; };
@@ -581,13 +591,13 @@ namespace platf {
/** /**
* @brief Creates a new virtual gamepad. * @brief Creates a new virtual gamepad.
* @param input The input context. * @param input The input context.
* @param nr The assigned controller number. * @param id The gamepad ID.
* @param metadata Controller metadata from client (empty if none provided). * @param metadata Controller metadata from client (empty if none provided).
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success. * @return 0 on success.
*/ */
int int
alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue); alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue);
void void
free_gamepad(input_t &input, int nr); free_gamepad(input_t &input, int nr);

View File

@@ -452,7 +452,7 @@ namespace platf {
public: public:
KITTY_DEFAULT_CONSTR_MOVE(effect_t) KITTY_DEFAULT_CONSTR_MOVE(effect_t)
effect_t(int gamepadnr, uinput_t::pointer dev, feedback_queue_t &&q): effect_t(std::uint8_t gamepadnr, uinput_t::pointer dev, feedback_queue_t &&q):
gamepadnr { gamepadnr }, dev { dev }, rumble_queue { std::move(q) }, gain { 0xFFFF }, id_to_data {} {} gamepadnr { gamepadnr }, dev { dev }, rumble_queue { std::move(q) }, gain { 0xFFFF }, id_to_data {} {}
class data_t { class data_t {
@@ -628,8 +628,8 @@ namespace platf {
BOOST_LOG(debug) << "Removed rumble effect id ["sv << id << ']'; BOOST_LOG(debug) << "Removed rumble effect id ["sv << id << ']';
} }
// Used as ID for rumble notifications // Client-relative gamepad index for rumble notifications
int gamepadnr; std::uint8_t gamepadnr;
// Used as ID for adding/removinf devices from evdev notifications // Used as ID for adding/removinf devices from evdev notifications
uinput_t::pointer dev; uinput_t::pointer dev;
@@ -772,14 +772,14 @@ namespace platf {
/** /**
* @brief Creates a new virtual gamepad. * @brief Creates a new virtual gamepad.
* @param nr The assigned controller number. * @param id The gamepad ID.
* @param metadata Controller metadata from client (empty if none provided). * @param metadata Controller metadata from client (empty if none provided).
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success. * @return 0 on success.
*/ */
int int
alloc_gamepad(int nr, const gamepad_arrival_t &metadata, feedback_queue_t &&feedback_queue) { alloc_gamepad(const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t &&feedback_queue) {
TUPLE_2D_REF(input, gamepad_state, gamepads[nr]); TUPLE_2D_REF(input, gamepad_state, gamepads[id.globalIndex]);
int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input); int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input);
@@ -791,7 +791,7 @@ namespace platf {
} }
std::stringstream ss; std::stringstream ss;
ss << "sunshine_gamepad_"sv << nr; ss << "sunshine_gamepad_"sv << id.globalIndex;
auto gamepad_path = platf::appdata() / ss.str(); auto gamepad_path = platf::appdata() / ss.str();
if (std::filesystem::is_symlink(gamepad_path)) { if (std::filesystem::is_symlink(gamepad_path)) {
@@ -801,7 +801,7 @@ namespace platf {
auto dev_node = libevdev_uinput_get_devnode(input.get()); auto dev_node = libevdev_uinput_get_devnode(input.get());
rumble_ctx->rumble_queue_queue.raise( rumble_ctx->rumble_queue_queue.raise(
nr, id.clientRelativeIndex,
input.get(), input.get(),
std::move(feedback_queue), std::move(feedback_queue),
pollfd_t { pollfd_t {
@@ -1490,14 +1490,14 @@ namespace platf {
/** /**
* @brief Creates a new virtual gamepad. * @brief Creates a new virtual gamepad.
* @param input The input context. * @param input The input context.
* @param nr The assigned controller number. * @param id The gamepad ID.
* @param metadata Controller metadata from client (empty if none provided). * @param metadata Controller metadata from client (empty if none provided).
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success. * @return 0 on success.
*/ */
int int
alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
return ((input_raw_t *) input.get())->alloc_gamepad(nr, metadata, std::move(feedback_queue)); return ((input_raw_t *) input.get())->alloc_gamepad(id, metadata, std::move(feedback_queue));
} }
void void

View File

@@ -291,13 +291,13 @@ const KeyCodeMap kKeyCodesMap[] = {
/** /**
* @brief Creates a new virtual gamepad. * @brief Creates a new virtual gamepad.
* @param input The input context. * @param input The input context.
* @param nr The assigned controller number. * @param id The gamepad ID.
* @param metadata Controller metadata from client (empty if none provided). * @param metadata Controller metadata from client (empty if none provided).
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success. * @return 0 on success.
*/ */
int int
alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv; BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv;
return -1; return -1;
} }

View File

@@ -61,6 +61,8 @@ namespace platf {
std::map<uint32_t, uint8_t> pointer_id_map; std::map<uint32_t, uint8_t> pointer_id_map;
uint8_t available_pointers; uint8_t available_pointers;
uint8_t client_relative_index;
gamepad_feedback_msg_t last_rumble; gamepad_feedback_msg_t last_rumble;
gamepad_feedback_msg_t last_rgb_led; gamepad_feedback_msg_t last_rgb_led;
}; };
@@ -193,16 +195,18 @@ namespace platf {
/** /**
* @brief Attaches a new gamepad. * @brief Attaches a new gamepad.
* @param nr The gamepad index. * @param id The gamepad ID.
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @param gp_type The type of gamepad. * @param gp_type The type of gamepad.
* @return 0 on success. * @return 0 on success.
*/ */
int int
alloc_gamepad_internal(int nr, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) { alloc_gamepad_internal(const gamepad_id_t &id, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) {
auto &gamepad = gamepads[nr]; auto &gamepad = gamepads[id.globalIndex];
assert(!gamepad.gp); assert(!gamepad.gp);
gamepad.client_relative_index = id.clientRelativeIndex;
if (gp_type == Xbox360Wired) { if (gp_type == Xbox360Wired) {
gamepad.gp.reset(vigem_target_x360_alloc()); gamepad.gp.reset(vigem_target_x360_alloc());
XUSB_REPORT_INIT(&gamepad.report.x360); XUSB_REPORT_INIT(&gamepad.report.x360);
@@ -218,8 +222,8 @@ namespace platf {
ds4_update_motion(gamepad, LI_MOTION_TYPE_GYRO, 0.0f, 0.0f, 0.0f); ds4_update_motion(gamepad, LI_MOTION_TYPE_GYRO, 0.0f, 0.0f, 0.0f);
// Request motion events from the client at 100 Hz // Request motion events from the client at 100 Hz
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(nr, LI_MOTION_TYPE_ACCEL, 100)); feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(gamepad.client_relative_index, LI_MOTION_TYPE_ACCEL, 100));
feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(nr, LI_MOTION_TYPE_GYRO, 100)); feedback_queue->raise(gamepad_feedback_msg_t::make_motion_event_state(gamepad.client_relative_index, LI_MOTION_TYPE_GYRO, 100));
// We support pointer index 0 and 1 // We support pointer index 0 and 1
gamepad.available_pointers = 0x3; gamepad.available_pointers = 0x3;
@@ -285,7 +289,9 @@ namespace platf {
// Don't resend duplicate rumble data // Don't resend duplicate rumble data
if (normalizedSmallMotor != gamepad.last_rumble.data.rumble.highfreq || if (normalizedSmallMotor != gamepad.last_rumble.data.rumble.highfreq ||
normalizedLargeMotor != gamepad.last_rumble.data.rumble.lowfreq) { normalizedLargeMotor != gamepad.last_rumble.data.rumble.lowfreq) {
gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rumble(x, normalizedLargeMotor, normalizedSmallMotor); // We have to use the client-relative index when communicating back to the client
gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rumble(
gamepad.client_relative_index, normalizedLargeMotor, normalizedSmallMotor);
gamepad.feedback_queue->raise(msg); gamepad.feedback_queue->raise(msg);
gamepad.last_rumble = msg; gamepad.last_rumble = msg;
} }
@@ -308,8 +314,11 @@ namespace platf {
if (gamepad.gp.get() == target) { if (gamepad.gp.get() == target) {
// Don't resend duplicate RGB data // Don't resend duplicate RGB data
if (r != gamepad.last_rgb_led.data.rgb_led.r || g != gamepad.last_rgb_led.data.rgb_led.g || b != gamepad.last_rgb_led.data.rgb_led.b) { if (r != gamepad.last_rgb_led.data.rgb_led.r ||
gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rgb_led(x, r, g, b); g != gamepad.last_rgb_led.data.rgb_led.g ||
b != gamepad.last_rgb_led.data.rgb_led.b) {
// We have to use the client-relative index when communicating back to the client
gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rgb_led(gamepad.client_relative_index, r, g, b);
gamepad.feedback_queue->raise(msg); gamepad.feedback_queue->raise(msg);
gamepad.last_rgb_led = msg; gamepad.last_rgb_led = msg;
} }
@@ -603,13 +612,13 @@ namespace platf {
/** /**
* @brief Creates a new virtual gamepad. * @brief Creates a new virtual gamepad.
* @param input The input context. * @param input The input context.
* @param nr The assigned controller number. * @param id The gamepad ID.
* @param metadata Controller metadata from client (empty if none provided). * @param metadata Controller metadata from client (empty if none provided).
* @param feedback_queue The queue for posting messages back to the client. * @param feedback_queue The queue for posting messages back to the client.
* @return 0 on success. * @return 0 on success.
*/ */
int int
alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { alloc_gamepad(input_t &input, const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) {
auto raw = (input_raw_t *) input.get(); auto raw = (input_raw_t *) input.get();
if (!raw->vigem) { if (!raw->vigem) {
@@ -619,35 +628,35 @@ namespace platf {
VIGEM_TARGET_TYPE selectedGamepadType; VIGEM_TARGET_TYPE selectedGamepadType;
if (config::input.gamepad == "x360"sv) { if (config::input.gamepad == "x360"sv) {
BOOST_LOG(info) << "Gamepad " << nr << " will be Xbox 360 controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (manual selection)"sv;
selectedGamepadType = Xbox360Wired; selectedGamepadType = Xbox360Wired;
} }
else if (config::input.gamepad == "ps4"sv || config::input.gamepad == "ds4"sv) { else if (config::input.gamepad == "ps4"sv || config::input.gamepad == "ds4"sv) {
BOOST_LOG(info) << "Gamepad " << nr << " will be DualShock 4 controller (manual selection)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (manual selection)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} }
else if (metadata.type == LI_CTYPE_PS) { else if (metadata.type == LI_CTYPE_PS) {
BOOST_LOG(info) << "Gamepad " << nr << " will be DualShock 4 controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} }
else if (metadata.type == LI_CTYPE_XBOX) { else if (metadata.type == LI_CTYPE_XBOX) {
BOOST_LOG(info) << "Gamepad " << nr << " will be Xbox 360 controller (auto-selected by client-reported type)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (auto-selected by client-reported type)"sv;
selectedGamepadType = Xbox360Wired; selectedGamepadType = Xbox360Wired;
} }
else if (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO)) { else if (metadata.capabilities & (LI_CCAP_ACCEL | LI_CCAP_GYRO)) {
BOOST_LOG(info) << "Gamepad " << nr << " will be DualShock 4 controller (auto-selected by motion sensor presence)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by motion sensor presence)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} }
else if (metadata.capabilities & LI_CCAP_TOUCHPAD) { else if (metadata.capabilities & LI_CCAP_TOUCHPAD) {
BOOST_LOG(info) << "Gamepad " << nr << " will be DualShock 4 controller (auto-selected by touchpad presence)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be DualShock 4 controller (auto-selected by touchpad presence)"sv;
selectedGamepadType = DualShock4Wired; selectedGamepadType = DualShock4Wired;
} }
else { else {
BOOST_LOG(info) << "Gamepad " << nr << " will be Xbox 360 controller (default)"sv; BOOST_LOG(info) << "Gamepad " << id.globalIndex << " will be Xbox 360 controller (default)"sv;
selectedGamepadType = Xbox360Wired; selectedGamepadType = Xbox360Wired;
} }
return raw->vigem->alloc_gamepad_internal(nr, feedback_queue, selectedGamepadType); return raw->vigem->alloc_gamepad_internal(id, feedback_queue, selectedGamepadType);
} }
void void
@@ -873,7 +882,7 @@ namespace platf {
return; return;
} }
auto &gamepad = vigem->gamepads[touch.gamepadNumber]; auto &gamepad = vigem->gamepads[touch.id.globalIndex];
if (!gamepad.gp) { if (!gamepad.gp) {
return; return;
} }
@@ -973,7 +982,7 @@ namespace platf {
return; return;
} }
auto &gamepad = vigem->gamepads[motion.gamepadNumber]; auto &gamepad = vigem->gamepads[motion.id.globalIndex];
if (!gamepad.gp) { if (!gamepad.gp) {
return; return;
} }
@@ -1005,7 +1014,7 @@ namespace platf {
return; return;
} }
auto &gamepad = vigem->gamepads[battery.gamepadNumber]; auto &gamepad = vigem->gamepads[battery.id.globalIndex];
if (!gamepad.gp) { if (!gamepad.gp) {
return; return;
} }