Skip to main content

vcs_classic_hid/
input.rs

1//! Controller input handling module
2
3use crate::Device;
4
5/// Identifier for the position of the controller's stick.
6///
7/// They can be used 
8#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
9#[repr(u8)]
10pub enum StickPosition {
11    Center = 0,
12    Up = 1,
13    UpRight = 2,
14    Right = 3,
15    DownRight = 4,
16    Down = 5,
17    DownLeft = 6,
18    Left = 7,
19    UpLeft = 8,
20}
21
22impl Default for StickPosition {
23    fn default() -> Self {
24        StickPosition::Center
25    }
26}
27
28impl StickPosition {
29    pub fn new() -> Self {
30        StickPosition::default()
31    }
32    
33    pub fn from_u8(position: u8) -> Option<Self> {
34        match position {
35            0 => Some(StickPosition::Center),
36            1 => Some(StickPosition::Up),
37            2 => Some(StickPosition::UpRight),
38            3 => Some(StickPosition::Right),
39            4 => Some(StickPosition::DownRight),
40            5 => Some(StickPosition::Down),
41            6 => Some(StickPosition::DownLeft),
42            7 => Some(StickPosition::Left),
43            8 => Some(StickPosition::UpLeft),
44            _ => None,
45        }
46    }
47}
48
49/// A friendly representation of a game controller input state.
50#[derive(Debug, Default, Copy, Clone, PartialEq)]
51pub struct State {
52    /// The position of the stick
53    pub stick_position: StickPosition,
54    /// Whether the main button is down
55    pub button_1: bool,
56    /// Whether the secondary trigger is down
57    pub button_2: bool,
58    /// Whether the back button is down
59    pub button_back: bool,
60    /// Whether the menu/context button is down
61    pub button_menu: bool,
62    /// Whether the Fuji (Atari) button is down
63    pub button_fuji: bool,
64    /// The absolute position of the rotational paddle,
65    /// as a number between 0 and 1023
66    pub roll: u16,
67}
68
69impl State {
70
71    /// Obtain the controller's state from the full report packet.
72    ///
73    /// May panic if the data cannot represent an input report.
74    pub fn from_report(data: &[u8]) -> Self {
75        assert!(data.len() >= 6);
76        assert_eq!(data[0], 1);
77
78        msg_to_state(&data[1..])
79    }
80}
81
82fn msg_to_state(msg: &[u8]) -> State {
83    assert_eq!(msg.len(), 5);
84    State {
85        stick_position: StickPosition::from_u8(msg[2] >> 4).unwrap_or_default(),
86        button_1: (msg[1] & 1) == 1,
87        button_2: ((msg[1] >> 1) & 1) == 1,
88        button_back: (msg[2] & 1) == 1,
89        button_menu: ((msg[2] >> 1) & 1) == 1,
90        button_fuji: ((msg[2] >> 2) & 1) == 1,
91        roll: u16::from(msg[3]) + (u16::from(msg[4]) << 8),
92    }
93}
94
95
96/// Process input reports in queue from the device
97/// and return its current state.
98///
99/// This function does not block.
100/// Might return `None` if no input report was received.
101/// When this happens, game loops should preferably assume
102/// no changes occurred to the controller's input state.
103pub fn process_input<D>(mut device: D) -> Result<Option<State>, D::Error>
104where
105    D: Device,
106{
107    let mut buf = [0; 6];
108    buf.fill(0);
109
110    let mut has_msg = false;
111    let mut last_amount = 0;
112    device.set_blocking(false)?;
113    let msg = loop {
114    
115        let amount = device.read(&mut buf)?;
116
117        if amount == 0 && !has_msg {
118            // queue empty, continue without message
119            break &buf[0..0];
120            
121        } else if amount != 0 {
122            has_msg = true;
123            last_amount = amount;
124            // consume more events while it doesn't block
125            continue;
126        }
127
128        let msg = &buf[..last_amount];
129        break msg;
130    };
131
132    if !msg.is_empty() {
133        if msg.len() != 5 {
134            eprintln!(
135                "Special report #{:02X}: {:?}",
136                buf[0],
137                msg,
138            );
139            Ok(None)
140        } else {
141            Ok(Some(msg_to_state(msg)))
142        }
143    } else {
144        Ok(None)
145    }
146}