arci_gamepad_keyboard/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg(unix)]
3#![warn(missing_docs)]
4
5use std::{
6    collections::HashMap,
7    io::{self, Read, Write},
8    sync::{
9        atomic::{AtomicBool, Ordering::Relaxed},
10        Arc,
11    },
12};
13
14use arci::{gamepad::*, *};
15use termios::{tcsetattr, Termios};
16use tracing::{debug, error};
17
18#[derive(Debug)]
19struct State {
20    sender: flume::Sender<GamepadEvent>,
21    key_state: HashMap<char, bool>,
22    button_map: HashMap<char, Button>,
23}
24
25#[rustfmt::skip]
26const LEFT_STICK_KEYS: &[char] = &[
27    'q', 'w', 'e',
28    'a', 's', 'd',
29    'z', 'x', 'c',
30];
31#[rustfmt::skip]
32const RIGHT_STICK_KEYS: &[char] = &[
33    'u', 'i', 'o',
34    'j', 'k', 'l',
35    'm', ',', '.',
36];
37const DEFAULT_AXIS_VALUE: f64 = 0.3;
38
39impl State {
40    fn new(sender: flume::Sender<GamepadEvent>) -> Self {
41        let mut key_state = HashMap::new();
42        for ch in ('0'..='9').chain('a'..='z') {
43            key_state.insert(ch, false);
44        }
45        key_state.insert(',', false);
46        key_state.insert('.', false);
47
48        Self {
49            sender,
50            key_state,
51            button_map: button_map(),
52        }
53    }
54
55    fn right_stick(&mut self, sign_x: i8, sign_y: i8) {
56        self.send(GamepadEvent::AxisChanged(
57            Axis::RightStickX,
58            sign_x as f64 * DEFAULT_AXIS_VALUE,
59        ));
60        self.send(GamepadEvent::AxisChanged(
61            Axis::RightStickY,
62            sign_y as f64 * DEFAULT_AXIS_VALUE,
63        ));
64    }
65
66    fn left_stick(&mut self, sign_x: i8, sign_y: i8) {
67        self.send(GamepadEvent::AxisChanged(
68            Axis::LeftStickX,
69            sign_x as f64 * DEFAULT_AXIS_VALUE,
70        ));
71        self.send(GamepadEvent::AxisChanged(
72            Axis::LeftStickY,
73            sign_y as f64 * DEFAULT_AXIS_VALUE,
74        ));
75    }
76
77    fn send_left_stick(&mut self, ch: char) {
78        match ch {
79            'q' => {
80                self.left_stick(1, 1);
81            }
82            'w' => {
83                self.left_stick(0, 1);
84            }
85            'e' => {
86                self.left_stick(-1, 1);
87            }
88            'a' => {
89                self.left_stick(1, 0);
90            }
91            's' => {
92                self.left_stick(0, 0);
93            }
94            'd' => {
95                self.left_stick(-1, 0);
96            }
97            'z' => {
98                self.left_stick(1, -1);
99            }
100            'x' => {
101                self.left_stick(0, -1);
102            }
103            'c' => {
104                self.left_stick(-1, -1);
105            }
106            _ => unreachable!(),
107        }
108    }
109
110    fn send_right_stick(&mut self, ch: char) {
111        match ch {
112            'u' => {
113                self.right_stick(1, 1);
114            }
115            'i' => {
116                self.right_stick(0, 1);
117            }
118            'o' => {
119                self.right_stick(-1, 1);
120            }
121            'j' => {
122                self.right_stick(1, 0);
123            }
124            'k' => {
125                self.right_stick(0, 0);
126            }
127            'l' => {
128                self.right_stick(-1, 0);
129            }
130            'm' => {
131                self.right_stick(1, -1);
132            }
133            ',' => {
134                self.right_stick(0, -1);
135            }
136            '.' => {
137                self.right_stick(-1, -1);
138            }
139            _ => unreachable!(),
140        }
141    }
142
143    fn send_button(&mut self, ch: char) {
144        if let Some(&button) = self.button_map.get(&ch) {
145            let active = self.key_state.get_mut(&ch).unwrap();
146            *active = !*active;
147            if *active {
148                self.send(GamepadEvent::ButtonPressed(button));
149            } else {
150                self.send(GamepadEvent::ButtonReleased(button));
151            }
152        }
153    }
154
155    fn send(&self, event: GamepadEvent) {
156        debug!("sending {event:?}");
157        if let Err(e) = self.sender.send(event) {
158            error!("{e}");
159        }
160    }
161
162    fn send_event(&mut self, ch: char) {
163        if LEFT_STICK_KEYS.contains(&ch) {
164            self.send_left_stick(ch);
165        } else if RIGHT_STICK_KEYS.contains(&ch) {
166            self.send_right_stick(ch);
167        } else {
168            self.send_button(ch);
169        }
170    }
171}
172
173fn button_map() -> HashMap<char, Button> {
174    let mut map = HashMap::new();
175    map.insert('1', Button::LeftTrigger);
176    map.insert('2', Button::LeftTrigger2);
177    map.insert('3', Button::LeftThumb);
178
179    map.insert('6', Button::Select);
180    map.insert('7', Button::Start);
181
182    map.insert('8', Button::RightTrigger);
183    map.insert('9', Button::RightTrigger2);
184    map.insert('0', Button::RightThumb);
185
186    // <^>v
187    map.insert('5', Button::DPadUp);
188    map.insert('r', Button::DPadLeft);
189    map.insert('t', Button::DPadRight);
190    map.insert('f', Button::DPadDown);
191
192    // △○□x
193    map.insert('y', Button::North);
194    map.insert('g', Button::West);
195    map.insert('h', Button::East);
196    map.insert('b', Button::South);
197
198    map
199}
200
201/// [`arci::Gamepad`] implementation for keyboard.
202#[derive(Debug)]
203pub struct KeyboardGamepad {
204    receiver: flume::Receiver<GamepadEvent>,
205    is_running: Arc<AtomicBool>,
206}
207
208impl KeyboardGamepad {
209    /// Creates a new `KeyboardGamepad`.
210    pub fn new() -> Self {
211        let (sender, receiver) = flume::unbounded();
212        let is_running = Arc::new(AtomicBool::new(true));
213        let is_running_cloned = is_running.clone();
214
215        // Based on https://stackoverflow.com/questions/26321592/how-can-i-read-one-character-from-stdin-without-having-to-hit-enter
216        let stdin = 0; // couldn't get std::os::unix::io::FromRawFd to work
217                       // on /dev/stdin or /dev/tty
218        let termios = Termios::from_fd(stdin).unwrap();
219        let mut new_termios = termios; // make a mutable copy of termios
220                                       // that we will modify
221        new_termios.c_lflag &= !(termios::ICANON | termios::ECHO); // no echo and canonical mode
222        tcsetattr(stdin, termios::TCSANOW, &new_termios).unwrap();
223        let mut reader = io::stdin();
224        io::stdout().lock().flush().unwrap();
225        std::thread::spawn(move || {
226            let mut state = State::new(sender);
227            while is_running_cloned.load(Relaxed) {
228                let mut buffer = [0; 1]; // read exactly one byte
229                reader.read_exact(&mut buffer).unwrap();
230                let b = buffer[0];
231                if b.is_ascii() {
232                    state.send_event(b as char);
233                    continue;
234                }
235                debug!("non-ascii input: {b}");
236            }
237            tcsetattr(stdin, termios::TCSANOW, &termios).unwrap(); // reset the stdin to
238        });
239
240        Self {
241            receiver,
242            is_running,
243        }
244    }
245}
246
247impl Default for KeyboardGamepad {
248    fn default() -> Self {
249        Self::new()
250    }
251}
252
253#[async_trait]
254impl Gamepad for KeyboardGamepad {
255    async fn next_event(&self) -> GamepadEvent {
256        match self.receiver.recv_async().await {
257            Ok(e) => e,
258            Err(e) => {
259                error!("recv error: {e}");
260                GamepadEvent::Unknown
261            }
262        }
263    }
264
265    fn stop(&self) {
266        self.is_running.store(false, Relaxed);
267    }
268}
269
270impl Drop for KeyboardGamepad {
271    fn drop(&mut self) {
272        self.stop();
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use std::time::Duration;
279
280    use super::*;
281
282    const TIMEOUT: Duration = Duration::from_secs(1);
283
284    #[test]
285    fn button() {
286        let (sender, receiver) = flume::unbounded();
287        let mut state = State::new(sender);
288        let button_map = button_map();
289
290        for (&ch, button) in &button_map {
291            state.send_event(ch);
292            assert!(
293                matches!(receiver.recv_timeout(TIMEOUT).unwrap(), GamepadEvent::ButtonPressed(b) if b == *button)
294            );
295        }
296        for (&ch, button) in &button_map {
297            state.send_event(ch);
298            assert!(
299                matches!(receiver.recv_timeout(TIMEOUT).unwrap(), GamepadEvent::ButtonReleased(b) if b == *button)
300            );
301        }
302    }
303
304    #[test]
305    fn axis() {
306        let (sender, receiver) = flume::unbounded();
307        let mut state = State::new(sender);
308
309        for &ch in LEFT_STICK_KEYS {
310            state.send_event(ch);
311            assert!(matches!(
312                receiver.recv_timeout(TIMEOUT).unwrap(),
313                GamepadEvent::AxisChanged(Axis::LeftStickX, _)
314            ));
315            assert!(matches!(
316                receiver.recv_timeout(TIMEOUT).unwrap(),
317                GamepadEvent::AxisChanged(Axis::LeftStickY, _)
318            ));
319        }
320        for &ch in RIGHT_STICK_KEYS {
321            state.send_event(ch);
322            assert!(matches!(
323                receiver.recv_timeout(TIMEOUT).unwrap(),
324                GamepadEvent::AxisChanged(Axis::RightStickX, _)
325            ));
326            assert!(matches!(
327                receiver.recv_timeout(TIMEOUT).unwrap(),
328                GamepadEvent::AxisChanged(Axis::RightStickY, _)
329            ));
330        }
331    }
332}