when osu sound i guess

fix: sync icon properly on sound toggle

ptr.pet a5355bf0 f274fa4f

verified
Changed files
+119 -139
src
+119 -139
src/main.rs
··· 1 #![windows_subsystem = "windows"] 2 3 use std::collections::HashSet; 4 - use std::{ 5 - collections::HashMap, 6 - sync::{ 7 - Arc, 8 - atomic::{AtomicBool, Ordering}, 9 - }, 10 - thread, 11 - time::Duration, 12 - }; 13 14 use device_query::{DeviceState, Keycode}; 15 use quad_snd::{AudioContext, Sound}; ··· 23 } 24 25 fn main() { 26 - // Shared state for sound toggle 27 - let sounds_enabled = Arc::new(AtomicBool::new(true)); 28 - let sounds_enabled_clone = Arc::clone(&sounds_enabled); 29 - 30 - // Start audio/key processing thread 31 - thread::spawn(move || { 32 - audio_key_thread(sounds_enabled_clone); 33 - }); 34 - 35 let on_icon = trayicon::Icon::from_buffer( 36 Box::new(std::fs::read("osuclack.ico").unwrap()).leak(), 37 None, ··· 51 .on_click(TrayEvents::ToggleSound) 52 .on_right_click(TrayEvents::ShowMenu) 53 .menu(MenuBuilder::new().item("quit", TrayEvents::Quit)) 54 - .sender(move |e| tray_tx.send(e.clone()).unwrap()) 55 .build() 56 .unwrap(); 57 58 thread::spawn(move || { 59 loop { 60 - let Ok(event) = tray_rx.recv() else { 61 - continue; 62 }; 63 - match event { 64 - TrayEvents::ToggleSound => { 65 - let new = !sounds_enabled.load(Ordering::Relaxed); 66 - sounds_enabled.store(new, Ordering::Relaxed); 67 - tray_icon 68 - .set_icon(new.then_some(&on_icon).unwrap_or(&off_icon)) 69 - .unwrap(); 70 } 71 - TrayEvents::Quit => { 72 - std::process::exit(0); 73 } 74 - TrayEvents::ShowMenu => { 75 - tray_icon.show_menu().unwrap(); 76 } 77 } 78 } 79 }); 80 ··· 92 break; 93 } 94 } 95 - } 96 - } 97 - 98 - fn audio_key_thread(sounds_enabled: Arc<AtomicBool>) { 99 - let ctx = AudioContext::new(); 100 - let sounds = std::fs::read_dir("sounds") 101 - .expect("cant read sounds") 102 - .flat_map(|f| { 103 - let p = f.ok()?.path(); 104 - let n = p.file_stem()?.to_string_lossy().into_owned(); 105 - (n != "LICENSE" && n != "README").then(|| { 106 - ( 107 - n, 108 - Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")), 109 - ) 110 - }) 111 - }) 112 - .collect::<HashMap<String, Sound>>(); 113 - let play_sound = |name: &str| { 114 - let sound = sounds.get(name).unwrap(); 115 - sound.play(&ctx, Default::default()); 116 - }; 117 - let play_sound_for_key = |key: Keycode| match key { 118 - Keycode::CapsLock => play_sound("caps"), 119 - Keycode::Delete | Keycode::Backspace => play_sound("delete"), 120 - Keycode::Up | Keycode::Down | Keycode::Left | Keycode::Right => play_sound("movement"), 121 - _ => { 122 - let no = fastrand::u8(1..=4); 123 - play_sound(&format!("press-{no}")); 124 - } 125 - }; 126 - 127 - let state = DeviceState::new(); 128 - let mut previously_held_keys = HashSet::<Keycode>::new(); 129 - let mut key_press_times = HashMap::<Keycode, std::time::Instant>::new(); 130 - let mut last_sound_time = std::time::Instant::now(); 131 - 132 - let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat 133 - let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms 134 - 135 - loop { 136 - let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect(); 137 - 138 - // Check for toggle hotkey (Ctrl + Alt + L/R Shift + C) 139 - let hotkey_combo = [ 140 - [Keycode::LControl, Keycode::RControl], // Either left or right control 141 - [Keycode::LAlt, Keycode::RAlt], // Either left or right alt 142 - [Keycode::LShift, Keycode::RShift], // Either left or right shift 143 - [Keycode::C, Keycode::C], // C key (duplicated for array consistency) 144 - ]; 145 - 146 - let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| { 147 - hotkey_combo.iter().all(|key_group| { 148 - key_group 149 - .iter() 150 - .any(|key| current.contains(key) || previous.contains(key)) 151 - }) 152 - }; 153 - 154 - let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys); 155 - let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new()); 156 - 157 - let mut sounds_currently_enabled = sounds_enabled.load(Ordering::Relaxed); 158 - if hotkey_active && !hotkey_was_active { 159 - sounds_currently_enabled = !sounds_currently_enabled; 160 - sounds_enabled.store(sounds_currently_enabled, Ordering::Relaxed); 161 - } 162 - 163 - // Only process sound logic if sounds are enabled 164 - if sounds_currently_enabled { 165 - // Track when keys were first pressed 166 - for key in &currently_held_keys { 167 - if !previously_held_keys.contains(key) { 168 - // Key just pressed, record the time and play initial sound 169 - key_press_times.insert(*key, std::time::Instant::now()); 170 - play_sound_for_key(*key); 171 - } 172 - } 173 - 174 - // Remove timing info for released keys 175 - key_press_times.retain(|key, _| currently_held_keys.contains(key)); 176 - 177 - // Play repeating sounds every 50ms, but only after initial delay 178 - if last_sound_time.elapsed() >= repeat_interval { 179 - let now = std::time::Instant::now(); 180 - for key in &currently_held_keys { 181 - if is_modifier_key(key) { 182 - continue; 183 - } 184 - if let Some(press_time) = key_press_times.get(key) { 185 - // Only repeat if key has been held longer than initial delay 186 - if now.duration_since(*press_time) >= initial_delay { 187 - play_sound_for_key(*key); 188 - } 189 - } 190 - } 191 - last_sound_time = now; 192 - } 193 - } else { 194 - // Clear key press times when sounds are disabled to avoid stale data 195 - key_press_times.clear(); 196 - } 197 - 198 - previously_held_keys = currently_held_keys; 199 - 200 - // Buffer inputs at lower interval (5ms) 201 - thread::sleep(Duration::from_millis(5)); 202 } 203 } 204
··· 1 #![windows_subsystem = "windows"] 2 3 use std::collections::HashSet; 4 + use std::{collections::HashMap, thread, time::Duration}; 5 6 use device_query::{DeviceState, Keycode}; 7 use quad_snd::{AudioContext, Sound}; ··· 15 } 16 17 fn main() { 18 let on_icon = trayicon::Icon::from_buffer( 19 Box::new(std::fs::read("osuclack.ico").unwrap()).leak(), 20 None, ··· 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 ··· 179 break; 180 } 181 } 182 } 183 } 184