1#[cfg(target_os = "linux")]
2mod linux {
3 use evdev::{Device, EventSummary, KeyCode};
4 use std::collections::HashSet;
5 use std::fs::{self, OpenOptions};
6 use std::os::unix::io::AsRawFd;
7 use std::path::Path;
8
9 pub struct EvdevInput {
10 devices: Vec<Device>,
11 held_keys: HashSet<u16>,
12 }
13
14 impl EvdevInput {
15 pub fn new() -> std::io::Result<Self> {
16 let mut devices = Vec::new();
17
18 // Find all keyboard devices in /dev/input
19 let input_dir = Path::new("/dev/input");
20 if let Ok(entries) = fs::read_dir(input_dir) {
21 for entry in entries.flatten() {
22 let path = entry.path();
23 if let Some(name) = path.file_name() {
24 if name.to_string_lossy().starts_with("event") {
25 if let Ok(file) = OpenOptions::new().read(true).open(&path) {
26 // Set non-blocking mode
27 let fd = file.as_raw_fd();
28 unsafe {
29 let flags = libc::fcntl(fd, libc::F_GETFL, 0);
30 if flags != -1 {
31 libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
32 }
33 }
34
35 if let Ok(device) = Device::from_fd(file.into()) {
36 // Check if this device supports keyboard events
37 if device.supported_keys().map_or(false, |keys| {
38 keys.contains(KeyCode::KEY_A) || keys.contains(KeyCode::KEY_SPACE)
39 }) {
40 devices.push(device);
41 }
42 }
43 }
44 }
45 }
46 }
47 }
48
49 if devices.is_empty() {
50 eprintln!("Warning: No keyboard devices found. Make sure you have read permissions for /dev/input/event* devices.");
51 eprintln!("You may need to add your user to the 'input' group or run with appropriate permissions.");
52 }
53
54 Ok(Self {
55 devices,
56 held_keys: HashSet::new(),
57 })
58 }
59
60 pub fn query_keymap(&mut self) -> HashSet<u16> {
61 // Process events from all devices
62 for device in &mut self.devices {
63 while let Ok(events) = device.fetch_events() {
64 for event in events {
65 if let EventSummary::Key(_, key, value) = event.destructure() {
66 let code = key.code();
67 match value {
68 0 => {
69 // Key released
70 self.held_keys.remove(&code);
71 }
72 1 | 2 => {
73 // Key pressed (1) or repeated (2)
74 self.held_keys.insert(code);
75 }
76 _ => {}
77 }
78 }
79 }
80 }
81 }
82
83 self.held_keys.clone()
84 }
85 }
86
87 // Key code constants
88 #[allow(dead_code)]
89 pub const KEY_A: u16 = KeyCode::KEY_A.code();
90 pub const KEY_C: u16 = KeyCode::KEY_C.code();
91 pub const KEY_CAPSLOCK: u16 = KeyCode::KEY_CAPSLOCK.code();
92 pub const KEY_DELETE: u16 = KeyCode::KEY_DELETE.code();
93 pub const KEY_BACKSPACE: u16 = KeyCode::KEY_BACKSPACE.code();
94 pub const KEY_UP: u16 = KeyCode::KEY_UP.code();
95 pub const KEY_DOWN: u16 = KeyCode::KEY_DOWN.code();
96 pub const KEY_LEFT: u16 = KeyCode::KEY_LEFT.code();
97 pub const KEY_RIGHT: u16 = KeyCode::KEY_RIGHT.code();
98 pub const KEY_LEFTSHIFT: u16 = KeyCode::KEY_LEFTSHIFT.code();
99 pub const KEY_RIGHTSHIFT: u16 = KeyCode::KEY_RIGHTSHIFT.code();
100 pub const KEY_LEFTCTRL: u16 = KeyCode::KEY_LEFTCTRL.code();
101 pub const KEY_RIGHTCTRL: u16 = KeyCode::KEY_RIGHTCTRL.code();
102 pub const KEY_LEFTALT: u16 = KeyCode::KEY_LEFTALT.code();
103 pub const KEY_RIGHTALT: u16 = KeyCode::KEY_RIGHTALT.code();
104 pub const KEY_LEFTMETA: u16 = KeyCode::KEY_LEFTMETA.code();
105 pub const KEY_RIGHTMETA: u16 = KeyCode::KEY_RIGHTMETA.code();
106
107 pub fn is_modifier_key(key: u16) -> bool {
108 matches!(
109 key,
110 KEY_LEFTSHIFT
111 | KEY_RIGHTSHIFT
112 | KEY_LEFTCTRL
113 | KEY_RIGHTCTRL
114 | KEY_RIGHTALT
115 | KEY_LEFTALT
116 | KEY_RIGHTMETA
117 | KEY_LEFTMETA
118 | KEY_CAPSLOCK
119 )
120 }
121}
122
123#[cfg(not(target_os = "linux"))]
124mod fallback {
125 use device_query::{DeviceQuery, DeviceState, Keycode};
126 use std::collections::HashSet;
127
128 pub struct DeviceQueryInput {
129 state: DeviceState,
130 }
131
132 impl DeviceQueryInput {
133 pub fn new() -> Self {
134 Self {
135 state: DeviceState::new(),
136 }
137 }
138
139 pub fn query_keymap(&mut self) -> HashSet<u16> {
140 self.state
141 .query_keymap()
142 .into_iter()
143 .map(keycode_to_u16)
144 .collect()
145 }
146 }
147
148 fn keycode_to_u16(keycode: Keycode) -> u16 {
149 // Map device_query keycodes to arbitrary u16 values
150 // We use values that won't collide with Linux key codes
151 match keycode {
152 Keycode::A => 1000,
153 Keycode::B => 1001,
154 Keycode::C => 1002,
155 Keycode::D => 1003,
156 Keycode::E => 1004,
157 Keycode::F => 1005,
158 Keycode::G => 1006,
159 Keycode::H => 1007,
160 Keycode::I => 1008,
161 Keycode::J => 1009,
162 Keycode::K => 1010,
163 Keycode::L => 1011,
164 Keycode::M => 1012,
165 Keycode::N => 1013,
166 Keycode::O => 1014,
167 Keycode::P => 1015,
168 Keycode::Q => 1016,
169 Keycode::R => 1017,
170 Keycode::S => 1018,
171 Keycode::T => 1019,
172 Keycode::U => 1020,
173 Keycode::V => 1021,
174 Keycode::W => 1022,
175 Keycode::X => 1023,
176 Keycode::Y => 1024,
177 Keycode::Z => 1025,
178 Keycode::CapsLock => 1026,
179 Keycode::Delete => 1027,
180 Keycode::Backspace => 1028,
181 Keycode::Up => 1029,
182 Keycode::Down => 1030,
183 Keycode::Left => 1031,
184 Keycode::Right => 1032,
185 Keycode::LShift => 1033,
186 Keycode::RShift => 1034,
187 Keycode::LControl => 1035,
188 Keycode::RControl => 1036,
189 Keycode::LAlt => 1037,
190 Keycode::RAlt => 1038,
191 Keycode::LMeta => 1039,
192 Keycode::RMeta => 1040,
193 _ => {
194 // For any other key, use a hash of the debug string
195 use std::collections::hash_map::DefaultHasher;
196 use std::hash::{Hash, Hasher};
197 let mut hasher = DefaultHasher::new();
198 format!("{:?}", keycode).hash(&mut hasher);
199 (hasher.finish() as u16).wrapping_add(2000)
200 }
201 }
202 }
203
204 // Key code constants - matching the values in keycode_to_u16
205 #[allow(dead_code)]
206 pub const KEY_A: u16 = 1000;
207 pub const KEY_C: u16 = 1002;
208 pub const KEY_CAPSLOCK: u16 = 1026;
209 pub const KEY_DELETE: u16 = 1027;
210 pub const KEY_BACKSPACE: u16 = 1028;
211 pub const KEY_UP: u16 = 1029;
212 pub const KEY_DOWN: u16 = 1030;
213 pub const KEY_LEFT: u16 = 1031;
214 pub const KEY_RIGHT: u16 = 1032;
215 pub const KEY_LEFTSHIFT: u16 = 1033;
216 pub const KEY_RIGHTSHIFT: u16 = 1034;
217 pub const KEY_LEFTCTRL: u16 = 1035;
218 pub const KEY_RIGHTCTRL: u16 = 1036;
219 pub const KEY_LEFTALT: u16 = 1037;
220 pub const KEY_RIGHTALT: u16 = 1038;
221 pub const KEY_LEFTMETA: u16 = 1039;
222 pub const KEY_RIGHTMETA: u16 = 1040;
223
224 pub fn is_modifier_key(key: u16) -> bool {
225 matches!(
226 key,
227 KEY_LEFTSHIFT
228 | KEY_RIGHTSHIFT
229 | KEY_LEFTCTRL
230 | KEY_RIGHTCTRL
231 | KEY_RIGHTALT
232 | KEY_LEFTALT
233 | KEY_RIGHTMETA
234 | KEY_LEFTMETA
235 | KEY_CAPSLOCK
236 )
237 }
238}
239
240#[cfg(target_os = "linux")]
241pub use linux::*;
242
243#[cfg(not(target_os = "linux"))]
244pub use fallback::*;
245
246#[cfg(target_os = "linux")]
247pub type Input = linux::EvdevInput;
248
249#[cfg(not(target_os = "linux"))]
250pub type Input = fallback::DeviceQueryInput;
251
252pub fn create_input() -> std::io::Result<Input> {
253 Input::new()
254}