diff --git a/src/drivers/steam_deck/driver.rs b/src/drivers/steam_deck/driver.rs index 464d5c09..9dce4d43 100644 --- a/src/drivers/steam_deck/driver.rs +++ b/src/drivers/steam_deck/driver.rs @@ -70,18 +70,12 @@ impl Driver { /// Rumble the gamepad pub fn haptic_rumble( &mut self, - intensity: u16, left_speed: u16, right_speed: u16, - left_gain: u8, - right_gain: u8, ) -> Result<(), Box> { let mut report = PackedRumbleReport::new(); - report.intensity = Integer::from_primitive(intensity); report.left_speed = Integer::from_primitive(left_speed); report.right_speed = Integer::from_primitive(right_speed); - report.left_gain = left_gain; - report.right_gain = right_gain; // Write the report to the device let buf = report.pack()?; @@ -90,6 +84,11 @@ impl Driver { Ok(()) } + pub fn write(&self, buf: &[u8]) -> Result<(), Box> { + self.device.write(buf)?; + Ok(()) + } + /// Set lizard mode, which will automatically try to emulate mouse/keyboard /// if enabled. pub fn set_lizard_mode(&self, enabled: bool) -> Result<(), Box> { diff --git a/src/drivers/steam_deck/hid_report.rs b/src/drivers/steam_deck/hid_report.rs index 703c748f..86905294 100644 --- a/src/drivers/steam_deck/hid_report.rs +++ b/src/drivers/steam_deck/hid_report.rs @@ -509,12 +509,28 @@ impl Default for PackedInputDataReport { } #[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)] -pub enum Pad { +pub enum PadSide { Left = 0, Right = 1, Both = 2, } +#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)] +pub enum Intensity { + Default = 0, + Short = 1, + Medium = 2, + Long = 3, + Insane = 4, +} + +#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)] +pub enum CommandType { + Off = 0, + Tick = 1, + Click = 2, +} + /* * Send a haptic pulse to the trackpads * Duration and interval are measured in microseconds, count is the number @@ -529,7 +545,7 @@ pub struct PackedHapticPulseReport { #[packed_field(bytes = "1")] pub report_size: u8, #[packed_field(bytes = "2", ty = "enum")] - pub side: Pad, + pub side: PadSide, #[packed_field(bytes = "3..=4", endian = "lsb")] pub amplitude: Integer>, #[packed_field(bytes = "5..=6", endian = "lsb")] @@ -543,7 +559,7 @@ impl PackedHapticPulseReport { Self { report_id: ReportType::TriggerHapticPulse as u8, report_size: 9, - side: Pad::Both, + side: PadSide::Both, amplitude: Integer::from_primitive(0), period: Integer::from_primitive(0), count: Integer::from_primitive(0), @@ -558,36 +574,34 @@ impl Default for PackedHapticPulseReport { } #[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] -#[packed_struct(bit_numbering = "msb0")] +#[packed_struct(bit_numbering = "msb0", size_bytes = "64")] pub struct PackedRumbleReport { #[packed_field(bytes = "0")] - pub report_id: u8, + pub cmd_id: u8, #[packed_field(bytes = "1")] pub report_size: u8, - #[packed_field(bytes = "3..=4", endian = "lsb")] - pub intensity: Integer>, + #[packed_field(bytes = "2")] + pub unk_2: u8, + #[packed_field(bytes = "3", endian = "lsb")] + pub event_type: u8, + #[packed_field(bytes = "4", endian = "lsb")] + pub intensity: u8, #[packed_field(bytes = "5..=6", endian = "lsb")] pub left_speed: Integer>, #[packed_field(bytes = "7..=8", endian = "lsb")] pub right_speed: Integer>, - /// Max gain: 135 - #[packed_field(bytes = "9")] - pub left_gain: u8, - /// Max gain: 135 - #[packed_field(bytes = "10")] - pub right_gain: u8, } impl PackedRumbleReport { pub fn new() -> Self { Self { - report_id: ReportType::TriggerRumbleCommand as u8, + cmd_id: ReportType::TriggerRumbleCommand as u8, report_size: 9, - intensity: Integer::from_primitive(1), + unk_2: 0, + event_type: 0, + intensity: 0, left_speed: Integer::from_primitive(0), right_speed: Integer::from_primitive(0), - left_gain: 130, - right_gain: 130, } } } @@ -598,6 +612,54 @@ impl Default for PackedRumbleReport { } } +#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] +#[packed_struct(bit_numbering = "msb0", size_bytes = "64")] +pub struct PackedHapticReport { + #[packed_field(bytes = "0")] + pub cmd_id: u8, + #[packed_field(bytes = "1")] + pub report_size: u8, + #[packed_field(bytes = "2", ty = "enum")] + pub side: PadSide, + #[packed_field(bytes = "3", ty = "enum")] + pub cmd_type: CommandType, + #[packed_field(bytes = "4", ty = "enum")] + pub intensity: Intensity, + #[packed_field(bytes = "5")] + pub gain: i8, + #[packed_field(bytes = "6")] + pub unk_6: u8, + #[packed_field(bytes = "7")] + pub unk_7: u8, + #[packed_field(bytes = "8")] + pub unk_8: u8, + #[packed_field(bytes = "12")] + pub unk_12: u8, +} + +impl PackedHapticReport { + pub fn new() -> Self { + Self { + cmd_id: ReportType::TriggerHapticCommand as u8, + report_size: 13, + side: PadSide::Left, + cmd_type: CommandType::Off, + intensity: Intensity::Default, + gain: 0, + unk_6: 95, + unk_7: 204, + unk_8: 3, + unk_12: 16, + } + } +} + +impl Default for PackedHapticReport { + fn default() -> Self { + Self::new() + } +} + #[derive(PackedStruct, Debug, Copy, Clone, PartialEq)] #[packed_struct(bit_numbering = "msb0", size_bytes = "64")] pub struct PackedMappingsReport { diff --git a/src/input/capability.rs b/src/input/capability.rs index 883051a7..a27ac388 100644 --- a/src/input/capability.rs +++ b/src/input/capability.rs @@ -441,12 +441,8 @@ impl fmt::Display for GamepadButton { GamepadButton::DPadUp => write!(f, "DPadUp"), GamepadButton::East => write!(f, "East"), GamepadButton::Guide => write!(f, "Guide"), - GamepadButton::QuickAccess => write!(f, "QuickAccess"), - GamepadButton::QuickAccess2 => write!(f, "QuickAccess2"), GamepadButton::Keyboard => write!(f, "Keyboard"), GamepadButton::LeftBumper => write!(f, "LeftBumper"), - GamepadButton::LeftTop => write!(f, "LeftTop"), - GamepadButton::LeftTrigger => write!(f, "LeftTrigger"), GamepadButton::LeftPaddle1 => write!(f, "LeftPaddle1"), GamepadButton::LeftPaddle2 => write!(f, "LeftPaddle2"), GamepadButton::LeftPaddle3 => write!(f, "LeftPaddle3"), @@ -459,8 +455,6 @@ impl fmt::Display for GamepadButton { GamepadButton::QuickAccess => write!(f, "QuickAccess"), GamepadButton::QuickAccess2 => write!(f, "QuickAccess2"), GamepadButton::RightBumper => write!(f, "RightBumper"), - GamepadButton::RightTop => write!(f, "RightTop"), - GamepadButton::RightTrigger => write!(f, "RightTrigger"), GamepadButton::RightPaddle1 => write!(f, "RightPaddle1"), GamepadButton::RightPaddle2 => write!(f, "RightPaddle2"), GamepadButton::RightPaddle3 => write!(f, "RightPaddle3"), diff --git a/src/input/output_capability.rs b/src/input/output_capability.rs index 3607dbe0..86acca2a 100644 --- a/src/input/output_capability.rs +++ b/src/input/output_capability.rs @@ -6,6 +6,7 @@ pub enum OutputCapability { ForceFeedback, ForceFeedbackUpload, ForceFeedbackErase, + Haptics(Haptic), #[allow(clippy::upper_case_acronyms)] LED(LED), } @@ -17,3 +18,12 @@ pub enum LED { Brightness, Color, } + +/// Haptic capabilities +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Haptic { + TrackpadLeft, + TrackpadRight, + //TrackpadCenter, +} diff --git a/src/input/output_event/mod.rs b/src/input/output_event/mod.rs index 2f7af24d..046a1e30 100644 --- a/src/input/output_event/mod.rs +++ b/src/input/output_event/mod.rs @@ -2,9 +2,12 @@ use std::sync::mpsc::Sender; use ::evdev::{FFEffectData, InputEvent}; -use crate::drivers::dualsense::hid_report::SetStatePackedOutputData; +use crate::drivers::{ + dualsense::hid_report::SetStatePackedOutputData, + steam_deck::hid_report::{PackedHapticReport, PackedRumbleReport, PadSide}, +}; -use super::output_capability::OutputCapability; +use super::output_capability::{Haptic, OutputCapability}; /// Output events are events that flow from target devices back to source devices #[derive(Debug, Clone)] @@ -12,41 +15,62 @@ pub enum OutputEvent { Evdev(InputEvent), Uinput(UinputOutputEvent), DualSense(SetStatePackedOutputData), + SteamDeckHaptics(PackedHapticReport), + SteamDeckRumble(PackedRumbleReport), } impl OutputEvent { /// Returns the capability of the output event - fn as_capability(&self) -> OutputCapability { + fn as_capability(&self) -> Vec { match self { OutputEvent::Evdev(event) => match event.destructure() { - evdev::EventSummary::Synchronization(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Key(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::RelativeAxis(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::AbsoluteAxis(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Misc(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Switch(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Led(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Sound(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Repeat(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::ForceFeedback(_, _, _) => OutputCapability::ForceFeedback, - evdev::EventSummary::Power(_, _, _) => OutputCapability::NotImplemented, + evdev::EventSummary::Synchronization(_, _, _) => { + vec![OutputCapability::NotImplemented] + } + evdev::EventSummary::Key(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::RelativeAxis(_, _, _) => { + vec![OutputCapability::NotImplemented] + } + evdev::EventSummary::AbsoluteAxis(_, _, _) => { + vec![OutputCapability::NotImplemented] + } + evdev::EventSummary::Misc(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::Switch(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::Led(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::Sound(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::Repeat(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::ForceFeedback(_, _, _) => { + vec![OutputCapability::ForceFeedback] + } + evdev::EventSummary::Power(_, _, _) => vec![OutputCapability::NotImplemented], evdev::EventSummary::ForceFeedbackStatus(_, _, _) => { - OutputCapability::NotImplemented + vec![OutputCapability::NotImplemented] } - evdev::EventSummary::UInput(_, _, _) => OutputCapability::NotImplemented, - evdev::EventSummary::Other(_, _, _) => OutputCapability::NotImplemented, + evdev::EventSummary::UInput(_, _, _) => vec![OutputCapability::NotImplemented], + evdev::EventSummary::Other(_, _, _) => vec![OutputCapability::NotImplemented], }, OutputEvent::Uinput(uinput) => match uinput { - UinputOutputEvent::FFUpload(_, _, _) => OutputCapability::ForceFeedbackUpload, - UinputOutputEvent::FFErase(_) => OutputCapability::ForceFeedbackErase, + UinputOutputEvent::FFUpload(_, _, _) => vec![OutputCapability::ForceFeedbackUpload], + UinputOutputEvent::FFErase(_) => vec![OutputCapability::ForceFeedbackErase], }, OutputEvent::DualSense(report) => { if report.use_rumble_not_haptics { - OutputCapability::ForceFeedback + vec![OutputCapability::ForceFeedback] } else { - OutputCapability::NotImplemented + vec![OutputCapability::NotImplemented] + } + } + OutputEvent::SteamDeckHaptics(packed_haptic_report) => { + match packed_haptic_report.side { + PadSide::Left => vec![OutputCapability::Haptics(Haptic::TrackpadLeft)], + PadSide::Right => vec![OutputCapability::Haptics(Haptic::TrackpadRight)], + PadSide::Both => vec![ + OutputCapability::Haptics(Haptic::TrackpadLeft), + OutputCapability::Haptics(Haptic::TrackpadRight), + ], } } + OutputEvent::SteamDeckRumble(_) => vec![OutputCapability::ForceFeedback], } } } diff --git a/src/input/source/evdev/gamepad.rs b/src/input/source/evdev/gamepad.rs index 70929e96..a731d8aa 100644 --- a/src/input/source/evdev/gamepad.rs +++ b/src/input/source/evdev/gamepad.rs @@ -6,7 +6,9 @@ use evdev::{ FFTrigger, InputEvent, }; use nix::fcntl::{FcntlArg, OFlag}; +use packed_struct::types::SizedInteger; +use crate::drivers::steam_deck::hid_report::PackedRumbleReport; use crate::{ drivers::dualsense::hid_report::SetStatePackedOutputData, input::{ @@ -24,6 +26,7 @@ pub struct GamepadEventDevice { axes_info: HashMap, ff_effects: HashMap, ff_effects_dualsense: Option, + ff_effects_deck: Option, hat_state: HashMap, } @@ -54,6 +57,7 @@ impl GamepadEventDevice { axes_info, ff_effects: HashMap::new(), ff_effects_dualsense: None, + ff_effects_deck: None, hat_state: HashMap::new(), }) } @@ -172,6 +176,71 @@ impl GamepadEventDevice { Ok(()) } + + // Process Steam Deck FFB events. + fn process_deck_ff(&mut self, report: PackedRumbleReport) -> Result<(), Box> { + // If no effect was uploaded to handle Steam Deck force feedback, upload one. + if self.ff_effects_deck.is_none() { + let effect_data = FFEffectData { + direction: 0, + trigger: FFTrigger { + button: 0, + interval: 0, + }, + replay: FFReplay { + length: 50, + delay: 0, + }, + kind: FFEffectKind::Rumble { + strong_magnitude: 0, + weak_magnitude: 0, + }, + }; + log::debug!("Uploading FF effect data"); + let effect = self.device.upload_ff_effect(effect_data)?; + let id = effect.id() as i16; + self.ff_effects.insert(id, effect); + self.ff_effects_deck = Some(id); + } + + let effect_id = self.ff_effects_deck.unwrap(); + let effect = self.ff_effects.get_mut(&effect_id).unwrap(); + + let left_speed = report.left_speed.to_primitive(); + let right_speed = report.right_speed.to_primitive(); + + log::debug!("Got FF event data, Left Speed: {left_speed}, Right Speed: {right_speed}"); + + // Stop playing the effect if values are set to zero + if left_speed == 0 && right_speed == 0 { + log::trace!("Stopping FF effect"); + effect.stop()?; + return Ok(()); + } + + // Set the values of the effect and play it + let effect_data = FFEffectData { + direction: 0, + trigger: FFTrigger { + button: 0, + interval: 0, + }, + replay: FFReplay { + length: 60000, + delay: 0, + }, + kind: FFEffectKind::Rumble { + strong_magnitude: left_speed, + weak_magnitude: right_speed, + }, + }; + log::trace!("Updating effect data"); + effect.update(effect_data)?; + log::trace!("Playing effect with data: {:?}", effect_data); + effect.play(1)?; + + Ok(()) + } } impl SourceInputDevice for GamepadEventDevice { @@ -312,12 +381,20 @@ impl SourceOutputDevice for GamepadEventDevice { log::debug!("Received DualSense output report"); if report.use_rumble_not_haptics || report.enable_improved_rumble_emulation { if let Err(e) = self.process_dualsense_ff(report) { - log::error!("Failed to process dualsense output report: {:?}", e); + log::error!("Failed to process dualsense output report: {e:?}"); } } Ok(()) } OutputEvent::Uinput(_) => Ok(()), + OutputEvent::SteamDeckHaptics(_report) => Ok(()), + OutputEvent::SteamDeckRumble(report) => { + log::debug!("Received Steam Deck FFB Output Report"); + if let Err(e) = self.process_deck_ff(report) { + log::error!("Failed to process Steam Deck Force Feedback Report: {e:?}") + } + Ok(()) + } } } diff --git a/src/input/source/hidraw/dualsense.rs b/src/input/source/hidraw/dualsense.rs index dc4bf249..6a26a5c2 100644 --- a/src/input/source/hidraw/dualsense.rs +++ b/src/input/source/hidraw/dualsense.rs @@ -2,8 +2,10 @@ use std::fmt::Debug; use std::{collections::HashMap, error::Error}; use evdev::{FFEffectData, FFEffectKind}; +use packed_struct::types::SizedInteger; use crate::drivers::dualsense::driver::{DS5_EDGE_PID, DS5_PID, DS5_VID}; +use crate::drivers::steam_deck::hid_report::PackedRumbleReport; use crate::{ drivers::dualsense::{self, driver::Driver}, input::{ @@ -112,13 +114,11 @@ impl DualSenseController { weak_magnitude, } => { // Scale the rumble values to the DS5 values - let left_speed = (strong_magnitude as f64 / u16::MAX as f64) * u8::MAX as f64; - let left_speed = left_speed.round() as u8; - let right_speed = (weak_magnitude as f64 / u16::MAX as f64) * u8::MAX as f64; - let right_speed = right_speed.round() as u8; + let left_speed = strong_magnitude / u8::MAX as u16 + 1; + let right_speed = weak_magnitude / u8::MAX as u16 + 1; // Do rumble - if let Err(e) = self.driver.rumble(left_speed, right_speed) { + if let Err(e) = self.driver.rumble(left_speed as u8, right_speed as u8) { let err = format!("Failed to do rumble: {:?}", e); return Err(err.into()); } @@ -127,6 +127,16 @@ impl DualSenseController { Ok(()) } + + /// Procces Steam Deck FFB events. + fn process_deck_ff(&mut self, report: PackedRumbleReport) -> Result<(), Box> { + let left_speed = report.left_speed.to_primitive() / u8::MAX as u16 + 1; + let right_speed = report.right_speed.to_primitive() / u8::MAX as u16 + 1; + self.driver + .rumble(left_speed as u8, right_speed as u8) + .map_err(|e| e.to_string())?; + Ok(()) + } } impl SourceInputDevice for DualSenseController { @@ -157,6 +167,14 @@ impl SourceOutputDevice for DualSenseController { Ok(self.driver.write(report)?) } OutputEvent::Uinput(_) => Ok(()), + OutputEvent::SteamDeckHaptics(_report) => Ok(()), + OutputEvent::SteamDeckRumble(report) => { + log::debug!("Received Steam Deck FFB Output Report"); + if let Err(e) = self.process_deck_ff(report) { + log::error!("Failed to process Steam Deck Force Feedback Report: {e:?}") + } + Ok(()) + } } } diff --git a/src/input/source/hidraw/steam_deck.rs b/src/input/source/hidraw/steam_deck.rs index 27621a61..61e4c12f 100644 --- a/src/input/source/hidraw/steam_deck.rs +++ b/src/input/source/hidraw/steam_deck.rs @@ -8,6 +8,7 @@ use std::{ }; use evdev::{FFEffectData, FFEffectKind, InputEvent}; +use packed_struct::PackedStruct; use crate::{ drivers::{ @@ -119,7 +120,7 @@ impl DeckController { // The value determines if the effect should be playing or not. if value == 0 { - if let Err(e) = self.driver.haptic_rumble(0, 0, 0, 0, 0) { + if let Err(e) = self.driver.haptic_rumble(0, 0) { log::debug!("Failed to stop haptic rumble: {:?}", e); return Ok(()); } @@ -154,26 +155,11 @@ impl DeckController { weak_magnitude, } => { // Set rumble values based on the effect data - let intensity = 0; let left_speed = strong_magnitude; let right_speed = weak_magnitude; - let mut left_gain = 0; // Max 130 - let mut right_gain = 0; - if left_speed == 0 { - left_gain = 0; - } - if right_speed == 0 { - right_gain = 0; - } // Do rumble - if let Err(e) = self.driver.haptic_rumble( - intensity, - left_speed, - right_speed, - left_gain, - right_gain, - ) { + if let Err(e) = self.driver.haptic_rumble(left_speed, right_speed) { let err = format!("Failed to do haptic rumble: {:?}", e); return Err(err.into()); } @@ -189,22 +175,10 @@ impl DeckController { report: SetStatePackedOutputData, ) -> Result<(), Box> { // Set the rumble values based on the DualSense output report - let intensity = 0; let left_speed = report.rumble_emulation_left as u16 * 256; let right_speed = report.rumble_emulation_right as u16 * 256; - let mut left_gain = 0; // Max 130 - let mut right_gain = 0; - if left_speed == 0 { - left_gain = 0; - } - if right_speed == 0 { - right_gain = 0; - } - if let Err(e) = - self.driver - .haptic_rumble(intensity, left_speed, right_speed, left_gain, right_gain) - { + if let Err(e) = self.driver.haptic_rumble(left_speed, right_speed) { let err = format!("Failed to do haptic rumble: {:?}", e); return Err(err.into()); } @@ -249,6 +223,14 @@ impl SourceOutputDevice for DeckController { } } OutputEvent::Uinput(_) => (), + OutputEvent::SteamDeckHaptics(packed_haptic_report) => { + let report = packed_haptic_report.pack().map_err(|e| e.to_string())?; + self.driver.write(&report)?; + } + OutputEvent::SteamDeckRumble(packed_rumble_report) => { + let report = packed_rumble_report.pack().map_err(|e| e.to_string())?; + self.driver.write(&report)?; + } } Ok(()) diff --git a/src/input/source/hidraw/xpad_uhid.rs b/src/input/source/hidraw/xpad_uhid.rs index ce27f992..e3255f00 100644 --- a/src/input/source/hidraw/xpad_uhid.rs +++ b/src/input/source/hidraw/xpad_uhid.rs @@ -146,6 +146,8 @@ impl SourceOutputDevice for XpadUhid { OutputEvent::Evdev(input_event) => Ok(self.process_evdev_ff(input_event)?), OutputEvent::DualSense(_) => Ok(()), OutputEvent::Uinput(_) => Ok(()), + OutputEvent::SteamDeckHaptics(_packed_haptic_report) => Ok(()), + OutputEvent::SteamDeckRumble(_packed_rumble_report) => Ok(()), } } diff --git a/src/input/target/steam_deck.rs b/src/input/target/steam_deck.rs index 5cabaa53..939784e3 100644 --- a/src/input/target/steam_deck.rs +++ b/src/input/target/steam_deck.rs @@ -24,9 +24,9 @@ use crate::{ drivers::steam_deck::{ driver::{PID, VID}, hid_report::{ - PackedInputDataReport, ReportType, PAD_FORCE_MAX, PAD_X_MAX, PAD_X_MIN, PAD_Y_MAX, - PAD_Y_MIN, STICK_FORCE_MAX, STICK_X_MAX, STICK_X_MIN, STICK_Y_MAX, STICK_Y_MIN, - TRIGG_MAX, + PackedHapticReport, PackedInputDataReport, PackedRumbleReport, ReportType, + PAD_FORCE_MAX, PAD_X_MAX, PAD_X_MIN, PAD_Y_MAX, PAD_Y_MIN, STICK_FORCE_MAX, + STICK_X_MAX, STICK_X_MIN, STICK_Y_MAX, STICK_Y_MIN, TRIGG_MAX, }, report_descriptor::{CONTROLLER_DESCRIPTOR, KEYBOARD_DESCRIPTOR, MOUSE_DESCRIPTOR}, }, @@ -40,6 +40,7 @@ use crate::{ native::{NativeEvent, ScheduledNativeEvent}, value::InputValue, }, + output_capability::{Haptic, OutputCapability}, output_event::OutputEvent, }, }; @@ -60,6 +61,7 @@ pub struct SteamDeckDevice { serial_number: String, queued_events: Vec, pressed_events: HashMap, + output_event: Option, } impl SteamDeckDevice { @@ -82,6 +84,7 @@ impl SteamDeckDevice { serial_number: "INPU7PLUMB3R".to_string(), queued_events: vec![], pressed_events: HashMap::new(), + output_event: None, }) } @@ -423,8 +426,50 @@ impl SteamDeckDevice { log::debug!("Serial number requested"); self.current_report = ReportType::GetSerial; } - ReportType::TriggerHapticCommand => (), - ReportType::TriggerRumbleCommand => (), + ReportType::TriggerHapticCommand => { + self.current_report = ReportType::TriggerHapticCommand; + + let buf = match data.as_slice().try_into() { + Ok(buffer) => buffer, + Err(e) => { + log::error!("Failed to process Haptic Command: {e}"); + return; + } + }; + + let packed_haptic_report = match PackedHapticReport::unpack(buf) { + Ok(report) => report, + Err(e) => { + log::error!("Failed to process Haptic Command: {e}"); + return; + } + }; + //log::trace!("Got PackedHapticReport: {packed_haptic_report}"); + let event = OutputEvent::SteamDeckHaptics(packed_haptic_report); + self.output_event = Some(event); + } + ReportType::TriggerRumbleCommand => { + self.current_report = ReportType::TriggerRumbleCommand; + + let buf = match data.as_slice().try_into() { + Ok(buffer) => buffer, + Err(e) => { + log::error!("Failed to process Rumble Command: {e}"); + return; + } + }; + + let packed_rumble_report = match PackedRumbleReport::unpack(buf) { + Ok(report) => report, + Err(e) => { + log::error!("Failed to process Rumble Command: {e}"); + return; + } + }; + //log::trace!("Got PackedRumbleReport: {packed_rumble_report}"); + let event = OutputEvent::SteamDeckRumble(packed_rumble_report); + self.output_event = Some(event); + } } } // Ignore other types of requests @@ -809,8 +854,23 @@ impl TargetOutputDevice for SteamDeckDevice { } } + // Handle [OutputEvent] if it was created + let event = self.output_event.take(); + if let Some(event) = event { + return Ok(vec![event]); + } + Ok(vec![]) } + + /// Returns the possible output events this device is capable of emitting + fn get_output_capabilities(&self) -> Result, OutputError> { + Ok(vec![ + OutputCapability::ForceFeedback, + OutputCapability::Haptics(Haptic::TrackpadLeft), + OutputCapability::Haptics(Haptic::TrackpadRight), + ]) + } } impl Debug for SteamDeckDevice {