when osu sound i guess

Compare changes

Choose any two refs to compare.

+1
.gitignore
··· 1 1 /target 2 + osuclack
+77
Cargo.lock
··· 26 26 source = "registry+https://github.com/rust-lang/crates.io-index" 27 27 checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 28 28 29 + [[package]] 30 + name = "bitvec" 31 + version = "1.0.1" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" 34 + dependencies = [ 35 + "funty", 36 + "radium", 37 + "tap", 38 + "wyz", 39 + ] 40 + 29 41 [[package]] 30 42 name = "block2" 31 43 version = "0.6.1" ··· 41 53 source = "registry+https://github.com/rust-lang/crates.io-index" 42 54 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 43 55 56 + [[package]] 57 + name = "cfg-if" 58 + version = "1.0.3" 59 + source = "registry+https://github.com/rust-lang/crates.io-index" 60 + checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" 61 + 62 + [[package]] 63 + name = "cfg_aliases" 64 + version = "0.2.1" 65 + source = "registry+https://github.com/rust-lang/crates.io-index" 66 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 67 + 44 68 [[package]] 45 69 name = "core-foundation" 46 70 version = "0.9.4" ··· 96 120 "objc2", 97 121 ] 98 122 123 + [[package]] 124 + name = "evdev" 125 + version = "0.13.2" 126 + source = "registry+https://github.com/rust-lang/crates.io-index" 127 + checksum = "25b686663ba7f08d92880ff6ba22170f1df4e83629341cba34cf82cd65ebea99" 128 + dependencies = [ 129 + "bitvec", 130 + "cfg-if", 131 + "libc", 132 + "nix", 133 + ] 134 + 99 135 [[package]] 100 136 name = "fastrand" 101 137 version = "2.3.0" 102 138 source = "registry+https://github.com/rust-lang/crates.io-index" 103 139 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 104 140 141 + [[package]] 142 + name = "funty" 143 + version = "2.0.0" 144 + source = "registry+https://github.com/rust-lang/crates.io-index" 145 + checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 146 + 105 147 [[package]] 106 148 name = "hound" 107 149 version = "3.5.1" ··· 141 183 source = "registry+https://github.com/rust-lang/crates.io-index" 142 184 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 143 185 186 + [[package]] 187 + name = "nix" 188 + version = "0.29.0" 189 + source = "registry+https://github.com/rust-lang/crates.io-index" 190 + checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 191 + dependencies = [ 192 + "bitflags", 193 + "cfg-if", 194 + "cfg_aliases", 195 + "libc", 196 + ] 197 + 144 198 [[package]] 145 199 name = "objc2" 146 200 version = "0.6.2" ··· 280 334 version = "0.1.0" 281 335 dependencies = [ 282 336 "device_query", 337 + "evdev", 283 338 "fastrand", 339 + "libc", 284 340 "quad-snd", 285 341 "trayicon", 286 342 "winapi", ··· 314 370 "winapi", 315 371 ] 316 372 373 + [[package]] 374 + name = "radium" 375 + version = "0.7.0" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 378 + 317 379 [[package]] 318 380 name = "readkey" 319 381 version = "0.2.2" ··· 335 397 "maybe-uninit", 336 398 ] 337 399 400 + [[package]] 401 + name = "tap" 402 + version = "1.0.1" 403 + source = "registry+https://github.com/rust-lang/crates.io-index" 404 + checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 405 + 338 406 [[package]] 339 407 name = "trayicon" 340 408 version = "0.3.0" ··· 435 503 source = "registry+https://github.com/rust-lang/crates.io-index" 436 504 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 437 505 506 + [[package]] 507 + name = "wyz" 508 + version = "0.5.1" 509 + source = "registry+https://github.com/rust-lang/crates.io-index" 510 + checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" 511 + dependencies = [ 512 + "tap", 513 + ] 514 + 438 515 [[package]] 439 516 name = "x11" 440 517 version = "2.21.0"
+12 -3
Cargo.toml
··· 3 3 version = "0.1.0" 4 4 edition = "2024" 5 5 6 + [features] 7 + tray = ["dep:trayicon", "dep:winapi"] 8 + 6 9 [dependencies] 7 - device_query = "4.0.1" 8 10 fastrand = "2.3.0" 9 11 quad-snd = "0.2.8" 10 - trayicon = "0.3.0" 12 + trayicon = { version = "0.3.0", optional = true } 11 13 winapi = { version = "0.3.9", features = [ 12 14 "winuser", 13 15 "windef", ··· 16 18 "libloaderapi", 17 19 "commctrl", 18 20 "basetsd", 19 - ] } 21 + ], optional = true } 22 + 23 + [target.'cfg(target_os = "linux")'.dependencies] 24 + evdev = "0.13.2" 25 + libc = "0.2" 26 + 27 + [target.'cfg(not(target_os = "linux"))'.dependencies] 28 + device_query = "4.0.1"
+254
src/input.rs
··· 1 + #[cfg(target_os = "linux")] 2 + mod 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"))] 124 + mod 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")] 241 + pub use linux::*; 242 + 243 + #[cfg(not(target_os = "linux"))] 244 + pub use fallback::*; 245 + 246 + #[cfg(target_os = "linux")] 247 + pub type Input = linux::EvdevInput; 248 + 249 + #[cfg(not(target_os = "linux"))] 250 + pub type Input = fallback::DeviceQueryInput; 251 + 252 + pub fn create_input() -> std::io::Result<Input> { 253 + Input::new() 254 + }
+33 -31
src/main.rs
··· 3 3 use std::collections::HashSet; 4 4 use std::{collections::HashMap, thread, time::Duration}; 5 5 6 - use device_query::{DeviceState, Keycode}; 7 6 use quad_snd::{AudioContext, Sound}; 7 + #[cfg(feature = "tray")] 8 8 use trayicon::{MenuBuilder, TrayIconBuilder}; 9 9 10 + mod input; 11 + use input::*; 12 + 13 + #[cfg(feature = "tray")] 10 14 #[derive(PartialEq, Clone)] 11 15 enum TrayEvents { 12 16 ShowMenu, ··· 15 19 } 16 20 17 21 fn main() { 22 + #[cfg(feature = "tray")] 18 23 let on_icon = trayicon::Icon::from_buffer( 19 24 Box::new(std::fs::read("osuclack.ico").unwrap()).leak(), 20 25 None, 21 26 None, 22 27 ) 23 28 .unwrap(); 29 + #[cfg(feature = "tray")] 24 30 let off_icon = trayicon::Icon::from_buffer( 25 31 Box::new(std::fs::read("osuclack_mute.ico").unwrap()).leak(), 26 32 None, 27 33 None, 28 34 ) 29 35 .unwrap(); 36 + #[cfg(feature = "tray")] 30 37 let (tray_tx, tray_rx) = std::sync::mpsc::channel(); 38 + #[cfg(feature = "tray")] 31 39 let mut tray_icon = TrayIconBuilder::new() 32 40 .tooltip("osuclack") 33 41 .icon(on_icon.clone()) ··· 41 49 .build() 42 50 .unwrap(); 43 51 44 - thread::spawn(move || { 52 + let _t = thread::spawn(move || { 45 53 let ctx = AudioContext::new(); 46 54 let sounds = std::fs::read_dir("sounds") 47 55 .expect("cant read sounds") ··· 60 68 let sound = sounds.get(name).unwrap(); 61 69 sound.play(&ctx, Default::default()); 62 70 }; 63 - let play_sound_for_key = |key: Keycode| match key { 64 - Keycode::CapsLock => play_sound("caps"), 65 - Keycode::Delete | Keycode::Backspace => play_sound("delete"), 66 - Keycode::Up | Keycode::Down | Keycode::Left | Keycode::Right => play_sound("movement"), 71 + let play_sound_for_key = |key: u16| match key { 72 + KEY_CAPSLOCK => play_sound("caps"), 73 + KEY_DELETE | KEY_BACKSPACE => play_sound("delete"), 74 + KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT => play_sound("movement"), 67 75 _ => { 68 76 let no = fastrand::u8(1..=4); 69 77 play_sound(&format!("press-{no}")); 70 78 } 71 79 }; 72 80 73 - let state = DeviceState::new(); 74 - let mut previously_held_keys = HashSet::<Keycode>::new(); 75 - let mut key_press_times = HashMap::<Keycode, std::time::Instant>::new(); 81 + let mut input = create_input().expect("Failed to initialize input system"); 82 + let mut previously_held_keys = HashSet::<u16>::new(); 83 + let mut key_press_times = HashMap::<u16, std::time::Instant>::new(); 76 84 let mut last_sound_time = std::time::Instant::now(); 77 85 let mut sound_enabled = true; 78 86 ··· 80 88 let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms 81 89 82 90 loop { 83 - let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect(); 91 + let currently_held_keys = input.query_keymap(); 84 92 85 93 // Check for toggle hotkey (Ctrl + Alt + L/R Shift + C) 86 94 let hotkey_combo = [ 87 - [Keycode::LControl, Keycode::RControl], // Either left or right control 88 - [Keycode::LAlt, Keycode::RAlt], // Either left or right alt 89 - [Keycode::LShift, Keycode::RShift], // Either left or right shift 90 - [Keycode::C, Keycode::C], // C key (duplicated for array consistency) 95 + [KEY_LEFTCTRL, KEY_RIGHTCTRL], // Either left or right control 96 + [KEY_LEFTALT, KEY_RIGHTALT], // Either left or right alt 97 + [KEY_LEFTSHIFT, KEY_RIGHTSHIFT], // Either left or right shift 98 + [KEY_C, KEY_C], // C key (duplicated for array consistency) 91 99 ]; 92 100 93 - let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| { 101 + let check_hotkey = |current: &HashSet<u16>, previous: &HashSet<u16>| { 94 102 hotkey_combo.iter().all(|key_group| { 95 103 key_group 96 104 .iter() ··· 101 109 let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys); 102 110 let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new()); 103 111 112 + #[cfg(feature = "tray")] 104 113 if hotkey_active && !hotkey_was_active { 105 114 tray_tx.send(TrayEvents::ToggleSound).unwrap(); 106 115 } 107 116 117 + if hotkey_active && !hotkey_was_active { 118 + sound_enabled = !sound_enabled; 119 + } 120 + 108 121 // handle tray events 122 + #[cfg(feature = "tray")] 109 123 if let Ok(event) = tray_rx.try_recv() { 110 124 match event { 111 125 TrayEvents::ToggleSound => { ··· 141 155 if last_sound_time.elapsed() >= repeat_interval { 142 156 let now = std::time::Instant::now(); 143 157 for key in &currently_held_keys { 144 - if is_modifier_key(key) { 158 + if is_modifier_key(*key) { 145 159 continue; 146 160 } 147 161 if let Some(press_time) = key_press_times.get(key) { ··· 165 179 } 166 180 }); 167 181 182 + #[cfg(feature = "tray")] 168 183 loop { 169 184 use std::mem; 170 185 use winapi::um::winuser; ··· 180 195 } 181 196 } 182 197 } 183 - } 184 - 185 - fn is_modifier_key(key: &Keycode) -> bool { 186 - matches!( 187 - key, 188 - Keycode::LShift 189 - | Keycode::RShift 190 - | Keycode::LControl 191 - | Keycode::RControl 192 - | Keycode::RAlt 193 - | Keycode::LAlt 194 - | Keycode::RMeta 195 - | Keycode::LMeta 196 - | Keycode::CapsLock 197 - ) 198 + #[cfg(not(feature = "tray"))] 199 + _t.join().unwrap(); 198 200 }