1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Controller input handling module

use crate::Device;

/// Identifier for the position of the controller's stick.
///
/// They can be used 
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
#[repr(u8)]
pub enum StickPosition {
    Center = 0,
    Up = 1,
    UpRight = 2,
    Right = 3,
    DownRight = 4,
    Down = 5,
    DownLeft = 6,
    Left = 7,
    UpLeft = 8,
}

impl Default for StickPosition {
    fn default() -> Self {
        StickPosition::Center
    }
}

impl StickPosition {
    pub fn new() -> Self {
        StickPosition::default()
    }
    
    pub fn from_u8(position: u8) -> Option<Self> {
        match position {
            0 => Some(StickPosition::Center),
            1 => Some(StickPosition::Up),
            2 => Some(StickPosition::UpRight),
            3 => Some(StickPosition::Right),
            4 => Some(StickPosition::DownRight),
            5 => Some(StickPosition::Down),
            6 => Some(StickPosition::DownLeft),
            7 => Some(StickPosition::Left),
            8 => Some(StickPosition::UpLeft),
            _ => None,
        }
    }
}

/// A friendly representation of a game controller input state.
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct State {
    /// The position of the stick
    pub stick_position: StickPosition,
    /// Whether the main button is down
    pub button_1: bool,
    /// Whether the secondary trigger is down
    pub button_2: bool,
    /// Whether the back button is down
    pub button_back: bool,
    /// Whether the menu/context button is down
    pub button_menu: bool,
    /// Whether the Fuji (Atari) button is down
    pub button_fuji: bool,
    /// The absolute position of the rotational paddle,
    /// as a number between 0 and 1023
    pub roll: u16,
}

impl State {

    /// Obtain the controller's state from the full report packet.
    ///
    /// May panic if the data cannot represent an input report.
    pub fn from_report(data: &[u8]) -> Self {
        assert!(data.len() >= 6);
        assert_eq!(data[0], 1);

        msg_to_state(&data[1..])
    }
}

fn msg_to_state(msg: &[u8]) -> State {
    assert_eq!(msg.len(), 5);
    State {
        stick_position: StickPosition::from_u8(msg[2] >> 4).unwrap_or_default(),
        button_1: (msg[1] & 1) == 1,
        button_2: ((msg[1] >> 1) & 1) == 1,
        button_back: (msg[2] & 1) == 1,
        button_menu: ((msg[2] >> 1) & 1) == 1,
        button_fuji: ((msg[2] >> 2) & 1) == 1,
        roll: u16::from(msg[3]) + (u16::from(msg[4]) << 8),
    }
}


/// Process input reports in queue from the device
/// and return its current state.
///
/// This function does not block.
/// Might return `None` if no input report was received.
/// When this happens, game loops should preferably assume
/// no changes occurred to the controller's input state.
pub fn process_input<D>(mut device: D) -> Result<Option<State>, D::Error>
where
    D: Device,
{
    let mut buf = [0; 6];
    buf.fill(0);

    let mut has_msg = false;
    let mut last_amount = 0;
    device.set_blocking(false)?;
    let msg = loop {
    
        let amount = device.read(&mut buf)?;

        if amount == 0 && !has_msg {
            // queue empty, continue without message
            break &buf[0..0];
            
        } else if amount != 0 {
            has_msg = true;
            last_amount = amount;
            // consume more events while it doesn't block
            continue;
        }

        let msg = &buf[..last_amount];
        break msg;
    };

    if !msg.is_empty() {
        if msg.len() != 5 {
            eprintln!(
                "Special report #{:02X}: {:?}",
                buf[0],
                msg,
            );
            Ok(None)
        } else {
            Ok(Some(msg_to_state(msg)))
        }
    } else {
        Ok(None)
    }
}