vigem_rust/controller/
ds4.rs

1use bitflags::bitflags;
2use std::{
3    mem,
4    ops::{Deref, DerefMut},
5};
6
7bitflags! {
8    /// Represents the main digital buttons on a virtual DualShock 4 controller.
9    ///
10    /// These flags can be combined using bitwise OR to represent multiple button presses.
11    ///
12    /// # Examples
13    ///
14    /// ```
15    /// use vigem_rust::controller::ds4::Ds4Button;
16    ///
17    /// // Press the Triangle and Right Shoulder buttons.
18    /// let buttons = Ds4Button::TRIANGLE | Ds4Button::SHOULDER_RIGHT;
19    ///
20    /// assert!(buttons.contains(Ds4Button::TRIANGLE));
21    /// assert!(!buttons.contains(Ds4Button::CIRCLE));
22    /// ```
23    #[repr(transparent)]
24    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
25    pub struct Ds4Button: u16 {
26        const THUMB_RIGHT      = 1 << 15;
27        const THUMB_LEFT       = 1 << 14;
28        const OPTIONS          = 1 << 13;
29        const SHARE            = 1 << 12;
30        const TRIGGER_RIGHT    = 1 << 11;
31        const TRIGGER_LEFT     = 1 << 10;
32        const SHOULDER_RIGHT   = 1 << 9;
33        const SHOULDER_LEFT    = 1 << 8;
34        const TRIANGLE         = 1 << 7;
35        const CIRCLE           = 1 << 6;
36        const CROSS            = 1 << 5;
37        const SQUARE           = 1 << 4;
38    }
39}
40
41bitflags! {
42    /// Represents the special buttons (PS, Touchpad) on a virtual DualShock 4 controller.
43    #[repr(transparent)]
44    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
45    pub struct Ds4SpecialButton: u8 {
46        const PS           = 1 << 0;
47        const TOUCHPAD     = 1 << 1;
48    }
49}
50
51/// Represents the state of the D-Pad on a virtual DualShock 4 controller.
52///
53/// Unlike the main buttons, the D-Pad is represented by a single value, not a bitmask.
54/// The `Ds4Report` struct provides a helper method `set_dpad` to correctly apply this state.
55#[repr(u8)]
56#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
57pub enum Ds4Dpad {
58    North = 0,
59    NorthEast = 1,
60    East = 2,
61    SouthEast = 3,
62    South = 4,
63    SouthWest = 5,
64    West = 6,
65    NorthWest = 7,
66    #[default]
67    Neutral = 8,
68}
69
70/// Represents the standard input state of a virtual DualShock 4 controller.
71///
72/// An instance of this struct is sent to the bus via `TargetHandle::update` to
73/// update the controller's state.
74///
75/// # Examples
76///
77/// ```
78/// use vigem_rust::controller::ds4::{Ds4Report, Ds4Button, Ds4Dpad};
79///
80/// let mut report = Ds4Report::default();
81///
82/// // Set analog stick positions (128 is center)
83/// report.thumb_lx = 200; // Move right
84/// report.thumb_ly = 50;  // Move up
85///
86/// // Press the Cross button
87/// report.buttons = Ds4Button::CROSS.bits();
88///
89/// // Set the D-Pad to South
90/// report.set_dpad(Ds4Dpad::South);
91///
92/// // Pull the right trigger
93/// report.trigger_r = 255;
94/// ```
95#[repr(C)]
96#[derive(Debug, Clone, Copy)]
97pub struct Ds4Report {
98    /// Left thumbstick X-axis (0-255). 128 is center.
99    pub thumb_lx: u8,
100    /// Left thumbstick Y-axis (0-255). 128 is center.
101    pub thumb_ly: u8,
102    /// Right thumbstick X-axis (0-255). 128 is center.
103    pub thumb_rx: u8,
104    /// Right thumbstick Y-axis (0-255). 128 is center.
105    pub thumb_ry: u8,
106    /// A bitmask of the main digital buttons and D-Pad state. See `Ds4Button` and `set_dpad`.
107    pub buttons: u16,
108    /// A bitmask of the special buttons. See `Ds4SpecialButton`.
109    pub special: u8,
110    /// Left trigger value (0-255).
111    pub trigger_l: u8,
112    /// Right trigger value (0-255).
113    pub trigger_r: u8,
114}
115
116impl Ds4Report {
117    /// Sets the D-Pad state on the report.
118    ///
119    /// This helper correctly manipulates the lower 4 bits of the `buttons` field
120    /// to set the D-Pad state, leaving the other button flags untouched.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use vigem_rust::controller::ds4::{Ds4Report, Ds4Button, Ds4Dpad};
126    ///
127    /// let mut report = Ds4Report::default();
128    /// report.buttons = Ds4Button::SQUARE.bits();
129    /// report.set_dpad(Ds4Dpad::East);
130    ///
131    /// // The Square button is still set
132    /// assert!(report.buttons & Ds4Button::SQUARE.bits() != 0);
133    /// // The D-Pad bits are correctly set
134    /// assert_eq!(report.buttons & 0x000F, Ds4Dpad::East as u16);
135    /// ```
136    #[inline]
137    pub fn set_dpad(&mut self, dpad: Ds4Dpad) {
138        const DPAD_MASK: u16 = 0x000F;
139        self.buttons = (self.buttons & !DPAD_MASK) | (dpad as u16);
140    }
141}
142
143impl Default for Ds4Report {
144    fn default() -> Self {
145        let mut report = Self {
146            thumb_lx: 128,
147            thumb_ly: 128,
148            thumb_rx: 128,
149            thumb_ry: 128,
150            buttons: 0,
151            special: 0,
152            trigger_l: 0,
153            trigger_r: 0,
154        };
155        report.set_dpad(Ds4Dpad::Neutral);
156        report
157    }
158}
159
160// EXTENDED REPORT SECTION
161
162/// Represents a single packet of touchpad data for a DualShock 4 controller.
163///
164/// The DS4 can track up to two simultaneous touch points. This struct contains
165/// the state for both potential touches, along with a counter to sequence the packets.
166/// It is used within the [`Ds4ReportExData`] struct.
167#[repr(C, packed)]
168#[derive(Debug, Clone, Copy, Default)]
169pub struct Ds4Touch {
170    /// A timestamp or packet counter that increments with each new touch data packet,
171    /// used to sequence events.
172    pub packet_counter: u8,
173
174    /// Touch state and tracking ID for the first touch point. This is a bit-packed field:
175    /// - The most significant bit (MSB, `0x80`) indicates the contact state. This is
176    ///   "active-low", meaning `0` for touch down and `1` for touch up.
177    /// - The lower 7 bits (`0x7F`) are the tracking ID for the finger. This ID is unique
178    ///   for a single press-drag-release gesture and increments for a new press.
179    pub is_up_tracking_num_1: u8,
180
181    /// The raw X/Y coordinate data for the first touch point, with a resolution of **1920x943**.
182    ///
183    /// This is a packed 24-bit value encoding a 12-bit X and 12-bit Y coordinate. The middle
184    /// byte holds the 4 least significant bits of X and the 4 most significant bits of Y.
185    ///
186    /// You can unpack the coordinates like this:
187    /// ```rust,ignore
188    /// let x = (self.touch_data_1[0] as u16) | (((self.touch_data_1[1] & 0x0F) as u16) << 8);
189    /// let y = (((self.touch_data_1[1] & 0xF0) as u16) >> 4) | ((self.touch_data_1[2] as u16) << 4);
190    /// ```
191    pub touch_data_1: [u8; 3],
192
193    /// Touch state and tracking ID for the second touch point.
194    /// Formatted identically to `is_up_tracking_num_1`.
195    pub is_up_tracking_num_2: u8,
196
197    /// The raw X/Y coordinate data for the second touch point.
198    /// Formatted identically to `touch_data_1`.
199    pub touch_data_2: [u8; 3],
200}
201
202impl Ds4Touch {
203    /// Packs touchpad coordinates into the required 3-byte format.
204    #[inline]
205    fn pack_coords(buf: &mut [u8; 3], x: u16, y: u16) {
206        // Clamp values to the valid touchpad range (1920x943).
207        let x = x.min(1919);
208        let y = y.min(942);
209
210        // Pack the 12-bit X and 12-bit Y coordinates into 3 bytes.
211        buf[0] = (x & 0xFF) as u8;
212        buf[1] = (((x >> 8) & 0x0F) | ((y & 0x0F) << 4)) as u8;
213        buf[2] = ((y >> 4) & 0xFF) as u8;
214    }
215
216    /// Unpacks touchpad coordinates from the 3-byte format.
217    #[inline]
218    fn unpack_coords(buf: &[u8; 3]) -> (u16, u16) {
219        let x = (buf[0] as u16) | (((buf[1] & 0x0F) as u16) << 8);
220        let y = (((buf[1] & 0xF0) as u16) >> 4) | ((buf[2] as u16) << 4);
221        (x, y)
222    }
223
224    /// Sets the state for the first touch contact, abstracting away the bit-packing.
225    ///
226    /// # Arguments
227    /// * `is_down` - `true` if the finger is touching the pad, `false` otherwise.
228    /// * `tracking_num` - A unique ID for the finger gesture (0-127).
229    /// * `x` - The X coordinate (0-1919).
230    /// * `y` - The Y coordinate (0-942).
231    #[inline]
232    pub fn set_touch_1(&mut self, is_down: bool, tracking_num: u8, x: u16, y: u16) {
233        let up_bit = if is_down { 0 } else { 1 << 7 };
234        // Lower 7 bits are the tracking number
235        self.is_up_tracking_num_1 = up_bit | (tracking_num & 0x7F);
236        Self::pack_coords(&mut self.touch_data_1, x, y);
237    }
238
239    /// Sets the state for the second touch contact, abstracting away the bit-packing.
240    ///
241    /// # Arguments
242    /// * `is_down` - `true` if the finger is touching the pad, `false` otherwise.
243    /// * `tracking_num` - A unique ID for the finger gesture (0-127).
244    /// * `x` - The X coordinate (0-1919).
245    /// * `y` - The Y coordinate (0-942).
246    #[inline]
247    pub fn set_touch_2(&mut self, is_down: bool, tracking_num: u8, x: u16, y: u16) {
248        let up_bit = if is_down { 0 } else { 1 << 7 };
249        self.is_up_tracking_num_2 = up_bit | (tracking_num & 0x7F);
250        Self::pack_coords(&mut self.touch_data_2, x, y);
251    }
252
253    /// Returns the packet counter/timestamp for this touch event.
254    #[inline]
255    pub fn get_packet_counter(&self) -> u8 {
256        self.packet_counter
257    }
258
259    /// Returns `true` if the first touch point is active (finger is down).
260    #[inline]
261    pub fn get_is_down_1(&self) -> bool {
262        // MSB is 0 for down, 1 for up.
263        (self.is_up_tracking_num_1 & 0x80) == 0
264    }
265
266    /// Returns the tracking ID for the first touch point (0-127).
267    #[inline]
268    pub fn get_tracking_num_1(&self) -> u8 {
269        // Lower 7 bits are the tracking number.
270        self.is_up_tracking_num_1 & 0x7F
271    }
272
273    /// Returns the (X, Y) coordinates for the first touch point.
274    /// X is in the range 0-1919, Y is in the range 0-942.
275    #[inline]
276    pub fn get_coords_1(&self) -> (u16, u16) {
277        Self::unpack_coords(&self.touch_data_1)
278    }
279
280    /// Returns `true` if the second touch point is active (finger is down).
281    #[inline]
282    pub fn get_is_down_2(&self) -> bool {
283        // MSB is 0 for down, 1 for up.
284        (self.is_up_tracking_num_2 & 0x80) == 0
285    }
286
287    /// Returns the tracking ID for the second touch point (0-127).
288    #[inline]
289    pub fn get_tracking_num_2(&self) -> u8 {
290        // Lower 7 bits are the tracking number.
291        self.is_up_tracking_num_2 & 0x7F
292    }
293
294    /// Returns the (X, Y) coordinates for the second touch point.
295    /// X is in the range 0-1919, Y is in the range 0-942.
296    #[inline]
297    pub fn get_coords_2(&self) -> (u16, u16) {
298        Self::unpack_coords(&self.touch_data_2)
299    }
300}
301
302/// Represents the complete, extended input state of a virtual DualShock 4 controller.
303///
304/// This struct is used for advanced scenarios that require simulating motion controls
305/// (gyroscope and accelerometer) and detailed touchpad activity. It contains all the
306/// fields from the standard [`Ds4Report`] plus additional data.
307#[repr(C, packed)]
308#[derive(Clone, Copy)]
309pub struct Ds4ReportExData {
310    pub thumb_lx: u8,
311    pub thumb_ly: u8,
312    pub thumb_rx: u8,
313    pub thumb_ry: u8,
314    pub buttons: u16,
315    pub special: u8,
316    pub trigger_l: u8,
317    pub trigger_r: u8,
318    pub timestamp: u16,
319    pub battery_lvl: u8,
320    pub gyro_x: i16,
321    pub gyro_y: i16,
322    pub gyro_z: i16,
323    pub accel_x: i16,
324    pub accel_y: i16,
325    pub accel_z: i16,
326    pub _unknown1: [u8; 5],
327    pub battery_lvl_special: u8,
328    pub _unknown2: [u8; 2],
329    pub touch_packets_n: u8,
330    pub current_touch: Ds4Touch,
331    pub previous_touch: [Ds4Touch; 2],
332}
333
334impl Ds4ReportExData {
335    /// Returns an immutable reference to the standard [`Ds4Report`] portion of this extended report.
336    /// This is safe because [`Ds4ReportExData`] is `#[repr(C)]` and starts with the exact
337    /// same fields as [`Ds4Report`].
338    #[inline]
339    pub fn as_report(&self) -> &Ds4Report {
340        // SAFETY: The memory layout is guaranteed to match due to #[repr(C)] on both structs.
341        unsafe { &*(self as *const _ as *const Ds4Report) }
342    }
343
344    /// Returns a mutable reference to the standard [`Ds4Report`] portion of this extended report.
345    /// This allows using helpers like `set_dpad` on the extended report.
346    #[inline]
347    pub fn as_report_mut(&mut self) -> &mut Ds4Report {
348        // SAFETY: The memory layout is guaranteed to match due to #[repr(C)] on both structs.
349        unsafe { &mut *(self as *mut _ as *mut Ds4Report) }
350    }
351
352    /// A convenience method to set the D-Pad state on the extended report.
353    /// It correctly manipulates the `buttons` field.
354    pub fn set_dpad(&mut self, dpad: Ds4Dpad) {
355        self.as_report_mut().set_dpad(dpad);
356    }
357}
358
359impl Default for Ds4ReportExData {
360    /// Creates a new `Ds4ReportExData` with a valid default state (e.g., centered sticks).
361    fn default() -> Self {
362        let mut report: Self = unsafe { mem::zeroed() };
363        let base = Ds4Report::default();
364        report.thumb_lx = base.thumb_lx;
365        report.thumb_ly = base.thumb_ly;
366        report.thumb_rx = base.thumb_rx;
367        report.thumb_ry = base.thumb_ry;
368        report.buttons = base.buttons;
369        report.special = base.special;
370        report.trigger_l = base.trigger_l;
371        report.trigger_r = base.trigger_r;
372        report
373    }
374}
375
376/// An extended report for DualShock 4, including motion and touch data.
377///
378/// This is used for more advanced scenarios where you need to simulate more than basic inputs,
379/// such as gyroscope, accelerometer, or touchpad data. It is sent via the `update` method
380/// on a `TargetHandle<DualShock4>`.
381#[repr(C, packed)]
382pub union Ds4ReportEx {
383    pub report: Ds4ReportExData,
384    pub report_buffer: [u8; 63],
385}
386
387impl Deref for Ds4ReportEx {
388    type Target = Ds4ReportExData;
389
390    fn deref(&self) -> &Self::Target {
391        // SAFETY: Accessing the active union field is safe.
392        unsafe { &self.report }
393    }
394}
395
396impl DerefMut for Ds4ReportEx {
397    fn deref_mut(&mut self) -> &mut Self::Target {
398        // SAFETY: Accessing the active union field is safe.
399        unsafe { &mut self.report }
400    }
401}
402
403impl Clone for Ds4ReportEx {
404    fn clone(&self) -> Self {
405        *self
406    }
407}
408
409impl Copy for Ds4ReportEx {}
410
411impl Default for Ds4ReportEx {
412    /// Creates a new `Ds4ReportEx` with a valid default state.
413    fn default() -> Self {
414        Self {
415            report: Ds4ReportExData::default(),
416        }
417    }
418}
419
420#[repr(C)]
421#[derive(Debug, Clone, Copy, Default)]
422pub(crate) struct Ds4SubmitReport {
423    pub size: u32,
424    pub serial_no: u32,
425    pub report: Ds4Report,
426}
427
428#[repr(C, packed)]
429#[derive(Clone, Copy, Default)]
430pub(crate) struct Ds4SubmitReportEx {
431    pub size: u32,
432    pub serial_no: u32,
433    pub report: Ds4ReportEx,
434}
435
436// sanity check
437const _: () = {
438    assert!(
439        mem::size_of::<Ds4ReportExData>() == 60,
440        "Ds4ReportExData must be 60 bytes!"
441    );
442    assert!(
443        mem::size_of::<Ds4ReportEx>() == 63,
444        "Ds4ReportEx union must be 63 bytes!"
445    );
446};
447
448/// Represents an RGB color for the DualShock 4 lightbar.
449///
450/// This struct is part of a `Ds4Notification` and contains the color values
451/// sent by the host to be displayed on the controller's lightbar.
452///
453/// # Examples
454///
455/// ```
456/// use vigem_rust::controller::ds4::Ds4LightbarColor;
457///
458/// let color = Ds4LightbarColor::new(255, 0, 128); // A pink color
459/// assert_eq!(color.red, 255);
460/// ```
461#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
462pub struct Ds4LightbarColor {
463    /// The red component of the color (0-255).
464    pub red: u8,
465    /// The green component of the color (0-255).
466    pub green: u8,
467    /// The blue component of the color (0-255).
468    pub blue: u8,
469}
470
471impl Ds4LightbarColor {
472    #[inline]
473    pub fn new(r: u8, g: u8, b: u8) -> Self {
474        Self {
475            red: r,
476            green: g,
477            blue: b,
478        }
479    }
480}
481
482/// A notification received from the bus for a DualShock 4 target.
483///
484/// This contains feedback from the system (e.g., a game), like rumble and
485/// lightbar color commands. You can listen for these notifications using
486/// `TargetHandle<DualShock4>::register_notification`.
487///
488/// # Examples
489///
490/// ```no_run
491/// # use vigem_rust::{Client, target::DualShock4};
492/// # use std::error::Error;
493/// # fn main() -> Result<(), Box<dyn Error>> {
494/// # let client = Client::connect()?;
495/// # let ds4 = client.new_ds4_target().plugin()?;
496/// let receiver = ds4.register_notification()?;
497///
498/// // In a separate thread or an event loop:
499/// if let Ok(Ok(notification)) = receiver.recv() {
500///     println!("Rumble: large={}, small={}", notification.large_motor, notification.small_motor);
501///     println!("Lightbar: R={}, G={}, B={}",
502///         notification.lightbar.red,
503///         notification.lightbar.green,
504///         notification.lightbar.blue
505///     );
506/// }
507/// # Ok(())
508/// # }
509/// ```
510#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
511pub struct Ds4Notification {
512    /// Rumble strength for the large motor (0-255).
513    pub large_motor: u8,
514    /// Rumble strength for the small motor (0-255).
515    pub small_motor: u8,
516    /// The color for the controller's lightbar.
517    pub lightbar: Ds4LightbarColor,
518}
519
520/// A raw 64-byte output packet received from the bus for a DS4 target.
521///
522/// This is for advanced use cases where you need to parse the raw output report from
523/// the bus yourself, which may contain more detailed information than the standard
524/// `Ds4Notification`. Obtain a receiver for this type via
525/// `TargetHandle<DualShock4>::register_notification_raw_buffer`.
526#[derive(Debug, Clone, Copy, PartialEq, Eq)]
527pub struct Ds4OutputBuffer {
528    pub buf: [u8; 64],
529}