From 572b54b94df3b9538ac2c73ef56da367262b5db1 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Tue, 18 Mar 2025 20:16:37 +0000 Subject: [PATCH 1/6] feat: exposing adaptive trigger effects --- include/inputtino/input.hpp | 14 +++++++++ src/uhid/include/uhid/protected_types.hpp | 2 ++ src/uhid/include/uhid/ps5.hpp | 13 +++++--- src/uhid/joypad_ps5.cpp | 25 +++++++++++++-- tests/testJoypads.cpp | 38 ++++++++++++++++++++--- 5 files changed, 81 insertions(+), 11 deletions(-) diff --git a/include/inputtino/input.hpp b/include/inputtino/input.hpp index 7b399b6..d2debfe 100644 --- a/include/inputtino/input.hpp +++ b/include/inputtino/input.hpp @@ -428,6 +428,20 @@ class PS5Joypad : public Joypad { void set_on_led(const std::function &callback); + /** + * This is an opaque blob that is sent to the controller. + * There is some reversed engineered information here: + * https://gist.github.com/Nielk1/6d54cc2c00d2201ccb8c2720ad7538db + */ + struct TriggerEffect { + uint8_t type_left; + uint8_t type_right; + std::array left; + std::array right; + }; + + void set_on_trigger_effect(const std::function &callback); + protected: typedef struct PS5JoypadState PS5JoypadState; std::shared_ptr _state; diff --git a/src/uhid/include/uhid/protected_types.hpp b/src/uhid/include/uhid/protected_types.hpp index 02ad79d..f90ec58 100644 --- a/src/uhid/include/uhid/protected_types.hpp +++ b/src/uhid/include/uhid/protected_types.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include #include @@ -25,6 +26,7 @@ struct PS5JoypadState { std::optional> on_rumble = std::nullopt; std::optional> on_led = std::nullopt; + std::optional> on_trigger_effect = std::nullopt; bool stop_repeat_thread = false; bool is_bluetooth = true; }; diff --git a/src/uhid/include/uhid/ps5.hpp b/src/uhid/include/uhid/ps5.hpp index cd65061..dd2561c 100644 --- a/src/uhid/include/uhid/ps5.hpp +++ b/src/uhid/include/uhid/ps5.hpp @@ -22,7 +22,7 @@ static constexpr int PS5_GYRO_RES_PER_DEG_S = 1024; static constexpr int PS5_GYRO_RANGE = (2048 * PS5_GYRO_RES_PER_DEG_S); static constexpr int PS5_TOUCHPAD_WIDTH = 1920; static constexpr int PS5_TOUCHPAD_HEIGHT = 1080; -static constexpr float SDL_STANDARD_GRAVITY = 9.80665f; +static constexpr float SDL_STANDARD_GRAVITY_CONST = 9.80665f; /* * Bluetooth constants, @@ -472,8 +472,9 @@ struct dualsense_input_report { enum FLAG0 : uint8_t { MOTOR_OR_COMPATIBLE_VIBRATION = 0x01, - LED_OR_HAPTIC_SELECT = 0x02, - LED_BLINK = 0x04 + USE_RUMBLE_NOT_HAPTICS = 0x02, + RIGHT_TRIGGER_EFFECT = 0x04, + LEFT_TRIGGER_EFFECT = 0x08, }; enum FLAG1 : uint8_t { @@ -503,7 +504,11 @@ struct dualsense_output_report_common { uint8_t mute_button_led; uint8_t power_save_control; - uint8_t reserved2[28]; + uint8_t right_trigger_effect_type; + uint8_t right_trigger_effect[10]; + uint8_t left_trigger_effect_type; + uint8_t left_trigger_effect[10]; + uint8_t reserved2[6]; /* LEDs and lightbar */ uint8_t valid_flag2; diff --git a/src/uhid/joypad_ps5.cpp b/src/uhid/joypad_ps5.cpp index fe7acb0..f6b4b34 100644 --- a/src/uhid/joypad_ps5.cpp +++ b/src/uhid/joypad_ps5.cpp @@ -168,6 +168,21 @@ static void on_uhid_event(std::shared_ptr state, uhid_event ev, } } + /** + * Trigger effects + */ + if (report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT || report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT) { + if (state->on_trigger_effect) { + PS5Joypad::TriggerEffect effect = {.type_left = report.left_trigger_effect_type, + .type_right = report.right_trigger_effect_type, + .left = {}, + .right = {}}; + std::copy(&report.left_trigger_effect[0], &report.left_trigger_effect[10], &effect.left[0]); + std::copy(&report.right_trigger_effect[0], &report.right_trigger_effect[10], &effect.right[0]); + (*state->on_trigger_effect)(effect); + } + } + /* * LED */ @@ -472,9 +487,9 @@ static __le16 to_le_signed(float original, float value) { void PS5Joypad::set_motion(PS5Joypad::MOTION_TYPE type, float x, float y, float z) { switch (type) { case ACCELERATION: { - this->_state->current_state.accel[0] = to_le_signed(x, (x * uhid::SDL_STANDARD_GRAVITY * 100)); - this->_state->current_state.accel[1] = to_le_signed(y, (y * uhid::SDL_STANDARD_GRAVITY * 100)); - this->_state->current_state.accel[2] = to_le_signed(z, (z * uhid::SDL_STANDARD_GRAVITY * 100)); + this->_state->current_state.accel[0] = to_le_signed(x, (x * uhid::SDL_STANDARD_GRAVITY_CONST * 100)); + this->_state->current_state.accel[1] = to_le_signed(y, (y * uhid::SDL_STANDARD_GRAVITY_CONST * 100)); + this->_state->current_state.accel[2] = to_le_signed(z, (z * uhid::SDL_STANDARD_GRAVITY_CONST * 100)); send_report(*this->_state); break; @@ -504,6 +519,10 @@ void PS5Joypad::set_on_led(const std::function &callback) { this->_state->on_led = callback; } +void PS5Joypad::set_on_trigger_effect(const std::function &callback) { + this->_state->on_trigger_effect = callback; +} + void PS5Joypad::place_finger(int finger_nr, uint16_t x, uint16_t y) { if (finger_nr <= 1) { // If this finger was previously unpressed, we should increase the touch id diff --git a/tests/testJoypads.cpp b/tests/testJoypads.cpp index 89a7b31..21e6427 100644 --- a/tests/testJoypads.cpp +++ b/tests/testJoypads.cpp @@ -6,6 +6,7 @@ #include #include #include +#include using Catch::Matchers::Contains; using Catch::Matchers::ContainsSubstring; @@ -402,6 +403,39 @@ TEST_CASE_METHOD(SDLTestsFixture, "PS Joypad", "[SDL],[PS]") { } } + // Adaptive triggers aren't directly supported by SDL + // see:https://github.com/libsdl-org/SDL/issues/5125#issuecomment-1204261666 + // see: HIDAPI_DriverPS5_RumbleJoystickTriggers() + // but we can send custom data to the device, the following code is adapted from + // https://github.com/libsdl-org/SDL/blob/d66483dfccfcdc4e03f719e318c7a76f963f22d9/test/testcontroller.c#L235-L255 + { + auto trigger_event = std::make_shared(); + joypad.set_on_trigger_effect([trigger_event](const PS5Joypad::TriggerEffect &effect) { *trigger_event = effect; }); + + /* Resistance and vibration when trigger is pulled */ + uint8_t left_effect_type = 0x06; + uint8_t left_effect[10] = {15, 63, 128, 0, 0, 0, 0, 0, 0, 0}; + /* Constant resistance across entire trigger pull */ + uint8_t right_effect_type = 0x01; + uint8_t right_effect[10] = {0, 110, 0, 0, 0, 0, 0, 0, 0, 0}; + + uhid::dualsense_output_report_common state = {}; + SDL_zero(state); + state.valid_flag0 |= (uhid::RIGHT_TRIGGER_EFFECT | uhid::LEFT_TRIGGER_EFFECT); + state.right_trigger_effect_type = right_effect_type; + SDL_memcpy(state.right_trigger_effect, right_effect, sizeof(right_effect)); + state.left_trigger_effect_type = left_effect_type; + SDL_memcpy(state.left_trigger_effect, left_effect, sizeof(left_effect)); + SDL_GameControllerSendEffect(gc, &state, sizeof(state)); + + std::this_thread::sleep_for(15ms); + flush_sdl_events(); + REQUIRE(trigger_event->type_left == left_effect_type); + REQUIRE(trigger_event->type_right == right_effect_type); + REQUIRE(std::equal(std::begin(trigger_event->left), std::end(trigger_event->left), std::begin(left_effect))); + REQUIRE(std::equal(std::begin(trigger_event->right), std::end(trigger_event->right), std::begin(right_effect))); + } + { // Test creating a second device REQUIRE(SDL_NumJoysticks() == 1); auto joypad2 = std::move(*PS5Joypad::create()); @@ -419,10 +453,6 @@ TEST_CASE_METHOD(SDLTestsFixture, "PS Joypad", "[SDL],[PS]") { SDL_GameControllerClose(gc2); } - // Adaptive triggers aren't supported by SDL - // see:https://github.com/libsdl-org/SDL/issues/5125#issuecomment-1204261666 - // see: HIDAPI_DriverPS5_RumbleJoystickTriggers() - SDL_GameControllerClose(gc); } From 830587a1919c9d1bf761ee170cbf9cd2d96f1a64 Mon Sep 17 00:00:00 2001 From: Hans Gaiser Date: Wed, 19 Mar 2025 23:34:39 +0100 Subject: [PATCH 2/6] Add C and Rust binding for DualSense trigger effects. --- bindings/rust/inputtino/src/joypad_ps5.rs | 52 ++++++++++++++++++++--- include/inputtino/input.h | 5 +++ src/c-bindings/joypad_ps5.cpp | 9 ++++ tests/testCAPI.cpp | 1 + 4 files changed, 62 insertions(+), 5 deletions(-) diff --git a/bindings/rust/inputtino/src/joypad_ps5.rs b/bindings/rust/inputtino/src/joypad_ps5.rs index 73b70e5..9b173d0 100644 --- a/bindings/rust/inputtino/src/joypad_ps5.rs +++ b/bindings/rust/inputtino/src/joypad_ps5.rs @@ -8,7 +8,7 @@ use crate::sys::{ inputtino_joypad_ps5_set_on_led, inputtino_joypad_ps5_set_on_rumble, inputtino_joypad_ps5_set_pressed_buttons, inputtino_joypad_ps5_set_stick, inputtino_joypad_ps5_set_triggers, inputtino_joypad_ps5_set_motion, - inputtino_joypad_ps5_set_battery, + inputtino_joypad_ps5_set_battery, inputtino_joypad_ps5_set_on_trigger_effect, }; use crate::{BatteryState, InputtinoError, JoypadMotionType, JoypadStickPosition}; @@ -17,6 +17,7 @@ pub struct PS5Joypad { joypad: *mut crate::sys::InputtinoPS5Joypad, on_rumble_fn: *mut c_void, on_led_fn: *mut c_void, + on_trigger_effect_fn: *mut c_void, } impl PS5Joypad { @@ -44,6 +45,7 @@ impl PS5Joypad { joypad, on_rumble_fn: std::ptr::null_mut(), on_led_fn: std::ptr::null_mut(), + on_trigger_effect_fn: std::ptr::null_mut(), }) } @@ -130,6 +132,29 @@ impl PS5Joypad { } } + /// Sets a callback to be called when this device receives a trigger effect event. + /// + /// # Examples + /// + /// ```ignore + /// device.set_on_trigger_effect(|type_left, type_right, left_effect, right_effect| { + /// println!( + /// "Received trigger effect event: type_left: {type_left}, type_right: {type_right}, \ + /// left_effect: {:?}, right_effect: {:?}", + /// left_effect, right_effect + /// ); + /// }); + /// ``` + pub fn set_on_trigger_effect(&mut self, on_trigger_effect_fn: impl FnMut(u8, u8, &[u8], &[u8]) + 'static) { + let on_trigger_effect_fn = Box::new(TriggerEffectFunction { + on_trigger_effect_fn: Box::new(on_trigger_effect_fn), + }); + self.on_trigger_effect_fn = Box::into_raw(on_trigger_effect_fn) as *mut c_void; + unsafe { + inputtino_joypad_ps5_set_on_trigger_effect(self.joypad, Some(on_trigger_effect_c_fn), self.on_trigger_effect_fn); + } + } + pub fn get_nodes(&self) -> Result, InputtinoError> { get_nodes(inputtino_joypad_ps5_get_nodes, self.joypad) } @@ -205,10 +230,6 @@ struct RumbleFunction { on_rumble_fn: Box, } -struct LedFunction { - on_led_fn: Box, -} - unsafe extern "C" fn on_rumble_c_fn( left_motor: c_int, right_motor: c_int, @@ -218,6 +239,10 @@ unsafe extern "C" fn on_rumble_c_fn( ((*on_rumble_fn).on_rumble_fn)(left_motor, right_motor); } +struct LedFunction { + on_led_fn: Box, +} + unsafe extern "C" fn on_led_c_fn( r: c_int, g: c_int, @@ -228,4 +253,21 @@ unsafe extern "C" fn on_led_c_fn( ((*on_led_fn).on_led_fn)(r, g, b); } +struct TriggerEffectFunction { + on_trigger_effect_fn: Box, +} + +unsafe extern "C" fn on_trigger_effect_c_fn( + type_left: u8, + type_right: u8, + left: *const u8, + right: *const u8, + user_data: *mut ::core::ffi::c_void, +) { + let on_trigger_effect_fn = user_data as *mut TriggerEffectFunction; + let left_effect = std::slice::from_raw_parts(left, 10); + let right_effect = std::slice::from_raw_parts(right, 10); + ((*on_trigger_effect_fn).on_trigger_effect_fn)(type_left, type_right, left_effect, right_effect); +} + unsafe impl Send for PS5Joypad {} diff --git a/include/inputtino/input.h b/include/inputtino/input.h index 2d704e5..bd4e509 100644 --- a/include/inputtino/input.h +++ b/include/inputtino/input.h @@ -10,6 +10,7 @@ # include #endif #include +#include #ifdef __cplusplus extern "C" { @@ -309,6 +310,10 @@ typedef void (*InputtinoJoypadLEDFn)(int r, int g, int b, void *user_data); LIBINPUTTINO_EXPORT void inputtino_joypad_ps5_set_on_led(InputtinoPS5Joypad *joypad, InputtinoJoypadLEDFn led_fn, void *user_data); +typedef void (*InputtinoJoypadTriggerFn)(uint8_t type_left, uint8_t type_right, const uint8_t *left, const uint8_t *right, void *user_data); + +LIBINPUTTINO_EXPORT void inputtino_joypad_ps5_set_on_trigger_effect(InputtinoPS5Joypad *joypad, InputtinoJoypadTriggerFn trigger_effect_fn, void *user_data); + LIBINPUTTINO_EXPORT void inputtino_joypad_ps5_destroy(InputtinoPS5Joypad *joypad); #ifdef __cplusplus diff --git a/src/c-bindings/joypad_ps5.cpp b/src/c-bindings/joypad_ps5.cpp index 3e7e196..b37ee7a 100644 --- a/src/c-bindings/joypad_ps5.cpp +++ b/src/c-bindings/joypad_ps5.cpp @@ -91,6 +91,15 @@ void inputtino_joypad_ps5_set_on_led(InputtinoPS5Joypad *joypad, InputtinoJoypad } } +void inputtino_joypad_ps5_set_on_trigger_effect(InputtinoPS5Joypad *joypad, InputtinoJoypadTriggerFn trigger_effect_fn, void *user_data) { + if (joypad) { + reinterpret_cast(joypad)->set_on_trigger_effect( + [user_data, trigger_effect_fn](const inputtino::PS5Joypad::TriggerEffect &effect) { + trigger_effect_fn(effect.type_left, effect.type_right, effect.left.data(), effect.right.data(), user_data); + }); + } +} + void inputtino_joypad_ps5_destroy(InputtinoPS5Joypad *joypad) { if (joypad) { auto joypad_ptr = reinterpret_cast(joypad); diff --git a/tests/testCAPI.cpp b/tests/testCAPI.cpp index 477b206..e1e81a6 100644 --- a/tests/testCAPI.cpp +++ b/tests/testCAPI.cpp @@ -204,6 +204,7 @@ TEST_CASE("C PS5 API", "[C-API]") { inputtino_joypad_ps5_set_motion(ps_pad, INPUTTINO_JOYPAD_MOTION_TYPE::ACCELERATION, 1, 1, 1); inputtino_joypad_ps5_set_battery(ps_pad, BATTERY_STATE::BATTERY_DISCHARGING, 90); inputtino_joypad_ps5_set_on_led(ps_pad, [](int r, int g, int b, void *user_data) {}, nullptr); + inputtino_joypad_ps5_set_on_trigger_effect(ps_pad, [](uint8_t type_left, uint8_t type_right, const uint8_t *left, const uint8_t *right, void *user_data) {}, nullptr); } delete[] nodes; From c102abfbe97e8fe5503e5424c6f8e555577c94d5 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 20 Mar 2025 08:04:12 +0000 Subject: [PATCH 3/6] fix: encode when only one of the two triggers needs updating --- src/uhid/joypad_ps5.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/uhid/joypad_ps5.cpp b/src/uhid/joypad_ps5.cpp index f6b4b34..0a1efb1 100644 --- a/src/uhid/joypad_ps5.cpp +++ b/src/uhid/joypad_ps5.cpp @@ -173,8 +173,12 @@ static void on_uhid_event(std::shared_ptr state, uhid_event ev, */ if (report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT || report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT) { if (state->on_trigger_effect) { - PS5Joypad::TriggerEffect effect = {.type_left = report.left_trigger_effect_type, - .type_right = report.right_trigger_effect_type, + // We encode 0xFF as no effect, client side should be able to ignore a specific trigger this way + uint8_t left_type = report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT ? report.left_trigger_effect_type : 0xFF; + uint8_t right_type = report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT ? report.right_trigger_effect_type : 0xFF; + + PS5Joypad::TriggerEffect effect = {.type_left = left_type, + .type_right = right_type, .left = {}, .right = {}}; std::copy(&report.left_trigger_effect[0], &report.left_trigger_effect[10], &effect.left[0]); From c0ebf6734bb2379e3e6e13d22aa1c3eadb7dbb45 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 20 Mar 2025 16:09:28 +0000 Subject: [PATCH 4/6] fix: added event_flags and caching of the trigger status --- include/inputtino/input.h | 3 +- include/inputtino/input.hpp | 9 ++++-- src/c-bindings/joypad_ps5.cpp | 11 +++++-- src/uhid/include/uhid/protected_types.hpp | 3 ++ src/uhid/joypad_ps5.cpp | 38 ++++++++++++++++------- tests/testCAPI.cpp | 10 +++++- tests/testJoypads.cpp | 3 +- 7 files changed, 58 insertions(+), 19 deletions(-) diff --git a/include/inputtino/input.h b/include/inputtino/input.h index bd4e509..3ebf632 100644 --- a/include/inputtino/input.h +++ b/include/inputtino/input.h @@ -310,7 +310,8 @@ typedef void (*InputtinoJoypadLEDFn)(int r, int g, int b, void *user_data); LIBINPUTTINO_EXPORT void inputtino_joypad_ps5_set_on_led(InputtinoPS5Joypad *joypad, InputtinoJoypadLEDFn led_fn, void *user_data); -typedef void (*InputtinoJoypadTriggerFn)(uint8_t type_left, uint8_t type_right, const uint8_t *left, const uint8_t *right, void *user_data); +typedef void (*InputtinoJoypadTriggerFn)(uint8_t type_left, + uint8_t event_flags, uint8_t type_right, const uint8_t *left, const uint8_t *right, void *user_data); LIBINPUTTINO_EXPORT void inputtino_joypad_ps5_set_on_trigger_effect(InputtinoPS5Joypad *joypad, InputtinoJoypadTriggerFn trigger_effect_fn, void *user_data); diff --git a/include/inputtino/input.hpp b/include/inputtino/input.hpp index d2debfe..10a1ffc 100644 --- a/include/inputtino/input.hpp +++ b/include/inputtino/input.hpp @@ -434,10 +434,15 @@ class PS5Joypad : public Joypad { * https://gist.github.com/Nielk1/6d54cc2c00d2201ccb8c2720ad7538db */ struct TriggerEffect { + /** + * 0x04 - Right trigger + * 0x08 - Left trigger + */ + uint8_t event_flags; uint8_t type_left; uint8_t type_right; - std::array left; - std::array right; + std::array left = {}; + std::array right = {}; }; void set_on_trigger_effect(const std::function &callback); diff --git a/src/c-bindings/joypad_ps5.cpp b/src/c-bindings/joypad_ps5.cpp index b37ee7a..df74a54 100644 --- a/src/c-bindings/joypad_ps5.cpp +++ b/src/c-bindings/joypad_ps5.cpp @@ -91,11 +91,18 @@ void inputtino_joypad_ps5_set_on_led(InputtinoPS5Joypad *joypad, InputtinoJoypad } } -void inputtino_joypad_ps5_set_on_trigger_effect(InputtinoPS5Joypad *joypad, InputtinoJoypadTriggerFn trigger_effect_fn, void *user_data) { +void inputtino_joypad_ps5_set_on_trigger_effect(InputtinoPS5Joypad *joypad, + InputtinoJoypadTriggerFn trigger_effect_fn, + void *user_data) { if (joypad) { reinterpret_cast(joypad)->set_on_trigger_effect( [user_data, trigger_effect_fn](const inputtino::PS5Joypad::TriggerEffect &effect) { - trigger_effect_fn(effect.type_left, effect.type_right, effect.left.data(), effect.right.data(), user_data); + trigger_effect_fn(effect.event_flags, + effect.type_left, + effect.type_right, + effect.left.data(), + effect.right.data(), + user_data); }); } } diff --git a/src/uhid/include/uhid/protected_types.hpp b/src/uhid/include/uhid/protected_types.hpp index f90ec58..effe6d5 100644 --- a/src/uhid/include/uhid/protected_types.hpp +++ b/src/uhid/include/uhid/protected_types.hpp @@ -27,6 +27,9 @@ struct PS5JoypadState { std::optional> on_rumble = std::nullopt; std::optional> on_led = std::nullopt; std::optional> on_trigger_effect = std::nullopt; + uint32_t last_left_trigger_event = 0; + uint32_t last_right_trigger_event = 0; + bool stop_repeat_thread = false; bool is_bluetooth = true; }; diff --git a/src/uhid/joypad_ps5.cpp b/src/uhid/joypad_ps5.cpp index 0a1efb1..96a5a92 100644 --- a/src/uhid/joypad_ps5.cpp +++ b/src/uhid/joypad_ps5.cpp @@ -171,18 +171,32 @@ static void on_uhid_event(std::shared_ptr state, uhid_event ev, /** * Trigger effects */ - if (report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT || report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT) { - if (state->on_trigger_effect) { - // We encode 0xFF as no effect, client side should be able to ignore a specific trigger this way - uint8_t left_type = report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT ? report.left_trigger_effect_type : 0xFF; - uint8_t right_type = report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT ? report.right_trigger_effect_type : 0xFF; - - PS5Joypad::TriggerEffect effect = {.type_left = left_type, - .type_right = right_type, - .left = {}, - .right = {}}; - std::copy(&report.left_trigger_effect[0], &report.left_trigger_effect[10], &effect.left[0]); - std::copy(&report.right_trigger_effect[0], &report.right_trigger_effect[10], &effect.right[0]); + bool right_trigger = report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT; + bool left_trigger = report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT; + if ((right_trigger || left_trigger) && state->on_trigger_effect) { + auto left_array_start = std::begin(report.left_trigger_effect); + auto left_array_end = std::end(report.left_trigger_effect); + auto right_array_start = std::begin(report.right_trigger_effect); + auto right_array_end = std::end(report.right_trigger_effect); + // We have to cache these values because these flags will be set as long as the effect is active + uint32_t left_trigger_hash = std::accumulate(left_array_start, left_array_end, 0ul); + uint32_t right_trigger_hash = std::accumulate(right_array_start, right_array_end, 0ul); + if ((left_trigger && state->last_left_trigger_event != left_trigger_hash) || + (right_trigger && state->last_right_trigger_event != right_trigger_hash)) { + // First, update the cache + if (left_trigger) + state->last_left_trigger_event = left_trigger_hash; + if (right_trigger) + state->last_right_trigger_event = right_trigger_hash; + + // Then, trigger the event + uint8_t event_flags = (report.valid_flag0 & uhid::LEFT_TRIGGER_EFFECT) | + (report.valid_flag0 & uhid::RIGHT_TRIGGER_EFFECT); + PS5Joypad::TriggerEffect effect = {.event_flags = event_flags, + .type_left = report.left_trigger_effect_type, + .type_right = report.right_trigger_effect_type}; + std::copy(left_array_start, left_array_end, std::begin(effect.left)); + std::copy(right_array_start, right_array_end, std::begin(effect.right)); (*state->on_trigger_effect)(effect); } } diff --git a/tests/testCAPI.cpp b/tests/testCAPI.cpp index e1e81a6..eb7874e 100644 --- a/tests/testCAPI.cpp +++ b/tests/testCAPI.cpp @@ -204,7 +204,15 @@ TEST_CASE("C PS5 API", "[C-API]") { inputtino_joypad_ps5_set_motion(ps_pad, INPUTTINO_JOYPAD_MOTION_TYPE::ACCELERATION, 1, 1, 1); inputtino_joypad_ps5_set_battery(ps_pad, BATTERY_STATE::BATTERY_DISCHARGING, 90); inputtino_joypad_ps5_set_on_led(ps_pad, [](int r, int g, int b, void *user_data) {}, nullptr); - inputtino_joypad_ps5_set_on_trigger_effect(ps_pad, [](uint8_t type_left, uint8_t type_right, const uint8_t *left, const uint8_t *right, void *user_data) {}, nullptr); + inputtino_joypad_ps5_set_on_trigger_effect( + ps_pad, + [](uint8_t type_left, + uint8_t event_flags, + uint8_t type_right, + const uint8_t *left, + const uint8_t *right, + void *user_data) {}, + nullptr); } delete[] nodes; diff --git a/tests/testJoypads.cpp b/tests/testJoypads.cpp index 21e6427..57cf62b 100644 --- a/tests/testJoypads.cpp +++ b/tests/testJoypads.cpp @@ -421,7 +421,7 @@ TEST_CASE_METHOD(SDLTestsFixture, "PS Joypad", "[SDL],[PS]") { uhid::dualsense_output_report_common state = {}; SDL_zero(state); - state.valid_flag0 |= (uhid::RIGHT_TRIGGER_EFFECT | uhid::LEFT_TRIGGER_EFFECT); + state.valid_flag0 |= (uhid::RIGHT_TRIGGER_EFFECT); state.right_trigger_effect_type = right_effect_type; SDL_memcpy(state.right_trigger_effect, right_effect, sizeof(right_effect)); state.left_trigger_effect_type = left_effect_type; @@ -430,6 +430,7 @@ TEST_CASE_METHOD(SDLTestsFixture, "PS Joypad", "[SDL],[PS]") { std::this_thread::sleep_for(15ms); flush_sdl_events(); + REQUIRE(trigger_event->event_flags == uhid::RIGHT_TRIGGER_EFFECT); REQUIRE(trigger_event->type_left == left_effect_type); REQUIRE(trigger_event->type_right == right_effect_type); REQUIRE(std::equal(std::begin(trigger_event->left), std::end(trigger_event->left), std::begin(left_effect))); From e453ae2c8cf874bd25a4ec317ac038bd4946060a Mon Sep 17 00:00:00 2001 From: Hans Gaiser Date: Sat, 22 Mar 2025 23:59:41 +0100 Subject: [PATCH 5/6] Update C and Rust bindings for trigger effects. --- bindings/rust/inputtino/src/joypad_ps5.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/bindings/rust/inputtino/src/joypad_ps5.rs b/bindings/rust/inputtino/src/joypad_ps5.rs index 9b173d0..3f53b40 100644 --- a/bindings/rust/inputtino/src/joypad_ps5.rs +++ b/bindings/rust/inputtino/src/joypad_ps5.rs @@ -137,15 +137,15 @@ impl PS5Joypad { /// # Examples /// /// ```ignore - /// device.set_on_trigger_effect(|type_left, type_right, left_effect, right_effect| { + /// device.set_on_trigger_effect(|trigger_event_flags, type_left, type_right, left_effect, right_effect| { /// println!( - /// "Received trigger effect event: type_left: {type_left}, type_right: {type_right}, \ + /// "Received trigger effect event: trigger_event_flags: {trigger_event_flags}, type_left: {type_left}, type_right: {type_right}, \ /// left_effect: {:?}, right_effect: {:?}", /// left_effect, right_effect /// ); /// }); /// ``` - pub fn set_on_trigger_effect(&mut self, on_trigger_effect_fn: impl FnMut(u8, u8, &[u8], &[u8]) + 'static) { + pub fn set_on_trigger_effect(&mut self, on_trigger_effect_fn: impl FnMut(u8, u8, u8, &[u8], &[u8]) + 'static) { let on_trigger_effect_fn = Box::new(TriggerEffectFunction { on_trigger_effect_fn: Box::new(on_trigger_effect_fn), }); @@ -254,10 +254,11 @@ unsafe extern "C" fn on_led_c_fn( } struct TriggerEffectFunction { - on_trigger_effect_fn: Box, + on_trigger_effect_fn: Box, } unsafe extern "C" fn on_trigger_effect_c_fn( + trigger_event_flags: u8, type_left: u8, type_right: u8, left: *const u8, @@ -267,7 +268,13 @@ unsafe extern "C" fn on_trigger_effect_c_fn( let on_trigger_effect_fn = user_data as *mut TriggerEffectFunction; let left_effect = std::slice::from_raw_parts(left, 10); let right_effect = std::slice::from_raw_parts(right, 10); - ((*on_trigger_effect_fn).on_trigger_effect_fn)(type_left, type_right, left_effect, right_effect); + ((*on_trigger_effect_fn).on_trigger_effect_fn)( + trigger_event_flags, + type_left, + type_right, + left_effect, + right_effect, + ); } unsafe impl Send for PS5Joypad {} From 247699044d63327379657e463cf7a25174d86420 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Mon, 24 Mar 2025 20:15:13 +0000 Subject: [PATCH 6/6] feat: updated README --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8d3c466..3471cf2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # inputtino An easy to use virtual input library for Linux built on top of `uinput`, `evdev` and `uhid`. +Currently in use by [Wolf](https://github.com/games-on-whales/wolf), [Sunshine](https://github.com/LizardByte/Sunshine) and [Moonshine](https://github.com/hgaiser/moonshine) + Supports: - Keyboard @@ -11,13 +13,15 @@ Supports: - Joypad - Correctly emulates Xbox, PS5 or Nintendo joypads - Supports callbacks on Rumble events - - Gyro, Acceleration and Touchpad support (using UHID) + - Gyro, Acceleration, Touchpad, Adaptive triggers, LED and battery status fully supported when creating a virtual DualSense joypad (with full support without Steam Input for games that are compatible with DualSense) Interested in how the joypad works under the hood? Checkout these blog posts: - [When uinput Isn’t Enough: Virtualizing a DualSense controller](https://abeltra.me/blog/inputtino-uhid-1/) - [Creating a Virtual DualSense Controller via UHID](https://abeltra.me/blog/inputtino-uhid-2/) - [Beyond USB: Improving Virtual Controller Support in Linux Games](https://abeltra.me/blog/inputtino-uhid-3/) +A special thanks goes to [@hgaiser](https://github.com/hgaiser) for all the help in yak shaving the DualSense implementation. + ## Include in a C++ project If using `Cmake` it's as simple as @@ -31,7 +35,7 @@ FetchContent_MakeAvailable(inputtino) target_link_libraries( PUBLIC inputtino::libinputtino) ``` -## Example usage +### Example usage ```c++ #include