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}