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