when osu sound i guess

feat: tray icon

ptr.pet f274fa4f fcfe3ce9

verified
+164
Cargo.lock
··· 21 21 ] 22 22 23 23 [[package]] 24 + name = "bitflags" 25 + version = "2.9.4" 26 + source = "registry+https://github.com/rust-lang/crates.io-index" 27 + checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" 28 + 29 + [[package]] 30 + name = "block2" 31 + version = "0.6.1" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" 34 + dependencies = [ 35 + "objc2", 36 + ] 37 + 38 + [[package]] 24 39 name = "byteorder" 25 40 version = "1.5.0" 26 41 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 72 87 ] 73 88 74 89 [[package]] 90 + name = "dispatch2" 91 + version = "0.3.0" 92 + source = "registry+https://github.com/rust-lang/crates.io-index" 93 + checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" 94 + dependencies = [ 95 + "bitflags", 96 + "objc2", 97 + ] 98 + 99 + [[package]] 75 100 name = "fastrand" 76 101 version = "2.3.0" 77 102 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 117 142 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 118 143 119 144 [[package]] 145 + name = "objc2" 146 + version = "0.6.2" 147 + source = "registry+https://github.com/rust-lang/crates.io-index" 148 + checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" 149 + dependencies = [ 150 + "objc2-encode", 151 + ] 152 + 153 + [[package]] 154 + name = "objc2-app-kit" 155 + version = "0.3.1" 156 + source = "registry+https://github.com/rust-lang/crates.io-index" 157 + checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" 158 + dependencies = [ 159 + "bitflags", 160 + "block2", 161 + "libc", 162 + "objc2", 163 + "objc2-cloud-kit", 164 + "objc2-core-data", 165 + "objc2-core-foundation", 166 + "objc2-core-graphics", 167 + "objc2-core-image", 168 + "objc2-foundation", 169 + "objc2-quartz-core", 170 + ] 171 + 172 + [[package]] 173 + name = "objc2-cloud-kit" 174 + version = "0.3.1" 175 + source = "registry+https://github.com/rust-lang/crates.io-index" 176 + checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" 177 + dependencies = [ 178 + "bitflags", 179 + "objc2", 180 + "objc2-foundation", 181 + ] 182 + 183 + [[package]] 184 + name = "objc2-core-data" 185 + version = "0.3.1" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" 188 + dependencies = [ 189 + "bitflags", 190 + "objc2", 191 + "objc2-foundation", 192 + ] 193 + 194 + [[package]] 195 + name = "objc2-core-foundation" 196 + version = "0.3.1" 197 + source = "registry+https://github.com/rust-lang/crates.io-index" 198 + checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" 199 + dependencies = [ 200 + "bitflags", 201 + "dispatch2", 202 + "objc2", 203 + ] 204 + 205 + [[package]] 206 + name = "objc2-core-graphics" 207 + version = "0.3.1" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" 210 + dependencies = [ 211 + "bitflags", 212 + "dispatch2", 213 + "objc2", 214 + "objc2-core-foundation", 215 + "objc2-io-surface", 216 + ] 217 + 218 + [[package]] 219 + name = "objc2-core-image" 220 + version = "0.3.1" 221 + source = "registry+https://github.com/rust-lang/crates.io-index" 222 + checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" 223 + dependencies = [ 224 + "objc2", 225 + "objc2-foundation", 226 + ] 227 + 228 + [[package]] 229 + name = "objc2-encode" 230 + version = "4.1.0" 231 + source = "registry+https://github.com/rust-lang/crates.io-index" 232 + checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 233 + 234 + [[package]] 235 + name = "objc2-foundation" 236 + version = "0.3.1" 237 + source = "registry+https://github.com/rust-lang/crates.io-index" 238 + checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" 239 + dependencies = [ 240 + "bitflags", 241 + "block2", 242 + "libc", 243 + "objc2", 244 + "objc2-core-foundation", 245 + ] 246 + 247 + [[package]] 248 + name = "objc2-io-surface" 249 + version = "0.3.1" 250 + source = "registry+https://github.com/rust-lang/crates.io-index" 251 + checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" 252 + dependencies = [ 253 + "bitflags", 254 + "objc2", 255 + "objc2-core-foundation", 256 + ] 257 + 258 + [[package]] 259 + name = "objc2-quartz-core" 260 + version = "0.3.1" 261 + source = "registry+https://github.com/rust-lang/crates.io-index" 262 + checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" 263 + dependencies = [ 264 + "bitflags", 265 + "objc2", 266 + "objc2-foundation", 267 + ] 268 + 269 + [[package]] 120 270 name = "ogg" 121 271 version = "0.7.1" 122 272 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 132 282 "device_query", 133 283 "fastrand", 134 284 "quad-snd", 285 + "trayicon", 286 + "winapi", 135 287 ] 136 288 137 289 [[package]] ··· 181 333 checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" 182 334 dependencies = [ 183 335 "maybe-uninit", 336 + ] 337 + 338 + [[package]] 339 + name = "trayicon" 340 + version = "0.3.0" 341 + source = "registry+https://github.com/rust-lang/crates.io-index" 342 + checksum = "09786cfec3e032ab0536dfe8a84b015661e89c74b0c9278ad67c65a55270e0e2" 343 + dependencies = [ 344 + "objc2", 345 + "objc2-app-kit", 346 + "objc2-foundation", 347 + "winapi", 184 348 ] 185 349 186 350 [[package]]
+10
Cargo.toml
··· 7 7 device_query = "4.0.1" 8 8 fastrand = "2.3.0" 9 9 quad-snd = "0.2.8" 10 + trayicon = "0.3.0" 11 + winapi = { version = "0.3.9", features = [ 12 + "winuser", 13 + "windef", 14 + "minwindef", 15 + "shellapi", 16 + "libloaderapi", 17 + "commctrl", 18 + "basetsd", 19 + ] }
osuclack.ico

