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 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 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#[derive(Debug)]
203pub struct KeyboardGamepad {
204 receiver: flume::Receiver<GamepadEvent>,
205 is_running: Arc<AtomicBool>,
206}
207
208impl KeyboardGamepad {
209 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 let stdin = 0; let termios = Termios::from_fd(stdin).unwrap();
219 let mut new_termios = termios; new_termios.c_lflag &= !(termios::ICANON | termios::ECHO); 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]; 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(); });
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}