when osu sound i guess
1#![windows_subsystem = "windows"] 2 3use std::collections::HashSet; 4use std::{collections::HashMap, thread, time::Duration}; 5 6use device_query::{DeviceState, Keycode}; 7use quad_snd::{AudioContext, Sound}; 8use trayicon::{MenuBuilder, TrayIconBuilder}; 9 10#[derive(PartialEq, Clone)] 11enum TrayEvents { 12 ShowMenu, 13 ToggleSound, 14 Quit, 15} 16 17fn main() { 18 let on_icon = trayicon::Icon::from_buffer( 19 Box::new(std::fs::read("osuclack.ico").unwrap()).leak(), 20 None, 21 None, 22 ) 23 .unwrap(); 24 let off_icon = trayicon::Icon::from_buffer( 25 Box::new(std::fs::read("osuclack_mute.ico").unwrap()).leak(), 26 None, 27 None, 28 ) 29 .unwrap(); 30 let (tray_tx, tray_rx) = std::sync::mpsc::channel(); 31 let mut tray_icon = TrayIconBuilder::new() 32 .tooltip("osuclack") 33 .icon(on_icon.clone()) 34 .on_click(TrayEvents::ToggleSound) 35 .on_right_click(TrayEvents::ShowMenu) 36 .menu(MenuBuilder::new().item("quit", TrayEvents::Quit)) 37 .sender({ 38 let tray_tx = tray_tx.clone(); 39 move |e| tray_tx.send(e.clone()).unwrap() 40 }) 41 .build() 42 .unwrap(); 43 44 thread::spawn(move || { 45 let ctx = AudioContext::new(); 46 let sounds = std::fs::read_dir("sounds") 47 .expect("cant read sounds") 48 .flat_map(|f| { 49 let p = f.ok()?.path(); 50 let n = p.file_stem()?.to_string_lossy().into_owned(); 51 (n != "LICENSE" && n != "README").then(|| { 52 ( 53 n, 54 Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")), 55 ) 56 }) 57 }) 58 .collect::<HashMap<String, Sound>>(); 59 let play_sound = |name: &str| { 60 let sound = sounds.get(name).unwrap(); 61 sound.play(&ctx, Default::default()); 62 }; 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"), 67 _ => { 68 let no = fastrand::u8(1..=4); 69 play_sound(&format!("press-{no}")); 70 } 71 }; 72 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(); 76 let mut last_sound_time = std::time::Instant::now(); 77 let mut sound_enabled = true; 78 79 let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat 80 let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms 81 82 loop { 83 let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect(); 84 85 // Check for toggle hotkey (Ctrl + Alt + L/R Shift + C) 86 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) 91 ]; 92 93 let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| { 94 hotkey_combo.iter().all(|key_group| { 95 key_group 96 .iter() 97 .any(|key| current.contains(key) || previous.contains(key)) 98 }) 99 }; 100 101 let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys); 102 let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new()); 103 104 if hotkey_active && !hotkey_was_active { 105 tray_tx.send(TrayEvents::ToggleSound).unwrap(); 106 } 107 108 // handle tray events 109 if let Ok(event) = tray_rx.try_recv() { 110 match event { 111 TrayEvents::ToggleSound => { 112 sound_enabled = !sound_enabled; 113 tray_icon 114 .set_icon(sound_enabled.then_some(&on_icon).unwrap_or(&off_icon)) 115 .unwrap(); 116 } 117 TrayEvents::Quit => { 118 std::process::exit(0); 119 } 120 TrayEvents::ShowMenu => { 121 tray_icon.show_menu().unwrap(); 122 } 123 } 124 } 125 126 // Only process sound logic if sounds are enabled 127 if sound_enabled { 128 // Track when keys were first pressed 129 for key in &currently_held_keys { 130 if !previously_held_keys.contains(key) { 131 // Key just pressed, record the time and play initial sound 132 key_press_times.insert(*key, std::time::Instant::now()); 133 play_sound_for_key(*key); 134 } 135 } 136 137 // Remove timing info for released keys 138 key_press_times.retain(|key, _| currently_held_keys.contains(key)); 139 140 // Play repeating sounds every 50ms, but only after initial delay 141 if last_sound_time.elapsed() >= repeat_interval { 142 let now = std::time::Instant::now(); 143 for key in &currently_held_keys { 144 if is_modifier_key(key) { 145 continue; 146 } 147 if let Some(press_time) = key_press_times.get(key) { 148 // Only repeat if key has been held longer than initial delay 149 if now.duration_since(*press_time) >= initial_delay { 150 play_sound_for_key(*key); 151 } 152 } 153 } 154 last_sound_time = now; 155 } 156 } else { 157 // Clear key press times when sounds are disabled to avoid stale data 158 key_press_times.clear(); 159 } 160 161 previously_held_keys = currently_held_keys; 162 163 // Buffer inputs at lower interval (5ms) 164 thread::sleep(Duration::from_millis(5)); 165 } 166 }); 167 168 loop { 169 use std::mem; 170 use winapi::um::winuser; 171 172 unsafe { 173 let mut msg = mem::MaybeUninit::uninit(); 174 let bret = winuser::GetMessageA(msg.as_mut_ptr(), 0 as _, 0, 0); 175 if bret > 0 { 176 winuser::TranslateMessage(msg.as_ptr()); 177 winuser::DispatchMessageA(msg.as_ptr()); 178 } else { 179 break; 180 } 181 } 182 } 183} 184 185fn 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}