Skip to main content

scdsu_core/devices/
device.rs

1use std::fmt;
2use std::str::FromStr;
3
4use crate::dsu::DSUFrame;
5use crate::errors::DeviceError;
6
7/// Supported device families
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum DeviceFamily {
10    /// Triton Steam Controller (2026)
11    #[default]
12    Triton,
13    /// Legacy Steam Controller (2015)
14    Legacy,
15}
16
17impl fmt::Display for DeviceFamily {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        match self {
20            Self::Triton => f.write_str("triton"),
21            Self::Legacy => f.write_str("legacy"),
22        }
23    }
24}
25
26impl FromStr for DeviceFamily {
27    type Err = DeviceError;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        match s {
31            "triton" => Ok(Self::Triton),
32            "legacy" => Ok(Self::Legacy),
33            _ => Err(DeviceError::InvalidDeviceFamily(s.to_string())),
34        }
35    }
36}
37
38/// A trait defining shared behavior between compatible devices.
39pub trait Device {
40    /// Run any initialization logic the device requires.
41    fn initialize(&self) -> Result<(), DeviceError>;
42
43    /// Read a DSU frame from the device.
44    fn read_frame(&self) -> Result<DSUFrame, DeviceError>;
45}
46
47impl<T> Device for Box<T>
48where
49    T: Device + ?Sized,
50{
51    fn initialize(&self) -> Result<(), DeviceError> {
52        (**self).initialize()
53    }
54
55    fn read_frame(&self) -> Result<DSUFrame, DeviceError> {
56        (**self).read_frame()
57    }
58}
59
60/// A trait defining shared behavior dependant on the frame type `F` between compatible devices.
61pub trait FrameDevice<F> {
62    fn to_dsu_frame(&self, frame: &F, gyro_disabled: bool) -> DSUFrame;
63    /// Test if a [`DeviceButton`](crate::devices::DeviceButton) is pressed.
64    fn is_device_button_pressed(&self, button: &DeviceButton, frame: &F) -> bool;
65}
66
67/// Device buttons not specific to any one device.
68#[derive(Debug, Clone)]
69pub enum DeviceButton {
70    /// Directional pad Left
71    DpadLeft,
72    /// Directional pad Down
73    DpadDown,
74    /// Directional pad Right
75    DpadRight,
76    /// Directional pad Up
77    DpadUp,
78    /// Start, options, etc.
79    Start,
80    /// Select, share, etc.
81    Select,
82    /// PS button, steam button, etc.
83    Guide,
84    /// QAM, Mic, etc.
85    Quaternary,
86    /// XB layout A
87    A,
88    /// XB layout B
89    B,
90    /// XB layout X
91    X,
92    /// XB layout Y
93    Y,
94    /// L1, Left Bumper
95    L1,
96    /// R1, Right Bumper
97    R1,
98    /// L2, Left Trigger
99    L2,
100    /// R2, Right Trigger
101    R2,
102    /// L3, Left stick click
103    L3,
104    /// R3, Right stick click
105    R3,
106    /// Triton only
107    L4,
108    /// Triton only
109    L5,
110    /// Triton only
111    R4,
112    /// Triton only
113    R5,
114    /// Triton only
115    LeftStickTouch,
116    /// Triton only
117    RightStickTouch,
118    /// Triton only
119    LeftPadTouch,
120    /// Triton only
121    RightPadTouch,
122    /// Triton only
123    LeftGrip,
124    /// Triton only
125    RightGrip,
126    /// Unknown button
127    Unknown,
128}
129
130impl FromStr for DeviceButton {
131    type Err = DeviceError;
132
133    fn from_str(s: &str) -> Result<Self, Self::Err> {
134        match s {
135            "dpad_left" => Ok(Self::DpadLeft),
136            "dpad_down" => Ok(Self::DpadDown),
137            "dpad_right" => Ok(Self::DpadRight),
138            "dpad_up" => Ok(Self::DpadUp),
139            "start" => Ok(Self::Start),
140            "select" => Ok(Self::Select),
141            "guide" => Ok(Self::Guide),
142            "quaternary" => Ok(Self::Quaternary),
143            "a" => Ok(Self::A),
144            "b" => Ok(Self::B),
145            "x" => Ok(Self::X),
146            "y" => Ok(Self::Y),
147            "l1" => Ok(Self::L1),
148            "r1" => Ok(Self::R1),
149            "l2" => Ok(Self::L2),
150            "r2" => Ok(Self::R2),
151            "l3" => Ok(Self::L3),
152            "r3" => Ok(Self::R3),
153            "l4" => Ok(Self::L4),
154            "l5" => Ok(Self::L5),
155            "r4" => Ok(Self::R4),
156            "r5" => Ok(Self::R5),
157            "left_stick_touch" => Ok(Self::LeftStickTouch),
158            "right_stick_touch" => Ok(Self::RightStickTouch),
159            "left_pad_touch" => Ok(Self::LeftPadTouch),
160            "right_pad_touch" => Ok(Self::RightPadTouch),
161            "left_grip" => Ok(Self::LeftGrip),
162            "right_grip" => Ok(Self::RightGrip),
163            _ => Err(DeviceError::InvalidDeviceButton(s.to_string())),
164        }
165    }
166}
167
168impl std::fmt::Display for DeviceButton {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        match self {
171            Self::DpadLeft => write!(f, "dpad_left"),
172            Self::DpadDown => write!(f, "dpad_down"),
173            Self::DpadRight => write!(f, "dpad_right"),
174            Self::DpadUp => write!(f, "dpad_up"),
175            Self::Start => write!(f, "start"),
176            Self::Select => write!(f, "select"),
177            Self::Guide => write!(f, "guide"),
178            Self::Quaternary => write!(f, "quaternary"),
179            Self::A => write!(f, "a"),
180            Self::B => write!(f, "b"),
181            Self::X => write!(f, "x"),
182            Self::Y => write!(f, "y"),
183            Self::L1 => write!(f, "l1"),
184            Self::R1 => write!(f, "r1"),
185            Self::L2 => write!(f, "l2"),
186            Self::R2 => write!(f, "r2"),
187            Self::L3 => write!(f, "l3"),
188            Self::R3 => write!(f, "r3"),
189            Self::L4 => write!(f, "l4"),
190            Self::L5 => write!(f, "l5"),
191            Self::R4 => write!(f, "r4"),
192            Self::R5 => write!(f, "r5"),
193            Self::LeftStickTouch => write!(f, "left_stick_touch"),
194            Self::RightStickTouch => write!(f, "right_stick_touch"),
195            Self::LeftPadTouch => write!(f, "left_pad_touch"),
196            Self::RightPadTouch => write!(f, "right_pad_touch"),
197            Self::LeftGrip => write!(f, "left_grip"),
198            Self::RightGrip => write!(f, "right_grip"),
199            Self::Unknown => write!(f, "unknown"),
200        }
201    }
202}
203
204/// Gyro activation toggle mode.
205///
206/// Any => At least one button must be pressed to activate gyro.
207///
208/// All => All buttons must be pressed to activate gyro.
209#[derive(Default, Debug, Clone)]
210pub enum GyroActivationMode {
211    #[default]
212    Any,
213    All,
214}
215
216impl FromStr for GyroActivationMode {
217    type Err = DeviceError;
218
219    fn from_str(s: &str) -> Result<Self, Self::Err> {
220        match s {
221            "any" => Ok(Self::Any),
222            "all" => Ok(Self::All),
223            _ => Err(DeviceError::InvalidGyroActivationMode(s.to_string())),
224        }
225    }
226}
227
228impl std::fmt::Display for GyroActivationMode {
229    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230        match self {
231            Self::Any => write!(f, "any"),
232            Self::All => write!(f, "all"),
233        }
234    }
235}
236
237/// Device configuration.
238///
239/// Defines configurable behavior within device adapters themselves.
240///
241/// For configuration affecting the behavior of the DSU server,
242/// see ['ServerConfig'](crate::server::ServerConfig)
243#[derive(Debug, Clone)]
244pub struct DeviceConfig {
245    /// Don't enable lizard mode when the device is dropped.
246    pub no_enable_lizard_mode_on_close: bool,
247    /// Inputs that must be pressed to send gyro data through the DSU server.
248    pub gyro_activation_inputs: Vec<DeviceButton>,
249    /// See ['GyroActivationMode'](crate::devices::GyroActivationMode)
250    pub gyro_activation_mode: GyroActivationMode,
251    /// Gyro deadzone in degrees per second. Values below this threshold are reported as zero.
252    pub gyro_deadzone: f32,
253    /// Scale factor applied to the pitch gyro axis.
254    pub gyro_pitch_scale: f32,
255    /// Scale factor applied to the yaw gyro axis.
256    pub gyro_yaw_scale: f32,
257    /// Scale factor applied to the roll gyro axis.
258    pub gyro_roll_scale: f32,
259}
260
261impl Default for DeviceConfig {
262    fn default() -> Self {
263        Self {
264            no_enable_lizard_mode_on_close: false,
265            gyro_activation_inputs: Vec::new(),
266            gyro_activation_mode: GyroActivationMode::default(),
267            gyro_deadzone: 0.0,
268            gyro_pitch_scale: 1.0,
269            gyro_yaw_scale: 1.0,
270            gyro_roll_scale: 1.0,
271        }
272    }
273}