Skip to content

Commit

Permalink
feat(Steam Deck): Add rumble support to Steam Deck
Browse files Browse the repository at this point in the history
- Adds Steam Deck Target device rumble event report handling.
- Adds Steam Deck Source device handling of Steam Deck Rumble events.
- Adds Evdev Source device handling of Steam Deck Rumble Events.
- Adds dualsense Source device handling of Steam Deck Rumble Events.
- Adds Steam Deck Target Device haptic event report handling.
- Adds Steam Deck Source Device handling of Steam Deck Haptic events.
  • Loading branch information
pastaq committed Jan 17, 2025
1 parent 90d65b2 commit 3125795
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 91 deletions.
11 changes: 5 additions & 6 deletions src/drivers/steam_deck/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Error + Send + Sync>> {
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()?;
Expand All @@ -90,6 +84,11 @@ impl Driver {
Ok(())
}

pub fn write(&self, buf: &[u8]) -> Result<(), Box<dyn Error + Send + Sync>> {
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<dyn Error + Send + Sync>> {
Expand Down
96 changes: 79 additions & 17 deletions src/drivers/steam_deck/hid_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<u16, packed_bits::Bits<16>>,
#[packed_field(bytes = "5..=6", endian = "lsb")]
Expand All @@ -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),
Expand All @@ -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<u16, packed_bits::Bits<16>>,
#[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<u16, packed_bits::Bits<16>>,
#[packed_field(bytes = "7..=8", endian = "lsb")]
pub right_speed: Integer<u16, packed_bits::Bits<16>>,
/// 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,
}
}
}
Expand All @@ -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 {
Expand Down
6 changes: 0 additions & 6 deletions src/input/capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
Expand Down
10 changes: 10 additions & 0 deletions src/input/output_capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub enum OutputCapability {
ForceFeedback,
ForceFeedbackUpload,
ForceFeedbackErase,
Haptics(Haptic),
#[allow(clippy::upper_case_acronyms)]
LED(LED),
}
Expand All @@ -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,
}
66 changes: 45 additions & 21 deletions src/input/output_event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,75 @@ 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)]
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<OutputCapability> {
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],
}
}
}
Expand Down
Loading

0 comments on commit 3125795

Please sign in to comment.