From bd62f3a99fd36fbd02808fd8a97dcfc459f90a1a Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 25 Jun 2023 17:40:25 -0500 Subject: [PATCH 1/7] Send feature flags in RTSP DESCRIBE response --- src/platform/common.h | 15 +++++++++++++++ src/platform/linux/input.cpp | 9 +++++++++ src/platform/macos/input.cpp | 9 +++++++++ src/platform/windows/input.cpp | 9 +++++++++ src/rtsp.cpp | 4 ++++ 5 files changed, 46 insertions(+) diff --git a/src/platform/common.h b/src/platform/common.h index 91fced164f8..4d2ba92113d 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -160,6 +160,14 @@ namespace platf { int width, height; }; + // These values must match Limelight-internal.h's SS_FF_* constants! + namespace platform_caps { + typedef uint32_t caps_t; + + constexpr caps_t pen_touch = 0x01; // Pen and touch events + constexpr caps_t controller_touch = 0x02; // Controller touch events + }; // namespace platform_caps + struct gamepad_state_t { std::uint32_t buttonFlags; std::uint8_t lt; @@ -475,6 +483,13 @@ namespace platf { void free_gamepad(input_t &input, int nr); + /** + * @brief Returns the supported platform capabilities to advertise to the client. + * @return Capability flags. + */ + platform_caps::caps_t + get_capabilities(); + #define SERVICE_NAME "Sunshine" #define SERVICE_TYPE "_nvstream._tcp" diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index d266ed5ebda..942d69e9093 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -1833,4 +1833,13 @@ namespace platf { return gamepads; } + + /** + * @brief Returns the supported platform capabilities to advertise to the client. + * @return Capability flags. + */ + platform_caps::caps_t + get_capabilities() { + return 0; + } } // namespace platf diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 0ca85e75c72..b9b5b9b4a0f 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -500,4 +500,13 @@ const KeyCodeMap kKeyCodesMap[] = { return gamepads; } + + /** + * @brief Returns the supported platform capabilities to advertise to the client. + * @return Capability flags. + */ + platform_caps::caps_t + get_capabilities() { + return 0; + } } // namespace platf diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index c3067cb358c..797c9d7e9a8 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -825,4 +825,13 @@ namespace platf { return gps; } + + /** + * @brief Returns the supported platform capabilities to advertise to the client. + * @return Capability flags. + */ + platform_caps::caps_t + get_capabilities() { + return 0; + } } // namespace platf diff --git a/src/rtsp.cpp b/src/rtsp.cpp index a2e4a2fc3b1..28a029c6a5b 100644 --- a/src/rtsp.cpp +++ b/src/rtsp.cpp @@ -492,6 +492,10 @@ namespace rtsp_stream { option.content = const_cast(seqn_str.c_str()); std::stringstream ss; + + // Tell the client about our supported features + ss << "a=x-ss-general.featureFlags: " << (uint32_t) platf::get_capabilities(); + if (video::active_hevc_mode != 1) { ss << "sprop-parameter-sets=AAAAAU"sv << std::endl; } From 3c9eca195553cd70bd7e94549b45949085b4c91d Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 8 Jul 2023 12:04:16 -0500 Subject: [PATCH 2/7] Stub controller battery, touch, and motion packets --- src/input.cpp | 187 +++++++++++++++++++++++++++++++++ src/platform/common.h | 50 +++++++++ src/platform/linux/input.cpp | 30 ++++++ src/platform/macos/input.cpp | 30 ++++++ src/platform/windows/input.cpp | 30 ++++++ third-party/moonlight-common-c | 2 +- 6 files changed, 328 insertions(+), 1 deletion(-) diff --git a/src/input.cpp b/src/input.cpp index 375915147ec..c7b0be14c11 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -22,6 +22,8 @@ extern "C" { #include "thread_pool.h" #include "utility.h" +#include + using namespace std::literals; namespace input { @@ -78,6 +80,16 @@ namespace input { return kpid & 0xFF; } + /** + * @brief Converts a little-endian netfloat to a native endianness float. + * @param f Netfloat value. + * @return Float value. + */ + float + from_netfloat(netfloat f) { + return boost::endian::endian_load(f); + } + static task_pool_util::TaskPool::task_id_t key_press_repeat_id {}; static std::unordered_map key_press {}; static std::array mouse_press {}; @@ -279,6 +291,53 @@ namespace input { << "--end controller arrival packet--"sv; } + /** + * @brief Prints a controller touch packet. + * @param packet The controller touch packet. + */ + void + print(PSS_CONTROLLER_TOUCH_PACKET packet) { + BOOST_LOG(debug) + << "--begin controller touch packet--"sv << std::endl + << "controllerNumber ["sv << (uint32_t) packet->controllerNumber << ']' << std::endl + << "eventType ["sv << util::hex(packet->eventType).to_string_view() << ']' << std::endl + << "pointerId ["sv << util::hex(packet->pointerId).to_string_view() << ']' << std::endl + << "x ["sv << from_netfloat(packet->x) << ']' << std::endl + << "y ["sv << from_netfloat(packet->y) << ']' << std::endl + << "pressure ["sv << from_netfloat(packet->pressure) << ']' << std::endl + << "--end controller touch packet--"sv; + } + + /** + * @brief Prints a controller motion packet. + * @param packet The controller motion packet. + */ + void + print(PSS_CONTROLLER_MOTION_PACKET packet) { + BOOST_LOG(verbose) + << "--begin controller motion packet--"sv << std::endl + << "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl + << "motionType ["sv << util::hex(packet->motionType).to_string_view() << ']' << std::endl + << "x ["sv << from_netfloat(packet->x) << ']' << std::endl + << "y ["sv << from_netfloat(packet->y) << ']' << std::endl + << "z ["sv << from_netfloat(packet->z) << ']' << std::endl + << "--end controller motion packet--"sv; + } + + /** + * @brief Prints a controller battery packet. + * @param packet The controller battery packet. + */ + void + print(PSS_CONTROLLER_BATTERY_PACKET packet) { + BOOST_LOG(verbose) + << "--begin controller battery packet--"sv << std::endl + << "controllerNumber ["sv << util::hex(packet->controllerNumber).to_string_view() << ']' << std::endl + << "batteryState ["sv << util::hex(packet->batteryState).to_string_view() << ']' << std::endl + << "batteryPercentage ["sv << util::hex(packet->batteryPercentage).to_string_view() << ']' << std::endl + << "--end controller battery packet--"sv; + } + void print(void *payload) { auto header = (PNV_INPUT_HEADER) payload; @@ -313,6 +372,15 @@ namespace input { case SS_CONTROLLER_ARRIVAL_MAGIC: print((PSS_CONTROLLER_ARRIVAL_PACKET) payload); break; + case SS_CONTROLLER_TOUCH_MAGIC: + print((PSS_CONTROLLER_TOUCH_PACKET) payload); + break; + case SS_CONTROLLER_MOTION_MAGIC: + print((PSS_CONTROLLER_MOTION_PACKET) payload); + break; + case SS_CONTROLLER_BATTERY_MAGIC: + print((PSS_CONTROLLER_BATTERY_PACKET) payload); + break; } } @@ -716,6 +784,116 @@ namespace input { input->gamepads[packet->controllerNumber].id = packet->controllerNumber; } + /** + * @brief Called to pass a controller touch message to the platform backend. + * @param input The input context pointer. + * @param packet The controller touch packet. + */ + void + passthrough(std::shared_ptr &input, PSS_CONTROLLER_TOUCH_PACKET packet) { + if (!config::input.controller) { + return; + } + + if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) { + BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']'; + return; + } + + 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 (gamepad.id < 0) { + return; + } + + platf::gamepad_touch_t touch { + packet->controllerNumber, + packet->eventType, + util::endian::little(packet->pointerId), + from_netfloat(packet->x), + from_netfloat(packet->y), + from_netfloat(packet->pressure), + }; + + platf::gamepad_touch(platf_input, touch); + } + + /** + * @brief Called to pass a controller motion message to the platform backend. + * @param input The input context pointer. + * @param packet The controller motion packet. + */ + void + passthrough(std::shared_ptr &input, PSS_CONTROLLER_MOTION_PACKET packet) { + if (!config::input.controller) { + return; + } + + if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) { + BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']'; + return; + } + + 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 (gamepad.id < 0) { + return; + } + + platf::gamepad_motion_t motion { + packet->controllerNumber, + packet->motionType, + from_netfloat(packet->x), + from_netfloat(packet->y), + from_netfloat(packet->z), + }; + + platf::gamepad_motion(platf_input, motion); + } + + /** + * @brief Called to pass a controller battery message to the platform backend. + * @param input The input context pointer. + * @param packet The controller battery packet. + */ + void + passthrough(std::shared_ptr &input, PSS_CONTROLLER_BATTERY_PACKET packet) { + if (!config::input.controller) { + return; + } + + if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) { + BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']'; + return; + } + + 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 (gamepad.id < 0) { + return; + } + + platf::gamepad_battery_t battery { + packet->controllerNumber, + packet->batteryState, + packet->batteryPercentage + }; + + platf::gamepad_battery(platf_input, battery); + } + void passthrough(std::shared_ptr &input, PNV_MULTI_CONTROLLER_PACKET packet) { if (!config::input.controller) { @@ -1196,6 +1374,15 @@ namespace input { case SS_CONTROLLER_ARRIVAL_MAGIC: passthrough(input, (PSS_CONTROLLER_ARRIVAL_PACKET) payload); break; + case SS_CONTROLLER_TOUCH_MAGIC: + passthrough(input, (PSS_CONTROLLER_TOUCH_PACKET) payload); + break; + case SS_CONTROLLER_MOTION_MAGIC: + passthrough(input, (PSS_CONTROLLER_MOTION_PACKET) payload); + break; + case SS_CONTROLLER_BATTERY_MAGIC: + passthrough(input, (PSS_CONTROLLER_BATTERY_PACKET) payload); + break; } } diff --git a/src/platform/common.h b/src/platform/common.h index 4d2ba92113d..49e1b1c17b7 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -185,6 +185,32 @@ namespace platf { std::uint32_t supportedButtons; }; + struct gamepad_touch_t { + std::uint8_t gamepadNumber; + std::uint8_t eventType; + std::uint32_t pointerId; + float x; + float y; + float pressure; + }; + + struct gamepad_motion_t { + std::uint8_t gamepadNumber; + std::uint8_t motionType; + + // Accel: m/s^2 + // Gyro: deg/s + float x; + float y; + float z; + }; + + struct gamepad_battery_t { + std::uint8_t gamepadNumber; + std::uint8_t state; + std::uint8_t percentage; + }; + class deinit_t { public: virtual ~deinit_t() = default; @@ -470,6 +496,30 @@ namespace platf { void unicode(input_t &input, char *utf8, int size); + /** + * @brief Sends a gamepad touch event to the OS. + * @param input The input context. + * @param touch The touch event. + */ + void + gamepad_touch(input_t &input, const gamepad_touch_t &touch); + + /** + * @brief Sends a gamepad motion event to the OS. + * @param input The input context. + * @param motion The motion event. + */ + void + gamepad_motion(input_t &input, const gamepad_motion_t &motion); + + /** + * @brief Sends a gamepad battery event to the OS. + * @param input The input context. + * @param battery The battery event. + */ + void + gamepad_battery(input_t &input, const gamepad_battery_t &battery); + /** * @brief Creates a new virtual gamepad. * @param input The input context. diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index 942d69e9093..62a2e906fd7 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -1567,6 +1567,36 @@ namespace platf { libevdev_uinput_write_event(uinput.get(), EV_SYN, SYN_REPORT, 0); } + /** + * @brief Sends a gamepad touch event to the OS. + * @param input The input context. + * @param touch The touch event. + */ + void + gamepad_touch(input_t &input, const gamepad_touch_t &touch) { + // Unimplemented feature - platform_caps::controller_touch + } + + /** + * @brief Sends a gamepad motion event to the OS. + * @param input The input context. + * @param motion The motion event. + */ + void + gamepad_motion(input_t &input, const gamepad_motion_t &motion) { + // Unimplemented + } + + /** + * @brief Sends a gamepad battery event to the OS. + * @param input The input context. + * @param battery The battery event. + */ + void + gamepad_battery(input_t &input, const gamepad_battery_t &battery) { + // Unimplemented + } + /** * @brief Initialize a new keyboard and return it. * diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index b9b5b9b4a0f..b9eb073ae88 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -448,6 +448,36 @@ const KeyCodeMap kKeyCodesMap[] = { // Unimplemented } + /** + * @brief Sends a gamepad touch event to the OS. + * @param input The input context. + * @param touch The touch event. + */ + void + gamepad_touch(input_t &input, const gamepad_touch_t &touch) { + // Unimplemented feature - platform_caps::controller_touch + } + + /** + * @brief Sends a gamepad motion event to the OS. + * @param input The input context. + * @param motion The motion event. + */ + void + gamepad_motion(input_t &input, const gamepad_motion_t &motion) { + // Unimplemented + } + + /** + * @brief Sends a gamepad battery event to the OS. + * @param input The input context. + * @param battery The battery event. + */ + void + gamepad_battery(input_t &input, const gamepad_battery_t &battery) { + // Unimplemented + } + input_t input() { input_t result { new macos_input_t() }; diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 797c9d7e9a8..76f2cd74348 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -805,6 +805,36 @@ namespace platf { } } + /** + * @brief Sends a gamepad touch event to the OS. + * @param input The input context. + * @param touch The touch event. + */ + void + gamepad_touch(input_t &input, const gamepad_touch_t &touch) { + // Unimplemented feature - platform_caps::controller_touch + } + + /** + * @brief Sends a gamepad motion event to the OS. + * @param input The input context. + * @param motion The motion event. + */ + void + gamepad_motion(input_t &input, const gamepad_motion_t &motion) { + // Unimplemented + } + + /** + * @brief Sends a gamepad battery event to the OS. + * @param input The input context. + * @param battery The battery event. + */ + void + gamepad_battery(input_t &input, const gamepad_battery_t &battery) { + // Unimplemented + } + void freeInput(void *p) { auto input = (input_raw_t *) p; diff --git a/third-party/moonlight-common-c b/third-party/moonlight-common-c index 28d63b11ddb..49fef038302 160000 --- a/third-party/moonlight-common-c +++ b/third-party/moonlight-common-c @@ -1 +1 @@ -Subproject commit 28d63b11ddb808662f0a7d90674a1376d99059c4 +Subproject commit 49fef0383020272941c4704d2ecf3e1e71174092 From 7568b565f990ded518e67e1dde5c19c900cef1a6 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 8 Jul 2023 15:23:27 -0500 Subject: [PATCH 3/7] Convert rumble_queue into a generic feedback_queue for gamepad messages --- src/input.cpp | 16 ++-- src/main.h | 2 +- src/platform/common.h | 76 ++++++++++++++-- src/platform/linux/input.cpp | 22 ++--- src/platform/macos/input.cpp | 4 +- src/platform/windows/input.cpp | 70 +++++++++++--- src/stream.cpp | 162 ++++++++++++++++++++++++++------- 7 files changed, 274 insertions(+), 78 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index c7b0be14c11..b404516b49f 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -140,12 +140,12 @@ namespace input { input_t( safe::mail_raw_t::event_t touch_port_event, - platf::rumble_queue_t rumble_queue): + platf::feedback_queue_t feedback_queue): shortcutFlags {}, active_gamepad_state {}, gamepads(MAX_GAMEPADS), touch_port_event { std::move(touch_port_event) }, - rumble_queue { std::move(rumble_queue) }, + feedback_queue { std::move(feedback_queue) }, mouse_left_button_timeout {}, touch_port { { 0, 0, 0, 0 }, 0, 0, 1.0f } {} @@ -156,7 +156,7 @@ namespace input { std::vector gamepads; safe::mail_raw_t::event_t touch_port_event; - platf::rumble_queue_t rumble_queue; + platf::feedback_queue_t feedback_queue; std::list> input_queue; std::mutex input_queue_lock; @@ -703,7 +703,7 @@ namespace input { } int - updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state, const platf::rumble_queue_t &rumble_queue) { + updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state, const platf::feedback_queue_t &feedback_queue) { auto xorGamepadMask = old_state ^ new_state; if (!xorGamepadMask) { return 0; @@ -729,7 +729,7 @@ namespace input { return -1; } - if (platf::alloc_gamepad(platf_input, id, {}, rumble_queue)) { + if (platf::alloc_gamepad(platf_input, id, {}, feedback_queue)) { free_id(gamepadMask, id); // allocating a gamepad failed: solution: ignore gamepads // The implementations of platf::alloc_gamepad already has logging @@ -776,7 +776,7 @@ namespace input { input->active_gamepad_state |= (1 << packet->controllerNumber); // Allocate a new gamepad - if (platf::alloc_gamepad(platf_input, packet->controllerNumber, arrival, input->rumble_queue)) { + if (platf::alloc_gamepad(platf_input, packet->controllerNumber, arrival, input->feedback_queue)) { free_id(gamepadMask, packet->controllerNumber); return; } @@ -900,7 +900,7 @@ namespace input { return; } - if (updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask, input->rumble_queue)) { + if (updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask, input->feedback_queue)) { return; } @@ -1443,7 +1443,7 @@ namespace input { alloc(safe::mail_t mail) { auto input = std::make_shared( mail->event(mail::touch_port), - mail->queue(mail::rumble)); + mail->queue(mail::gamepad_feedback)); // Workaround to ensure new frames will be captured when a client connects task_pool.pushDelayed([]() { diff --git a/src/main.h b/src/main.h index e98206f3838..d5a6eeb5a07 100644 --- a/src/main.h +++ b/src/main.h @@ -62,7 +62,7 @@ namespace mail { // Local mail MAIL(touch_port); MAIL(idr); - MAIL(rumble); + MAIL(gamepad_feedback); MAIL(hdr); #undef MAIL diff --git a/src/platform/common.h b/src/platform/common.h index 49e1b1c17b7..eb1b45fd1eb 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -72,17 +72,75 @@ namespace platf { constexpr std::uint32_t TOUCHPAD_BUTTON = 0x100000; constexpr std::uint32_t MISC_BUTTON = 0x200000; - struct rumble_t { - KITTY_DEFAULT_CONSTR(rumble_t) + enum class gamepad_feedback_e { + rumble, + rumble_triggers, + set_motion_event_state, + set_rgb_led, + }; - rumble_t(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq): - id { id }, lowfreq { lowfreq }, highfreq { highfreq } {} + struct gamepad_feedback_msg_t { + static gamepad_feedback_msg_t + make_rumble(std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) { + gamepad_feedback_msg_t msg; + msg.type = gamepad_feedback_e::rumble; + msg.id = id; + msg.data.rumble = { lowfreq, highfreq }; + return msg; + } + static gamepad_feedback_msg_t + make_rumble_triggers(std::uint16_t id, std::uint16_t left, std::uint16_t right) { + gamepad_feedback_msg_t msg; + msg.type = gamepad_feedback_e::rumble_triggers; + msg.id = id; + msg.data.rumble_triggers = { left, right }; + return msg; + } + + static gamepad_feedback_msg_t + make_motion_event_state(std::uint16_t id, std::uint8_t motion_type, std::uint16_t report_rate) { + gamepad_feedback_msg_t msg; + msg.type = gamepad_feedback_e::set_motion_event_state; + msg.id = id; + msg.data.motion_event_state.motion_type = motion_type; + msg.data.motion_event_state.report_rate = report_rate; + return msg; + } + + static gamepad_feedback_msg_t + make_rgb_led(std::uint16_t id, std::uint8_t r, std::uint8_t g, std::uint8_t b) { + gamepad_feedback_msg_t msg; + msg.type = gamepad_feedback_e::set_rgb_led; + msg.id = id; + msg.data.rgb_led = { r, g, b }; + return msg; + } + + gamepad_feedback_e type; std::uint16_t id; - std::uint16_t lowfreq; - std::uint16_t highfreq; + union { + struct { + std::uint16_t lowfreq; + std::uint16_t highfreq; + } rumble; + struct { + std::uint16_t left_trigger; + std::uint16_t right_trigger; + } rumble_triggers; + struct { + std::uint16_t report_rate; + std::uint8_t motion_type; + } motion_event_state; + struct { + std::uint8_t r; + std::uint8_t g; + std::uint8_t b; + } rgb_led; + } data; }; - using rumble_queue_t = safe::mail_raw_t::queue_t; + + using feedback_queue_t = safe::mail_raw_t::queue_t; namespace speaker { enum speaker_e { @@ -525,11 +583,11 @@ namespace platf { * @param input The input context. * @param nr The assigned controller number. * @param metadata Controller metadata from client (empty if none provided). - * @param rumble_queue The queue for posting rumble messages to the client. + * @param feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, rumble_queue_t rumble_queue); + alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue); void free_gamepad(input_t &input, int nr); diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index 62a2e906fd7..c427013f2b2 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -133,7 +133,7 @@ namespace platf { } }); - using mail_evdev_t = std::tuple; + using mail_evdev_t = std::tuple; struct keycode_t { std::uint32_t keycode; @@ -452,7 +452,7 @@ namespace platf { public: KITTY_DEFAULT_CONSTR_MOVE(effect_t) - effect_t(int gamepadnr, uinput_t::pointer dev, rumble_queue_t &&q): + effect_t(int gamepadnr, uinput_t::pointer dev, feedback_queue_t &&q): gamepadnr { gamepadnr }, dev { dev }, rumble_queue { std::move(q) }, gain { 0xFFFF }, id_to_data {} {} class data_t { @@ -634,7 +634,7 @@ namespace platf { // Used as ID for adding/removinf devices from evdev notifications uinput_t::pointer dev; - rumble_queue_t rumble_queue; + feedback_queue_t rumble_queue; int gain; @@ -774,11 +774,11 @@ namespace platf { * @brief Creates a new virtual gamepad. * @param nr The assigned controller number. * @param metadata Controller metadata from client (empty if none provided). - * @param rumble_queue The queue for posting rumble messages to the client. + * @param feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(int nr, const gamepad_arrival_t &metadata, rumble_queue_t &&rumble_queue) { + alloc_gamepad(int nr, const gamepad_arrival_t &metadata, feedback_queue_t &&feedback_queue) { TUPLE_2D_REF(input, gamepad_state, gamepads[nr]); int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input); @@ -803,7 +803,7 @@ namespace platf { rumble_ctx->rumble_queue_queue.raise( nr, input.get(), - std::move(rumble_queue), + std::move(feedback_queue), pollfd_t { dup(libevdev_uinput_get_fd(input.get())), (std::int16_t) POLLIN, @@ -1048,9 +1048,9 @@ namespace platf { TUPLE_2D(weak, strong, effect.rumble(now)); if (old_weak != weak || old_strong != strong) { - BOOST_LOG(debug) << "Sending haptic feedback: lowfreq [0x"sv << util::hex(weak).to_string_view() << "]: highfreq [0x"sv << util::hex(strong).to_string_view() << ']'; + BOOST_LOG(debug) << "Sending haptic feedback: lowfreq [0x"sv << util::hex(strong).to_string_view() << "]: highfreq [0x"sv << util::hex(weak).to_string_view() << ']'; - effect.rumble_queue->raise(effect.gamepadnr, weak, strong); + effect.rumble_queue->raise(gamepad_feedback_msg_t::make_rumble(effect.gamepadnr, strong, weak)); } } } @@ -1492,12 +1492,12 @@ namespace platf { * @param input The input context. * @param nr The assigned controller number. * @param metadata Controller metadata from client (empty if none provided). - * @param rumble_queue The queue for posting rumble messages to the client. + * @param feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, rumble_queue_t rumble_queue) { - return ((input_raw_t *) input.get())->alloc_gamepad(nr, metadata, std::move(rumble_queue)); + alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { + return ((input_raw_t *) input.get())->alloc_gamepad(nr, metadata, std::move(feedback_queue)); } void diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index b9eb073ae88..17073479999 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -293,11 +293,11 @@ const KeyCodeMap kKeyCodesMap[] = { * @param input The input context. * @param nr The assigned controller number. * @param metadata Controller metadata from client (empty if none provided). - * @param rumble_queue The queue for posting rumble messages to the client. + * @param feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, rumble_queue_t rumble_queue) { + alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { BOOST_LOG(info) << "alloc_gamepad: Gamepad not yet implemented for MacOS."sv; return -1; } diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 76f2cd74348..74abfc428eb 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -44,12 +44,15 @@ namespace platf { struct gamepad_context_t { target_t gp; - rumble_queue_t rumble_queue; + feedback_queue_t feedback_queue; union { XUSB_REPORT x360; DS4_REPORT_EX ds4; } report; + + gamepad_feedback_msg_t last_rumble; + gamepad_feedback_msg_t last_rgb_led; }; constexpr float EARTH_G = 9.80665f; @@ -181,12 +184,12 @@ namespace platf { /** * @brief Attaches a new gamepad. * @param nr The gamepad index. - * @param rumble_queue The queue to publish rumble packets. + * @param feedback_queue The queue for posting messages back to the client. * @param gp_type The type of gamepad. * @return 0 on success. */ int - alloc_gamepad_internal(int nr, rumble_queue_t &rumble_queue, VIGEM_TARGET_TYPE gp_type) { + alloc_gamepad_internal(int nr, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) { auto &gamepad = gamepads[nr]; assert(!gamepad.gp); @@ -212,7 +215,7 @@ namespace platf { return -1; } - gamepad.rumble_queue = std::move(rumble_queue); + gamepad.feedback_queue = std::move(feedback_queue); if (gp_type == Xbox360Wired) { status = vigem_target_x360_register_notification(client.get(), gamepad.gp.get(), x360_notify, this); @@ -247,19 +250,52 @@ namespace platf { } /** - * @brief Pass rumble data back to the host. + * @brief Pass rumble data back to the client. * @param target The gamepad. - * @param smallMotor The small motor. * @param largeMotor The large motor. + * @param smallMotor The small motor. */ void - rumble(target_t::pointer target, std::uint8_t smallMotor, std::uint8_t largeMotor) { + rumble(target_t::pointer target, std::uint8_t largeMotor, std::uint8_t smallMotor) { for (int x = 0; x < gamepads.size(); ++x) { auto &gamepad = gamepads[x]; if (gamepad.gp.get() == target) { - gamepad.rumble_queue->raise(x, ((std::uint16_t) smallMotor) << 8, ((std::uint16_t) largeMotor) << 8); + // Convert from 8-bit to 16-bit values + uint16_t normalizedLargeMotor = largeMotor << 8; + uint16_t normalizedSmallMotor = smallMotor << 8; + + // Don't resend duplicate rumble data + if (normalizedSmallMotor != gamepad.last_rumble.data.rumble.highfreq || + normalizedLargeMotor != gamepad.last_rumble.data.rumble.lowfreq) { + gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rumble(x, normalizedLargeMotor, normalizedSmallMotor); + gamepad.feedback_queue->raise(msg); + gamepad.last_rumble = msg; + } + return; + } + } + } + /** + * @brief Pass RGB LED data back to the client. + * @param target The gamepad. + * @param r The red channel. + * @param g The red channel. + * @param b The red channel. + */ + void + set_rgb_led(target_t::pointer target, std::uint8_t r, std::uint8_t g, std::uint8_t b) { + for (int x = 0; x < gamepads.size(); ++x) { + auto &gamepad = gamepads[x]; + + if (gamepad.gp.get() == target) { + // 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) { + gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rgb_led(x, r, g, b); + gamepad.feedback_queue->raise(msg); + gamepad.last_rgb_led = msg; + } return; } } @@ -299,7 +335,7 @@ namespace platf { << "largeMotor: "sv << (int) largeMotor << std::endl << "smallMotor: "sv << (int) smallMotor; - task_pool.push(&vigem_t::rumble, (vigem_t *) userdata, target, smallMotor, largeMotor); + task_pool.push(&vigem_t::rumble, (vigem_t *) userdata, target, largeMotor, smallMotor); } void CALLBACK @@ -307,13 +343,17 @@ namespace platf { client_t::pointer client, target_t::pointer target, std::uint8_t largeMotor, std::uint8_t smallMotor, - DS4_LIGHTBAR_COLOR /* led_color */, + DS4_LIGHTBAR_COLOR led_color, void *userdata) { BOOST_LOG(debug) << "largeMotor: "sv << (int) largeMotor << std::endl - << "smallMotor: "sv << (int) smallMotor; + << "smallMotor: "sv << (int) smallMotor << std::endl + << "LED: "sv << util::hex(led_color.Red).to_string_view() << ' ' + << util::hex(led_color.Green).to_string_view() << ' ' + << util::hex(led_color.Blue).to_string_view() << std::endl; - task_pool.push(&vigem_t::rumble, (vigem_t *) userdata, target, smallMotor, largeMotor); + task_pool.push(&vigem_t::rumble, (vigem_t *) userdata, target, largeMotor, smallMotor); + task_pool.push(&vigem_t::set_rgb_led, (vigem_t *) userdata, target, led_color.Red, led_color.Green, led_color.Blue); } struct input_raw_t { @@ -554,11 +594,11 @@ namespace platf { * @param input The input context. * @param nr The assigned controller number. * @param metadata Controller metadata from client (empty if none provided). - * @param rumble_queue The queue for posting rumble messages to the client. + * @param feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, rumble_queue_t rumble_queue) { + alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { auto raw = (input_raw_t *) input.get(); if (!raw->vigem) { @@ -596,7 +636,7 @@ namespace platf { selectedGamepadType = Xbox360Wired; } - return raw->vigem->alloc_gamepad_internal(nr, rumble_queue, selectedGamepadType); + return raw->vigem->alloc_gamepad_internal(nr, feedback_queue, selectedGamepadType); } void diff --git a/src/stream.cpp b/src/stream.cpp index 236797c57e9..1643dadee15 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -39,6 +39,9 @@ extern "C" { #define IDX_REQUEST_IDR_FRAME 9 #define IDX_ENCRYPTED 10 #define IDX_HDR_MODE 11 +#define IDX_RUMBLE_TRIGGER_DATA 12 +#define IDX_SET_MOTION_EVENT 13 +#define IDX_SET_RGB_LED 14 static const short packetTypes[] = { 0x0305, // Start A @@ -53,6 +56,9 @@ static const short packetTypes[] = { 0x0302, // IDR frame 0x0001, // fully encrypted 0x010e, // HDR mode + 0x5500, // Rumble triggers (Sunshine protocol extension) + 0x5501, // Set motion event (Sunshine protocol extension) + 0x5502, // Set RGB LED (Sunshine protocol extension) }; namespace asio = boost::asio; @@ -146,6 +152,31 @@ namespace stream { std::uint16_t highfreq; }; + struct control_rumble_triggers_t { + control_header_v2 header; + + std::uint16_t id; + std::uint16_t left; + std::uint16_t right; + }; + + struct control_set_motion_event_t { + control_header_v2 header; + + std::uint16_t id; + std::uint16_t reportrate; + std::uint8_t type; + }; + + struct control_set_rgb_led_t { + control_header_v2 header; + + std::uint16_t id; + std::uint8_t r; + std::uint8_t g; + std::uint8_t b; + }; + struct control_hdr_mode_t { control_header_v2 header; @@ -350,7 +381,7 @@ namespace stream { net::peer_t peer; std::uint8_t seq; - platf::rumble_queue_t rumble_queue; + platf::feedback_queue_t feedback_queue; safe::mail_raw_t::event_t hdr_queue; } control; @@ -621,38 +652,107 @@ namespace stream { return replaced; } + /** + * @brief Pass gamepad feedback data back to the client. + * @param session The session object. + * @param msg The message to pass. + * @return 0 on success. + */ int - send_rumble(session_t *session, std::uint16_t id, std::uint16_t lowfreq, std::uint16_t highfreq) { + send_feedback_msg(session_t *session, platf::gamepad_feedback_msg_t &msg) { if (!session->control.peer) { - BOOST_LOG(warning) << "Couldn't send rumble data, still waiting for PING from Moonlight"sv; + BOOST_LOG(warning) << "Couldn't send gamepad feedback 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); + std::string payload; + if (msg.type == platf::gamepad_feedback_e::rumble) { + control_rumble_t plaintext; + plaintext.header.type = packetTypes[IDX_RUMBLE_DATA]; + plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2); - plaintext.useless = 0xC0FFEE; - plaintext.id = util::endian::little(id); - plaintext.lowfreq = util::endian::little(lowfreq); - plaintext.highfreq = util::endian::little(highfreq); + auto &data = msg.data.rumble; - BOOST_LOG(verbose) << id << " :: "sv << util::hex(lowfreq).to_string_view() << " :: "sv << util::hex(highfreq).to_string_view(); - std::array - encrypted_payload; + plaintext.useless = 0xC0FFEE; + plaintext.id = util::endian::little(msg.id); + plaintext.lowfreq = util::endian::little(data.lowfreq); + plaintext.highfreq = util::endian::little(data.highfreq); + + BOOST_LOG(verbose) << "Rumble: "sv << msg.id << " :: "sv << util::hex(data.lowfreq).to_string_view() << " :: "sv << util::hex(data.highfreq).to_string_view(); + std::array + encrypted_payload; + + payload = encode_control(session, util::view(plaintext), encrypted_payload); + } + else if (msg.type == platf::gamepad_feedback_e::rumble_triggers) { + control_rumble_triggers_t plaintext; + plaintext.header.type = packetTypes[IDX_RUMBLE_TRIGGER_DATA]; + plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2); + + auto &data = msg.data.rumble_triggers; + + plaintext.id = util::endian::little(msg.id); + plaintext.left = util::endian::little(data.left_trigger); + plaintext.right = util::endian::little(data.right_trigger); + + BOOST_LOG(verbose) << "Rumble triggers: "sv << msg.id << " :: "sv << util::hex(data.left_trigger).to_string_view() << " :: "sv << util::hex(data.right_trigger).to_string_view(); + std::array + encrypted_payload; + + payload = encode_control(session, util::view(plaintext), encrypted_payload); + } + else if (msg.type == platf::gamepad_feedback_e::set_motion_event_state) { + control_set_motion_event_t plaintext; + plaintext.header.type = packetTypes[IDX_SET_MOTION_EVENT]; + plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2); + + auto &data = msg.data.motion_event_state; + + plaintext.id = util::endian::little(msg.id); + plaintext.reportrate = util::endian::little(data.report_rate); + plaintext.type = data.motion_type; + + BOOST_LOG(verbose) << "Motion event state: "sv << msg.id << " :: "sv << util::hex(data.report_rate).to_string_view() << " :: "sv << util::hex(data.motion_type).to_string_view(); + std::array + encrypted_payload; + + payload = encode_control(session, util::view(plaintext), encrypted_payload); + } + else if (msg.type == platf::gamepad_feedback_e::set_rgb_led) { + control_set_rgb_led_t plaintext; + plaintext.header.type = packetTypes[IDX_SET_RGB_LED]; + plaintext.header.payloadLength = sizeof(plaintext) - sizeof(control_header_v2); + + auto &data = msg.data.rgb_led; + + plaintext.id = util::endian::little(msg.id); + plaintext.r = data.r; + plaintext.g = data.g; + plaintext.b = data.b; + + BOOST_LOG(verbose) << "RGB: "sv << msg.id << " :: "sv << util::hex(data.r).to_string_view() << util::hex(data.g).to_string_view() << util::hex(data.b).to_string_view(); + std::array + encrypted_payload; + + payload = encode_control(session, util::view(plaintext), encrypted_payload); + } + else { + BOOST_LOG(error) << "Unknown gamepad feedback message type"sv; + return -1; + } - 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 << ']'; + BOOST_LOG(warning) << "Couldn't send gamepad feedback to ["sv << addr << ':' << port << ']'; return -1; } - BOOST_LOG(debug) << "Send gamepadnr ["sv << id << "] with lowfreq ["sv << lowfreq << "] and highfreq ["sv << highfreq << ']'; - return 0; } @@ -858,22 +958,20 @@ namespace stream { if (!session->control.peer) { has_session_awaiting_peer = true; } + else { + auto &feedback_queue = session->control.feedback_queue; + while (feedback_queue->peek()) { + auto feedback_msg = feedback_queue->pop(); - 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); - } + send_feedback_msg(session, *feedback_msg); + } - // Unlike rumble which we send as best-effort, HDR state messages are critical - // for proper functioning of some clients. We must wait to pop entries from - // the queue until we're sure we have a peer to send them to. - auto &hdr_queue = session->control.hdr_queue; - while (session->control.peer && hdr_queue->peek()) { - auto hdr_info = hdr_queue->pop(); + auto &hdr_queue = session->control.hdr_queue; + while (session->control.peer && hdr_queue->peek()) { + auto hdr_info = hdr_queue->pop(); - send_hdr_mode(session, std::move(hdr_info)); + send_hdr_mode(session, std::move(hdr_info)); + } } ++pos; @@ -1648,7 +1746,7 @@ namespace stream { session->config = config; - session->control.rumble_queue = mail->queue(mail::rumble); + session->control.feedback_queue = mail->queue(mail::gamepad_feedback); session->control.hdr_queue = mail->event(mail::hdr); session->control.iv = iv; session->control.cipher = crypto::cipher::gcm_t { From 654b37b0556fe1d03d2a7c469482c8d143458e08 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 8 Jul 2023 18:08:35 -0500 Subject: [PATCH 4/7] Implement touch, motion, and battery for DS4 --- src/platform/windows/input.cpp | 221 +++++++++++++++++++++++++++++++-- 1 file changed, 214 insertions(+), 7 deletions(-) diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 74abfc428eb..6355442e8ca 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -42,6 +42,12 @@ namespace platf { DS4_LIGHTBAR_COLOR /* led_color */, void *userdata); + struct gp_touch_context_t { + uint8_t pointerIndex; + uint16_t x; + uint16_t y; + }; + struct gamepad_context_t { target_t gp; feedback_queue_t feedback_queue; @@ -51,6 +57,10 @@ namespace platf { DS4_REPORT_EX ds4; } report; + // Map from pointer ID to pointer index + std::map pointer_id_map; + uint8_t available_pointers; + gamepad_feedback_msg_t last_rumble; gamepad_feedback_msg_t last_rgb_led; }; @@ -81,7 +91,7 @@ namespace platf { .bTriggerL = 0, .bTriggerR = 0, .wTimestamp = 0, - .bBatteryLvl = 99, + .bBatteryLvl = 0xFF, .wGyroX = 0, .wGyroY = 0, .wGyroZ = 0, @@ -89,9 +99,9 @@ namespace platf { .wAccelY = 0, .wAccelZ = 0, ._bUnknown1 = { 0x00, 0x00, 0x00, 0x00, 0x00 }, - .bBatteryLvlSpecial = 0x10, // Wired + .bBatteryLvlSpecial = 0x1A, // Wired - Full battery ._bUnknown2 = { 0x00, 0x00 }, - .bTouchPacketsN = 0, + .bTouchPacketsN = 1, .sCurrentTouch = ds4_touch_unused, .sPreviousTouch = { ds4_touch_unused, ds4_touch_unused } } } }; @@ -206,6 +216,13 @@ namespace platf { // Set initial accelerometer and gyro state ds4_update_motion(gamepad, LI_MOTION_TYPE_ACCEL, 0.0f, EARTH_G, 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 + 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(nr, LI_MOTION_TYPE_GYRO, 100)); + + // We support pointer index 0 and 1 + gamepad.available_pointers = 0x3; } auto status = vigem_target_add(client.get(), gamepad.gp.get()); @@ -828,6 +845,9 @@ namespace platf { } auto &gamepad = vigem->gamepads[nr]; + if (!gamepad.gp) { + return; + } VIGEM_ERROR status; @@ -852,7 +872,97 @@ namespace platf { */ void gamepad_touch(input_t &input, const gamepad_touch_t &touch) { - // Unimplemented feature - platform_caps::controller_touch + auto vigem = ((input_raw_t *) input.get())->vigem; + + // If there is no gamepad support + if (!vigem) { + return; + } + + auto &gamepad = vigem->gamepads[touch.gamepadNumber]; + if (!gamepad.gp) { + return; + } + + // Touch is only supported on DualShock 4 controllers + if (vigem_target_get_type(gamepad.gp.get()) != DualShock4Wired) { + return; + } + + auto &report = gamepad.report.ds4.Report; + + uint8_t pointerIndex; + if (touch.eventType == LI_TOUCH_EVENT_DOWN) { + if (gamepad.available_pointers & 0x1) { + // Reserve pointer index 0 for this touch + gamepad.pointer_id_map[touch.pointerId] = pointerIndex = 0; + gamepad.available_pointers &= ~(1 << pointerIndex); + + // Set pointer 0 down + report.sCurrentTouch.bIsUpTrackingNum1 &= ~0x80; + report.sCurrentTouch.bIsUpTrackingNum1++; + } + else if (gamepad.available_pointers & 0x2) { + // Reserve pointer index 1 for this touch + gamepad.pointer_id_map[touch.pointerId] = pointerIndex = 1; + gamepad.available_pointers &= ~(1 << pointerIndex); + + // Set pointer 1 down + report.sCurrentTouch.bIsUpTrackingNum2 &= ~0x80; + report.sCurrentTouch.bIsUpTrackingNum2++; + } + else { + BOOST_LOG(warning) << "No more free pointer indices! Did the client miss an touch up event?"sv; + return; + } + } + else { + auto i = gamepad.pointer_id_map.find(touch.pointerId); + if (i == gamepad.pointer_id_map.end()) { + BOOST_LOG(warning) << "Pointer ID not found! Did the client miss a touch down event?"sv; + return; + } + + pointerIndex = (*i).second; + + if (touch.eventType == LI_TOUCH_EVENT_UP) { + // Remove the pointer index mapping + gamepad.pointer_id_map.erase(i); + + // Set pointer up + if (pointerIndex == 0) { + report.sCurrentTouch.bIsUpTrackingNum1 |= 0x80; + } + else { + report.sCurrentTouch.bIsUpTrackingNum2 |= 0x80; + } + + // Free the pointer index + gamepad.available_pointers |= (1 << pointerIndex); + } + } + + // Touchpad is 1920x943 according to ViGEm + uint16_t x = touch.x * 1920; + uint16_t y = touch.y * 943; + uint8_t touchData[] = { + (uint8_t) (x & 0xFF), // Low 8 bits of X + (uint8_t) (((x >> 8) & 0x0F) | ((y & 0x0F) << 4)), // High 4 bits of X and low 4 bits of Y + (uint8_t) (((y >> 4) & 0xFF)) // High 8 bits of Y + }; + + report.sCurrentTouch.bPacketCounter++; + if (pointerIndex == 0) { + memcpy(report.sCurrentTouch.bTouchData1, touchData, sizeof(touchData)); + } + else { + memcpy(report.sCurrentTouch.bTouchData2, touchData, sizeof(touchData)); + } + + auto status = vigem_target_ds4_update_ex(vigem->client.get(), gamepad.gp.get(), gamepad.report.ds4); + if (!VIGEM_SUCCESS(status)) { + BOOST_LOG(warning) << "Couldn't send gamepad touch input to ViGEm ["sv << util::hex(status).to_string_view() << ']'; + } } /** @@ -862,7 +972,29 @@ namespace platf { */ void gamepad_motion(input_t &input, const gamepad_motion_t &motion) { - // Unimplemented + auto vigem = ((input_raw_t *) input.get())->vigem; + + // If there is no gamepad support + if (!vigem) { + return; + } + + auto &gamepad = vigem->gamepads[motion.gamepadNumber]; + if (!gamepad.gp) { + return; + } + + // Motion is only supported on DualShock 4 controllers + if (vigem_target_get_type(gamepad.gp.get()) != DualShock4Wired) { + return; + } + + ds4_update_motion(gamepad, motion.motionType, motion.x, motion.y, motion.z); + + auto status = vigem_target_ds4_update_ex(vigem->client.get(), gamepad.gp.get(), gamepad.report.ds4); + if (!VIGEM_SUCCESS(status)) { + BOOST_LOG(warning) << "Couldn't send gamepad motion input to ViGEm ["sv << util::hex(status).to_string_view() << ']'; + } } /** @@ -872,7 +1004,75 @@ namespace platf { */ void gamepad_battery(input_t &input, const gamepad_battery_t &battery) { - // Unimplemented + auto vigem = ((input_raw_t *) input.get())->vigem; + + // If there is no gamepad support + if (!vigem) { + return; + } + + auto &gamepad = vigem->gamepads[battery.gamepadNumber]; + if (!gamepad.gp) { + return; + } + + // Battery is only supported on DualShock 4 controllers + if (vigem_target_get_type(gamepad.gp.get()) != DualShock4Wired) { + return; + } + + // For details on the report format of these battery level fields, see: + // https://github.com/torvalds/linux/blob/946c6b59c56dc6e7d8364a8959cb36bf6d10bc37/drivers/hid/hid-playstation.c#L2305-L2314 + + auto &report = gamepad.report.ds4.Report; + + // Update the battery state if it is known + switch (battery.state) { + case LI_BATTERY_STATE_CHARGING: + case LI_BATTERY_STATE_DISCHARGING: + if (battery.state == LI_BATTERY_STATE_CHARGING) { + report.bBatteryLvlSpecial |= 0x10; // Connected via USB + } + else { + report.bBatteryLvlSpecial &= ~0x10; // Not connected via USB + } + + // If there was a special battery status set before, clear that and + // initialize the battery level to 50%. It will be overwritten below + // if the actual percentage is known. + if ((report.bBatteryLvlSpecial & 0xF) > 0xA) { + report.bBatteryLvlSpecial = (report.bBatteryLvlSpecial & ~0xF) | 0x5; + } + break; + + case LI_BATTERY_STATE_FULL: + report.bBatteryLvlSpecial = 0x1B; // USB + Battery Full + report.bBatteryLvl = 0xFF; + break; + + case LI_BATTERY_STATE_NOT_PRESENT: + case LI_BATTERY_STATE_NOT_CHARGING: + report.bBatteryLvlSpecial = 0x1F; // USB + Charging Error + break; + + default: + break; + } + + // Update the battery level if it is known + if (battery.percentage != LI_BATTERY_PERCENTAGE_UNKNOWN) { + report.bBatteryLvl = battery.percentage * 255 / 100; + + // Don't overwrite low nibble if there's a special status there (see above) + if ((report.bBatteryLvlSpecial & 0x10) && (report.bBatteryLvlSpecial & 0xF) <= 0xA) { + report.bBatteryLvlSpecial = (report.bBatteryLvlSpecial & ~0xF) | ((battery.percentage + 5) / 10); + } + } + + auto status = vigem_target_ds4_update_ex(vigem->client.get(), gamepad.gp.get(), gamepad.report.ds4); + if (!VIGEM_SUCCESS(status)) { + BOOST_LOG(warning) << "Couldn't send gamepad battery input to ViGEm ["sv << util::hex(status).to_string_view() << ']'; + } } void @@ -902,6 +1102,13 @@ namespace platf { */ platform_caps::caps_t get_capabilities() { - return 0; + platform_caps::caps_t caps = 0; + + // We support controller touchpad input as long as we're not emulating X360 + if (config::input.gamepad != "x360"sv) { + caps |= platform_caps::controller_touch; + } + + return caps; } } // namespace platf From 8a7d5c77e2e1e1bf92f65b35c9e6310733968c9f Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 8 Jul 2023 23:14:35 -0500 Subject: [PATCH 5/7] Remove unused variable --- src/platform/windows/input.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 6355442e8ca..633ad5736e8 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -470,28 +470,22 @@ namespace platf { i.type = INPUT_MOUSE; auto &mi = i.mi; - int mouse_button; if (button == 1) { mi.dwFlags = release ? MOUSEEVENTF_LEFTUP : MOUSEEVENTF_LEFTDOWN; - mouse_button = VK_LBUTTON; } else if (button == 2) { mi.dwFlags = release ? MOUSEEVENTF_MIDDLEUP : MOUSEEVENTF_MIDDLEDOWN; - mouse_button = VK_MBUTTON; } else if (button == 3) { mi.dwFlags = release ? MOUSEEVENTF_RIGHTUP : MOUSEEVENTF_RIGHTDOWN; - mouse_button = VK_RBUTTON; } else if (button == 4) { mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.mouseData = XBUTTON1; - mouse_button = VK_XBUTTON1; } else { mi.dwFlags = release ? MOUSEEVENTF_XUP : MOUSEEVENTF_XDOWN; mi.mouseData = XBUTTON2; - mouse_button = VK_XBUTTON2; } send_input(i); From fb70f410f453f6f7f00397b67e23a293c1359727 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 9 Jul 2023 15:52:51 -0500 Subject: [PATCH 6/7] Rework active gamepad mask handling to be multi-channel-safe We may get activeGamepadMask changes out of order between different controller channels, so only check the bit of the mask corresponding to the controller number specified in the event itself. This also fixes the "ControllerNumber not allocated" warning each time a gamepad is removed. --- src/input.cpp | 113 ++++++++++++++++---------------------------------- 1 file changed, 35 insertions(+), 78 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index b404516b49f..7c9b867f5fb 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -142,7 +142,6 @@ namespace input { safe::mail_raw_t::event_t touch_port_event, platf::feedback_queue_t feedback_queue): shortcutFlags {}, - active_gamepad_state {}, gamepads(MAX_GAMEPADS), touch_port_event { std::move(touch_port_event) }, feedback_queue { std::move(feedback_queue) }, @@ -152,7 +151,6 @@ namespace input { // Keep track of alt+ctrl+shift key combo int shortcutFlags; - std::uint16_t active_gamepad_state; std::vector gamepads; safe::mail_raw_t::event_t touch_port_event; @@ -702,48 +700,6 @@ namespace input { platf::unicode(platf_input, packet->text, size); } - int - updateGamepads(std::vector &gamepads, std::int16_t old_state, std::int16_t new_state, const platf::feedback_queue_t &feedback_queue) { - 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) { - auto &gamepad = gamepads[x]; - - if ((old_state >> x) & 1) { - if (gamepad.id < 0) { - return -1; - } - - free_gamepad(platf_input, gamepad.id); - gamepad.id = -1; - } - else { - auto id = alloc_id(gamepadMask); - - if (id < 0) { - // Out of gamepads - return -1; - } - - if (platf::alloc_gamepad(platf_input, id, {}, feedback_queue)) { - 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; - } - /** * @brief Called to pass a controller arrival message to the platform backend. * @param input The input context pointer. @@ -755,13 +711,13 @@ namespace input { return; } - if (packet->controllerNumber >= gamepadMask.size()) { - // Invalid controller number + if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) { + BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']'; return; } - if (gamepadMask[packet->controllerNumber]) { - // There's already a gamepad in this slot + if (input->gamepads[packet->controllerNumber].id >= 0) { + BOOST_LOG(warning) << "ControllerNumber already allocated ["sv << packet->controllerNumber << ']'; return; } @@ -772,16 +728,18 @@ namespace input { util::endian::little(packet->supportedButtonFlags), }; - gamepadMask[packet->controllerNumber] = true; - input->active_gamepad_state |= (1 << packet->controllerNumber); + auto id = alloc_id(gamepadMask); + if (id < 0) { + return; + } // Allocate a new gamepad - if (platf::alloc_gamepad(platf_input, packet->controllerNumber, arrival, input->feedback_queue)) { - free_id(gamepadMask, packet->controllerNumber); + if (platf::alloc_gamepad(platf_input, id, arrival, input->feedback_queue)) { + free_id(gamepadMask, id); return; } - input->gamepads[packet->controllerNumber].id = packet->controllerNumber; + input->gamepads[packet->controllerNumber].id = id; } /** @@ -800,13 +758,9 @@ namespace input { return; } - 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 (gamepad.id < 0) { + BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv; return; } @@ -838,13 +792,9 @@ namespace input { return; } - 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 (gamepad.id < 0) { + BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv; return; } @@ -875,13 +825,9 @@ namespace input { return; } - 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 (gamepad.id < 0) { + BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv; return; } @@ -900,29 +846,40 @@ namespace input { return; } - if (updateGamepads(input->gamepads, input->active_gamepad_state, packet->activeGamepadMask, input->feedback_queue)) { - return; - } - - input->active_gamepad_state = packet->activeGamepadMask; - if (packet->controllerNumber < 0 || packet->controllerNumber >= input->gamepads.size()) { BOOST_LOG(warning) << "ControllerNumber out of range ["sv << packet->controllerNumber << ']'; return; } - if (!((input->active_gamepad_state >> packet->controllerNumber) & 1)) { - BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv; + auto &gamepad = input->gamepads[packet->controllerNumber]; + // If this is an event for a new gamepad, create the gamepad now. Ideally, the client would + // send a controller arrival instead of this but it's still supported for legacy clients. + if ((packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id < 0) { + auto id = alloc_id(gamepadMask); + if (id < 0) { + return; + } + + if (platf::alloc_gamepad(platf_input, id, {}, input->feedback_queue)) { + free_id(gamepadMask, id); + return; + } + + gamepad.id = id; + } + else if (!(packet->activeGamepadMask & (1 << packet->controllerNumber)) && gamepad.id >= 0) { + // If this is the final event for a gamepad being removed, free the gamepad and return. + free_gamepad(platf_input, gamepad.id); + gamepad.id = -1; 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) { + BOOST_LOG(warning) << "ControllerNumber ["sv << packet->controllerNumber << "] not allocated"sv; return; } From cb7c3a70dba7d7cca97ffc2a07b4702e3f5e2d8b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Mon, 10 Jul 2023 19:43:09 -0500 Subject: [PATCH 7/7] 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. --- src/input.cpp | 11 ++++---- src/platform/common.h | 22 +++++++++++---- src/platform/linux/input.cpp | 22 +++++++-------- src/platform/macos/input.cpp | 4 +-- src/platform/windows/input.cpp | 51 ++++++++++++++++++++-------------- 5 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/input.cpp b/src/input.cpp index 7c9b867f5fb..6f66f02c370 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -722,7 +722,6 @@ namespace input { } platf::gamepad_arrival_t arrival { - packet->controllerNumber, packet->type, util::endian::little(packet->capabilities), util::endian::little(packet->supportedButtonFlags), @@ -734,7 +733,7 @@ namespace input { } // 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); return; } @@ -765,7 +764,7 @@ namespace input { } platf::gamepad_touch_t touch { - packet->controllerNumber, + { gamepad.id, packet->controllerNumber }, packet->eventType, util::endian::little(packet->pointerId), from_netfloat(packet->x), @@ -799,7 +798,7 @@ namespace input { } platf::gamepad_motion_t motion { - packet->controllerNumber, + { gamepad.id, packet->controllerNumber }, packet->motionType, from_netfloat(packet->x), from_netfloat(packet->y), @@ -832,7 +831,7 @@ namespace input { } platf::gamepad_battery_t battery { - packet->controllerNumber, + { gamepad.id, packet->controllerNumber }, packet->batteryState, packet->batteryPercentage }; @@ -862,7 +861,7 @@ namespace input { 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); return; } diff --git a/src/platform/common.h b/src/platform/common.h index eb1b45fd1eb..1173afd5ad5 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -236,15 +236,25 @@ namespace platf { 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 { - std::uint8_t gamepadNumber; std::uint8_t type; std::uint16_t capabilities; std::uint32_t supportedButtons; }; struct gamepad_touch_t { - std::uint8_t gamepadNumber; + gamepad_id_t id; std::uint8_t eventType; std::uint32_t pointerId; float x; @@ -253,7 +263,7 @@ namespace platf { }; struct gamepad_motion_t { - std::uint8_t gamepadNumber; + gamepad_id_t id; std::uint8_t motionType; // Accel: m/s^2 @@ -264,7 +274,7 @@ namespace platf { }; struct gamepad_battery_t { - std::uint8_t gamepadNumber; + gamepad_id_t id; std::uint8_t state; std::uint8_t percentage; }; @@ -581,13 +591,13 @@ namespace platf { /** * @brief Creates a new virtual gamepad. * @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 feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ 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 free_gamepad(input_t &input, int nr); diff --git a/src/platform/linux/input.cpp b/src/platform/linux/input.cpp index c427013f2b2..a14a63c99cc 100644 --- a/src/platform/linux/input.cpp +++ b/src/platform/linux/input.cpp @@ -452,7 +452,7 @@ namespace platf { public: 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 {} {} class data_t { @@ -628,8 +628,8 @@ namespace platf { BOOST_LOG(debug) << "Removed rumble effect id ["sv << id << ']'; } - // Used as ID for rumble notifications - int gamepadnr; + // Client-relative gamepad index for rumble notifications + std::uint8_t gamepadnr; // Used as ID for adding/removinf devices from evdev notifications uinput_t::pointer dev; @@ -772,14 +772,14 @@ namespace platf { /** * @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 feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(int nr, const gamepad_arrival_t &metadata, feedback_queue_t &&feedback_queue) { - TUPLE_2D_REF(input, gamepad_state, gamepads[nr]); + alloc_gamepad(const gamepad_id_t &id, const gamepad_arrival_t &metadata, feedback_queue_t &&feedback_queue) { + TUPLE_2D_REF(input, gamepad_state, gamepads[id.globalIndex]); int err = libevdev_uinput_create_from_device(gamepad_dev.get(), LIBEVDEV_UINPUT_OPEN_MANAGED, &input); @@ -791,7 +791,7 @@ namespace platf { } std::stringstream ss; - ss << "sunshine_gamepad_"sv << nr; + ss << "sunshine_gamepad_"sv << id.globalIndex; auto gamepad_path = platf::appdata() / ss.str(); if (std::filesystem::is_symlink(gamepad_path)) { @@ -801,7 +801,7 @@ namespace platf { auto dev_node = libevdev_uinput_get_devnode(input.get()); rumble_ctx->rumble_queue_queue.raise( - nr, + id.clientRelativeIndex, input.get(), std::move(feedback_queue), pollfd_t { @@ -1490,14 +1490,14 @@ namespace platf { /** * @brief Creates a new virtual gamepad. * @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 feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ int - alloc_gamepad(input_t &input, int nr, const gamepad_arrival_t &metadata, feedback_queue_t feedback_queue) { - return ((input_raw_t *) input.get())->alloc_gamepad(nr, metadata, std::move(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(id, metadata, std::move(feedback_queue)); } void diff --git a/src/platform/macos/input.cpp b/src/platform/macos/input.cpp index 17073479999..ddb45761c33 100644 --- a/src/platform/macos/input.cpp +++ b/src/platform/macos/input.cpp @@ -291,13 +291,13 @@ const KeyCodeMap kKeyCodesMap[] = { /** * @brief Creates a new virtual gamepad. * @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 feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ 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; return -1; } diff --git a/src/platform/windows/input.cpp b/src/platform/windows/input.cpp index 633ad5736e8..f64855120a3 100644 --- a/src/platform/windows/input.cpp +++ b/src/platform/windows/input.cpp @@ -61,6 +61,8 @@ namespace platf { std::map pointer_id_map; uint8_t available_pointers; + uint8_t client_relative_index; + gamepad_feedback_msg_t last_rumble; gamepad_feedback_msg_t last_rgb_led; }; @@ -193,16 +195,18 @@ namespace platf { /** * @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 gp_type The type of gamepad. * @return 0 on success. */ int - alloc_gamepad_internal(int nr, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) { - auto &gamepad = gamepads[nr]; + alloc_gamepad_internal(const gamepad_id_t &id, feedback_queue_t &feedback_queue, VIGEM_TARGET_TYPE gp_type) { + auto &gamepad = gamepads[id.globalIndex]; assert(!gamepad.gp); + gamepad.client_relative_index = id.clientRelativeIndex; + if (gp_type == Xbox360Wired) { gamepad.gp.reset(vigem_target_x360_alloc()); 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); // 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(nr, LI_MOTION_TYPE_GYRO, 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(gamepad.client_relative_index, LI_MOTION_TYPE_GYRO, 100)); // We support pointer index 0 and 1 gamepad.available_pointers = 0x3; @@ -285,7 +289,9 @@ namespace platf { // Don't resend duplicate rumble data if (normalizedSmallMotor != gamepad.last_rumble.data.rumble.highfreq || 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.last_rumble = msg; } @@ -308,8 +314,11 @@ namespace platf { if (gamepad.gp.get() == target) { // 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) { - gamepad_feedback_msg_t msg = gamepad_feedback_msg_t::make_rgb_led(x, r, g, b); + 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) { + // 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.last_rgb_led = msg; } @@ -603,13 +612,13 @@ namespace platf { /** * @brief Creates a new virtual gamepad. * @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 feedback_queue The queue for posting messages back to the client. * @return 0 on success. */ 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(); if (!raw->vigem) { @@ -619,35 +628,35 @@ namespace platf { VIGEM_TARGET_TYPE selectedGamepadType; 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; } 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; } 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; } 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; } 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; } 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; } 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; } - return raw->vigem->alloc_gamepad_internal(nr, feedback_queue, selectedGamepadType); + return raw->vigem->alloc_gamepad_internal(id, feedback_queue, selectedGamepadType); } void @@ -873,7 +882,7 @@ namespace platf { return; } - auto &gamepad = vigem->gamepads[touch.gamepadNumber]; + auto &gamepad = vigem->gamepads[touch.id.globalIndex]; if (!gamepad.gp) { return; } @@ -973,7 +982,7 @@ namespace platf { return; } - auto &gamepad = vigem->gamepads[motion.gamepadNumber]; + auto &gamepad = vigem->gamepads[motion.id.globalIndex]; if (!gamepad.gp) { return; } @@ -1005,7 +1014,7 @@ namespace platf { return; } - auto &gamepad = vigem->gamepads[battery.gamepadNumber]; + auto &gamepad = vigem->gamepads[battery.id.globalIndex]; if (!gamepad.gp) { return; }