This is a binary file and will not be displayed.

osuclack_mute.ico

This is a binary file and will not be displayed.

+95 -5
src/main.rs
··· 1 1 #![windows_subsystem = "windows"] 2 2 3 3 use std::collections::HashSet; 4 - use std::{collections::HashMap, time::Duration}; 4 + use std::{ 5 + collections::HashMap, 6 + sync::{ 7 + Arc, 8 + atomic::{AtomicBool, Ordering}, 9 + }, 10 + thread, 11 + time::Duration, 12 + }; 5 13 6 14 use device_query::{DeviceState, Keycode}; 7 15 use quad_snd::{AudioContext, Sound}; 16 + use trayicon::{MenuBuilder, TrayIconBuilder}; 17 + 18 + #[derive(PartialEq, Clone)] 19 + enum TrayEvents { 20 + ShowMenu, 21 + ToggleSound, 22 + Quit, 23 + } 8 24 9 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, 38 + None, 39 + ) 40 + .unwrap(); 41 + let off_icon = trayicon::Icon::from_buffer( 42 + Box::new(std::fs::read("osuclack_mute.ico").unwrap()).leak(), 43 + None, 44 + None, 45 + ) 46 + .unwrap(); 47 + let (tray_tx, tray_rx) = std::sync::mpsc::channel(); 48 + let mut tray_icon = TrayIconBuilder::new() 49 + .tooltip("osuclack") 50 + .icon(on_icon.clone()) 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 + 81 + loop { 82 + use std::mem; 83 + use winapi::um::winuser; 84 + 85 + unsafe { 86 + let mut msg = mem::MaybeUninit::uninit(); 87 + let bret = winuser::GetMessageA(msg.as_mut_ptr(), 0 as _, 0, 0); 88 + if bret > 0 { 89 + winuser::TranslateMessage(msg.as_ptr()); 90 + winuser::DispatchMessageA(msg.as_ptr()); 91 + } else { 92 + break; 93 + } 94 + } 95 + } 96 + } 97 + 98 + fn audio_key_thread(sounds_enabled: Arc<AtomicBool>) { 10 99 let ctx = AudioContext::new(); 11 100 let sounds = std::fs::read_dir("sounds") 12 101 .expect("cant read sounds") ··· 39 128 let mut previously_held_keys = HashSet::<Keycode>::new(); 40 129 let mut key_press_times = HashMap::<Keycode, std::time::Instant>::new(); 41 130 let mut last_sound_time = std::time::Instant::now(); 42 - let mut sounds_enabled = true; // Toggle for enabling/disabling sounds 43 131 44 132 let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat 45 133 let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms ··· 66 154 let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys); 67 155 let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new()); 68 156 157 + let mut sounds_currently_enabled = sounds_enabled.load(Ordering::Relaxed); 69 158 if hotkey_active && !hotkey_was_active { 70 - sounds_enabled = !sounds_enabled; 159 + sounds_currently_enabled = !sounds_currently_enabled; 160 + sounds_enabled.store(sounds_currently_enabled, Ordering::Relaxed); 71 161 } 72 162 73 163 // Only process sound logic if sounds are enabled 74 - if sounds_enabled { 164 + if sounds_currently_enabled { 75 165 // Track when keys were first pressed 76 166 for key in &currently_held_keys { 77 167 if !previously_held_keys.contains(key) { ··· 108 198 previously_held_keys = currently_held_keys; 109 199 110 200 // Buffer inputs at lower interval (5ms) 111 - std::thread::sleep(Duration::from_millis(5)); 201 + thread::sleep(Duration::from_millis(5)); 112 202 } 113 203 } 114 204