Skip to content

Commit 7b2d221

Browse files
committed
feat(Steam Deck): Add rumble support to Steam Deck
- 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.
1 parent 90d65b2 commit 7b2d221

File tree

10 files changed

+320
-91
lines changed

10 files changed

+320
-91
lines changed

src/drivers/steam_deck/driver.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,18 +70,12 @@ impl Driver {
7070
/// Rumble the gamepad
7171
pub fn haptic_rumble(
7272
&mut self,
73-
intensity: u16,
7473
left_speed: u16,
7574
right_speed: u16,
76-
left_gain: u8,
77-
right_gain: u8,
7875
) -> Result<(), Box<dyn Error + Send + Sync>> {
7976
let mut report = PackedRumbleReport::new();
80-
report.intensity = Integer::from_primitive(intensity);
8177
report.left_speed = Integer::from_primitive(left_speed);
8278
report.right_speed = Integer::from_primitive(right_speed);
83-
report.left_gain = left_gain;
84-
report.right_gain = right_gain;
8579

8680
// Write the report to the device
8781
let buf = report.pack()?;
@@ -90,6 +84,13 @@ impl Driver {
9084
Ok(())
9185
}
9286

87+
/// Writes the given buffer (typically an [OutputReport]) to the source device
88+
/// physical interface.
89+
pub fn write(&self, buf: &[u8]) -> Result<(), Box<dyn Error + Send + Sync>> {
90+
self.device.write(buf)?;
91+
Ok(())
92+
}
93+
9394
/// Set lizard mode, which will automatically try to emulate mouse/keyboard
9495
/// if enabled.
9596
pub fn set_lizard_mode(&self, enabled: bool) -> Result<(), Box<dyn Error + Send + Sync>> {

src/drivers/steam_deck/hid_report.rs

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -509,12 +509,28 @@ impl Default for PackedInputDataReport {
509509
}
510510

511511
#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)]
512-
pub enum Pad {
512+
pub enum PadSide {
513513
Left = 0,
514514
Right = 1,
515515
Both = 2,
516516
}
517517

518+
#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)]
519+
pub enum Intensity {
520+
Default = 0,
521+
Short = 1,
522+
Medium = 2,
523+
Long = 3,
524+
Insane = 4,
525+
}
526+
527+
#[derive(PrimitiveEnum_u8, Clone, Copy, PartialEq, Debug)]
528+
pub enum CommandType {
529+
Off = 0,
530+
Tick = 1,
531+
Click = 2,
532+
}
533+
518534
/*
519535
* Send a haptic pulse to the trackpads
520536
* Duration and interval are measured in microseconds, count is the number
@@ -529,7 +545,7 @@ pub struct PackedHapticPulseReport {
529545
#[packed_field(bytes = "1")]
530546
pub report_size: u8,
531547
#[packed_field(bytes = "2", ty = "enum")]
532-
pub side: Pad,
548+
pub side: PadSide,
533549
#[packed_field(bytes = "3..=4", endian = "lsb")]
534550
pub amplitude: Integer<u16, packed_bits::Bits<16>>,
535551
#[packed_field(bytes = "5..=6", endian = "lsb")]
@@ -543,7 +559,7 @@ impl PackedHapticPulseReport {
543559
Self {
544560
report_id: ReportType::TriggerHapticPulse as u8,
545561
report_size: 9,
546-
side: Pad::Both,
562+
side: PadSide::Both,
547563
amplitude: Integer::from_primitive(0),
548564
period: Integer::from_primitive(0),
549565
count: Integer::from_primitive(0),
@@ -558,36 +574,34 @@ impl Default for PackedHapticPulseReport {
558574
}
559575

560576
#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)]
561-
#[packed_struct(bit_numbering = "msb0")]
577+
#[packed_struct(bit_numbering = "msb0", size_bytes = "64")]
562578
pub struct PackedRumbleReport {
563579
#[packed_field(bytes = "0")]
564-
pub report_id: u8,
580+
pub cmd_id: u8,
565581
#[packed_field(bytes = "1")]
566582
pub report_size: u8,
567-
#[packed_field(bytes = "3..=4", endian = "lsb")]
568-
pub intensity: Integer<u16, packed_bits::Bits<16>>,
583+
#[packed_field(bytes = "2")]
584+
pub unk_2: u8,
585+
#[packed_field(bytes = "3", endian = "lsb")]
586+
pub event_type: u8,
587+
#[packed_field(bytes = "4", endian = "lsb")]
588+
pub intensity: u8,
569589
#[packed_field(bytes = "5..=6", endian = "lsb")]
570590
pub left_speed: Integer<u16, packed_bits::Bits<16>>,
571591
#[packed_field(bytes = "7..=8", endian = "lsb")]
572592
pub right_speed: Integer<u16, packed_bits::Bits<16>>,
573-
/// Max gain: 135
574-
#[packed_field(bytes = "9")]
575-
pub left_gain: u8,
576-
/// Max gain: 135
577-
#[packed_field(bytes = "10")]
578-
pub right_gain: u8,
579593
}
580594

581595
impl PackedRumbleReport {
582596
pub fn new() -> Self {
583597
Self {
584-
report_id: ReportType::TriggerRumbleCommand as u8,
598+
cmd_id: ReportType::TriggerRumbleCommand as u8,
585599
report_size: 9,
586-
intensity: Integer::from_primitive(1),
600+
unk_2: 0,
601+
event_type: 0,
602+
intensity: 0,
587603
left_speed: Integer::from_primitive(0),
588604
right_speed: Integer::from_primitive(0),
589-
left_gain: 130,
590-
right_gain: 130,
591605
}
592606
}
593607
}
@@ -598,6 +612,54 @@ impl Default for PackedRumbleReport {
598612
}
599613
}
600614

615+
#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)]
616+
#[packed_struct(bit_numbering = "msb0", size_bytes = "64")]
617+
pub struct PackedHapticReport {
618+
#[packed_field(bytes = "0")]
619+
pub cmd_id: u8,
620+
#[packed_field(bytes = "1")]
621+
pub report_size: u8,
622+
#[packed_field(bytes = "2", ty = "enum")]
623+
pub side: PadSide,
624+
#[packed_field(bytes = "3", ty = "enum")]
625+
pub cmd_type: CommandType,
626+
#[packed_field(bytes = "4", ty = "enum")]
627+
pub intensity: Intensity,
628+
#[packed_field(bytes = "5")]
629+
pub gain: i8,
630+
#[packed_field(bytes = "6")]
631+
pub unk_6: u8,
632+
#[packed_field(bytes = "7")]
633+
pub unk_7: u8,
634+
#[packed_field(bytes = "8")]
635+
pub unk_8: u8,
636+
#[packed_field(bytes = "12")]
637+
pub unk_12: u8,
638+
}
639+
640+
impl PackedHapticReport {
641+
pub fn new() -> Self {
642+
Self {
643+
cmd_id: ReportType::TriggerHapticCommand as u8,
644+
report_size: 13,
645+
side: PadSide::Left,
646+
cmd_type: CommandType::Off,
647+
intensity: Intensity::Default,
648+
gain: 0,
649+
unk_6: 95,
650+
unk_7: 204,
651+
unk_8: 3,
652+
unk_12: 16,
653+
}
654+
}
655+
}
656+
657+
impl Default for PackedHapticReport {
658+
fn default() -> Self {
659+
Self::new()
660+
}
661+
}
662+
601663
#[derive(PackedStruct, Debug, Copy, Clone, PartialEq)]
602664
#[packed_struct(bit_numbering = "msb0", size_bytes = "64")]
603665
pub struct PackedMappingsReport {

src/input/capability.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,8 @@ impl fmt::Display for GamepadButton {
441441
GamepadButton::DPadUp => write!(f, "DPadUp"),
442442
GamepadButton::East => write!(f, "East"),
443443
GamepadButton::Guide => write!(f, "Guide"),
444-
GamepadButton::QuickAccess => write!(f, "QuickAccess"),
445-
GamepadButton::QuickAccess2 => write!(f, "QuickAccess2"),
446444
GamepadButton::Keyboard => write!(f, "Keyboard"),
447445
GamepadButton::LeftBumper => write!(f, "LeftBumper"),
448-
GamepadButton::LeftTop => write!(f, "LeftTop"),
449-
GamepadButton::LeftTrigger => write!(f, "LeftTrigger"),
450446
GamepadButton::LeftPaddle1 => write!(f, "LeftPaddle1"),
451447
GamepadButton::LeftPaddle2 => write!(f, "LeftPaddle2"),
452448
GamepadButton::LeftPaddle3 => write!(f, "LeftPaddle3"),
@@ -459,8 +455,6 @@ impl fmt::Display for GamepadButton {
459455
GamepadButton::QuickAccess => write!(f, "QuickAccess"),
460456
GamepadButton::QuickAccess2 => write!(f, "QuickAccess2"),
461457
GamepadButton::RightBumper => write!(f, "RightBumper"),
462-
GamepadButton::RightTop => write!(f, "RightTop"),
463-
GamepadButton::RightTrigger => write!(f, "RightTrigger"),
464458
GamepadButton::RightPaddle1 => write!(f, "RightPaddle1"),
465459
GamepadButton::RightPaddle2 => write!(f, "RightPaddle2"),
466460
GamepadButton::RightPaddle3 => write!(f, "RightPaddle3"),

src/input/output_capability.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub enum OutputCapability {
66
ForceFeedback,
77
ForceFeedbackUpload,
88
ForceFeedbackErase,
9+
Haptics(Haptic),
910
#[allow(clippy::upper_case_acronyms)]
1011
LED(LED),
1112
}
@@ -17,3 +18,11 @@ pub enum LED {
1718
Brightness,
1819
Color,
1920
}
21+
22+
/// Haptic capabilities
23+
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
24+
pub enum Haptic {
25+
TrackpadLeft,
26+
TrackpadRight,
27+
//TrackpadCenter,
28+
}

src/input/output_event/mod.rs

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,75 @@ use std::sync::mpsc::Sender;
22

33
use ::evdev::{FFEffectData, InputEvent};
44

5-
use crate::drivers::dualsense::hid_report::SetStatePackedOutputData;
5+
use crate::drivers::{
6+
dualsense::hid_report::SetStatePackedOutputData,
7+
steam_deck::hid_report::{PackedHapticReport, PackedRumbleReport, PadSide},
8+
};
69

7-
use super::output_capability::OutputCapability;
10+
use super::output_capability::{Haptic, OutputCapability};
811

912
/// Output events are events that flow from target devices back to source devices
1013
#[derive(Debug, Clone)]
1114
pub enum OutputEvent {
1215
Evdev(InputEvent),
1316
Uinput(UinputOutputEvent),
1417
DualSense(SetStatePackedOutputData),
18+
SteamDeckHaptics(PackedHapticReport),
19+
SteamDeckRumble(PackedRumbleReport),
1520
}
1621

1722
impl OutputEvent {
1823
/// Returns the capability of the output event
19-
fn as_capability(&self) -> OutputCapability {
24+
fn as_capability(&self) -> Vec<OutputCapability> {
2025
match self {
2126
OutputEvent::Evdev(event) => match event.destructure() {
22-
evdev::EventSummary::Synchronization(_, _, _) => OutputCapability::NotImplemented,
23-
evdev::EventSummary::Key(_, _, _) => OutputCapability::NotImplemented,
24-
evdev::EventSummary::RelativeAxis(_, _, _) => OutputCapability::NotImplemented,
25-
evdev::EventSummary::AbsoluteAxis(_, _, _) => OutputCapability::NotImplemented,
26-
evdev::EventSummary::Misc(_, _, _) => OutputCapability::NotImplemented,
27-
evdev::EventSummary::Switch(_, _, _) => OutputCapability::NotImplemented,
28-
evdev::EventSummary::Led(_, _, _) => OutputCapability::NotImplemented,
29-
evdev::EventSummary::Sound(_, _, _) => OutputCapability::NotImplemented,
30-
evdev::EventSummary::Repeat(_, _, _) => OutputCapability::NotImplemented,
31-
evdev::EventSummary::ForceFeedback(_, _, _) => OutputCapability::ForceFeedback,
32-
evdev::EventSummary::Power(_, _, _) => OutputCapability::NotImplemented,
27+
evdev::EventSummary::Synchronization(_, _, _) => {
28+
vec![OutputCapability::NotImplemented]
29+
}
30+
evdev::EventSummary::Key(_, _, _) => vec![OutputCapability::NotImplemented],
31+
evdev::EventSummary::RelativeAxis(_, _, _) => {
32+
vec![OutputCapability::NotImplemented]
33+
}
34+
evdev::EventSummary::AbsoluteAxis(_, _, _) => {
35+
vec![OutputCapability::NotImplemented]
36+
}
37+
evdev::EventSummary::Misc(_, _, _) => vec![OutputCapability::NotImplemented],
38+
evdev::EventSummary::Switch(_, _, _) => vec![OutputCapability::NotImplemented],
39+
evdev::EventSummary::Led(_, _, _) => vec![OutputCapability::NotImplemented],
40+
evdev::EventSummary::Sound(_, _, _) => vec![OutputCapability::NotImplemented],
41+
evdev::EventSummary::Repeat(_, _, _) => vec![OutputCapability::NotImplemented],
42+
evdev::EventSummary::ForceFeedback(_, _, _) => {
43+
vec![OutputCapability::ForceFeedback]
44+
}
45+
evdev::EventSummary::Power(_, _, _) => vec![OutputCapability::NotImplemented],
3346
evdev::EventSummary::ForceFeedbackStatus(_, _, _) => {
34-
OutputCapability::NotImplemented
47+
vec![OutputCapability::NotImplemented]
3548
}
36-
evdev::EventSummary::UInput(_, _, _) => OutputCapability::NotImplemented,
37-
evdev::EventSummary::Other(_, _, _) => OutputCapability::NotImplemented,
49+
evdev::EventSummary::UInput(_, _, _) => vec![OutputCapability::NotImplemented],
50+
evdev::EventSummary::Other(_, _, _) => vec![OutputCapability::NotImplemented],
3851
},
3952
OutputEvent::Uinput(uinput) => match uinput {
40-
UinputOutputEvent::FFUpload(_, _, _) => OutputCapability::ForceFeedbackUpload,
41-
UinputOutputEvent::FFErase(_) => OutputCapability::ForceFeedbackErase,
53+
UinputOutputEvent::FFUpload(_, _, _) => vec![OutputCapability::ForceFeedbackUpload],
54+
UinputOutputEvent::FFErase(_) => vec![OutputCapability::ForceFeedbackErase],
4255
},
4356
OutputEvent::DualSense(report) => {
4457
if report.use_rumble_not_haptics {
45-
OutputCapability::ForceFeedback
58+
vec![OutputCapability::ForceFeedback]
4659
} else {
47-
OutputCapability::NotImplemented
60+
vec![OutputCapability::NotImplemented]
61+
}
62+
}
63+
OutputEvent::SteamDeckHaptics(packed_haptic_report) => {
64+
match packed_haptic_report.side {
65+
PadSide::Left => vec![OutputCapability::Haptics(Haptic::TrackpadLeft)],
66+
PadSide::Right => vec![OutputCapability::Haptics(Haptic::TrackpadRight)],
67+
PadSide::Both => vec![
68+
OutputCapability::Haptics(Haptic::TrackpadLeft),
69+
OutputCapability::Haptics(Haptic::TrackpadRight),
70+
],
4871
}
4972
}
73+
OutputEvent::SteamDeckRumble(_) => vec![OutputCapability::ForceFeedback],
5074
}
5175
}
5276
}

0 commit comments

Comments
 (0)