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
···
#![windows_subsystem = "windows"]
use std::collections::HashSet;
-
use std::{
-
collections::HashMap,
-
sync::{
-
Arc,
-
atomic::{AtomicBool, Ordering},
-
},
-
thread,
-
time::Duration,
-
};
+
use std::{collections::HashMap, thread, time::Duration};
use device_query::{DeviceState, Keycode};
use quad_snd::{AudioContext, Sound};
···
}
fn main() {
-
// Shared state for sound toggle
-
let sounds_enabled = Arc::new(AtomicBool::new(true));
-
let sounds_enabled_clone = Arc::clone(&sounds_enabled);
-
-
// Start audio/key processing thread
-
thread::spawn(move || {
-
audio_key_thread(sounds_enabled_clone);
-
});
-
let on_icon = trayicon::Icon::from_buffer(
Box::new(std::fs::read("osuclack.ico").unwrap()).leak(),
None,
···
.on_click(TrayEvents::ToggleSound)
.on_right_click(TrayEvents::ShowMenu)
.menu(MenuBuilder::new().item("quit", TrayEvents::Quit))
-
.sender(move |e| tray_tx.send(e.clone()).unwrap())
+
.sender({
+
let tray_tx = tray_tx.clone();
+
move |e| tray_tx.send(e.clone()).unwrap()
+
})
.build()
.unwrap();
thread::spawn(move || {
+
let ctx = AudioContext::new();
+
let sounds = std::fs::read_dir("sounds")
+
.expect("cant read sounds")
+
.flat_map(|f| {
+
let p = f.ok()?.path();
+
let n = p.file_stem()?.to_string_lossy().into_owned();
+
(n != "LICENSE" && n != "README").then(|| {
+
(
+
n,
+
Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")),
+
)
+
})
+
})
+
.collect::<HashMap<String, Sound>>();
+
let play_sound = |name: &str| {
+
let sound = sounds.get(name).unwrap();
+
sound.play(&ctx, Default::default());
+
};
+
let play_sound_for_key = |key: Keycode| match key {
+
Keycode::CapsLock => play_sound("caps"),
+
Keycode::Delete | Keycode::Backspace => play_sound("delete"),
+
Keycode::Up | Keycode::Down | Keycode::Left | Keycode::Right => play_sound("movement"),
+
_ => {
+
let no = fastrand::u8(1..=4);
+
play_sound(&format!("press-{no}"));
+
}
+
};
+
+
let state = DeviceState::new();
+
let mut previously_held_keys = HashSet::<Keycode>::new();
+
let mut key_press_times = HashMap::<Keycode, std::time::Instant>::new();
+
let mut last_sound_time = std::time::Instant::now();
+
let mut sound_enabled = true;
+
+
let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat
+
let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms
+
loop {
-
let Ok(event) = tray_rx.recv() else {
-
continue;
+
let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect();
+
+
// Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
+
let hotkey_combo = [
+
[Keycode::LControl, Keycode::RControl], // Either left or right control
+
[Keycode::LAlt, Keycode::RAlt], // Either left or right alt
+
[Keycode::LShift, Keycode::RShift], // Either left or right shift
+
[Keycode::C, Keycode::C], // C key (duplicated for array consistency)
+
];
+
+
let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| {
+
hotkey_combo.iter().all(|key_group| {
+
key_group
+
.iter()
+
.any(|key| current.contains(key) || previous.contains(key))
+
})
};
-
match event {
-
TrayEvents::ToggleSound => {
-
let new = !sounds_enabled.load(Ordering::Relaxed);
-
sounds_enabled.store(new, Ordering::Relaxed);
-
tray_icon
-
.set_icon(new.then_some(&on_icon).unwrap_or(&off_icon))
-
.unwrap();
+
+
let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys);
+
let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new());
+
+
if hotkey_active && !hotkey_was_active {
+
tray_tx.send(TrayEvents::ToggleSound).unwrap();
+
}
+
+
// handle tray events
+
if let Ok(event) = tray_rx.try_recv() {
+
match event {
+
TrayEvents::ToggleSound => {
+
sound_enabled = !sound_enabled;
+
tray_icon
+
.set_icon(sound_enabled.then_some(&on_icon).unwrap_or(&off_icon))
+
.unwrap();
+
}
+
TrayEvents::Quit => {
+
std::process::exit(0);
+
}
+
TrayEvents::ShowMenu => {
+
tray_icon.show_menu().unwrap();
+
}
}
-
TrayEvents::Quit => {
-
std::process::exit(0);
+
}
+
+
// Only process sound logic if sounds are enabled
+
if sound_enabled {
+
// Track when keys were first pressed
+
for key in &currently_held_keys {
+
if !previously_held_keys.contains(key) {
+
// Key just pressed, record the time and play initial sound
+
key_press_times.insert(*key, std::time::Instant::now());
+
play_sound_for_key(*key);
+
}
}
-
TrayEvents::ShowMenu => {
-
tray_icon.show_menu().unwrap();
+
+
// Remove timing info for released keys
+
key_press_times.retain(|key, _| currently_held_keys.contains(key));
+
+
// Play repeating sounds every 50ms, but only after initial delay
+
if last_sound_time.elapsed() >= repeat_interval {
+
let now = std::time::Instant::now();
+
for key in &currently_held_keys {
+
if is_modifier_key(key) {
+
continue;
+
}
+
if let Some(press_time) = key_press_times.get(key) {
+
// Only repeat if key has been held longer than initial delay
+
if now.duration_since(*press_time) >= initial_delay {
+
play_sound_for_key(*key);
+
}
+
}
+
}
+
last_sound_time = now;
}
+
} else {
+
// Clear key press times when sounds are disabled to avoid stale data
+
key_press_times.clear();
}
+
+
previously_held_keys = currently_held_keys;
+
+
// Buffer inputs at lower interval (5ms)
+
thread::sleep(Duration::from_millis(5));
}
});
···
break;
}
}
-
}
-
}
-
-
fn audio_key_thread(sounds_enabled: Arc<AtomicBool>) {
-
let ctx = AudioContext::new();
-
let sounds = std::fs::read_dir("sounds")
-
.expect("cant read sounds")
-
.flat_map(|f| {
-
let p = f.ok()?.path();
-
let n = p.file_stem()?.to_string_lossy().into_owned();
-
(n != "LICENSE" && n != "README").then(|| {
-
(
-
n,
-
Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")),
-
)
-
})
-
})
-
.collect::<HashMap<String, Sound>>();
-
let play_sound = |name: &str| {
-
let sound = sounds.get(name).unwrap();
-
sound.play(&ctx, Default::default());
-
};
-
let play_sound_for_key = |key: Keycode| match key {
-
Keycode::CapsLock => play_sound("caps"),
-
Keycode::Delete | Keycode::Backspace => play_sound("delete"),
-
Keycode::Up | Keycode::Down | Keycode::Left | Keycode::Right => play_sound("movement"),
-
_ => {
-
let no = fastrand::u8(1..=4);
-
play_sound(&format!("press-{no}"));
-
}
-
};
-
-
let state = DeviceState::new();
-
let mut previously_held_keys = HashSet::<Keycode>::new();
-
let mut key_press_times = HashMap::<Keycode, std::time::Instant>::new();
-
let mut last_sound_time = std::time::Instant::now();
-
-
let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat
-
let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms
-
-
loop {
-
let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect();
-
-
// Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
-
let hotkey_combo = [
-
[Keycode::LControl, Keycode::RControl], // Either left or right control
-
[Keycode::LAlt, Keycode::RAlt], // Either left or right alt
-
[Keycode::LShift, Keycode::RShift], // Either left or right shift
-
[Keycode::C, Keycode::C], // C key (duplicated for array consistency)
-
];
-
-
let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| {
-
hotkey_combo.iter().all(|key_group| {
-
key_group
-
.iter()
-
.any(|key| current.contains(key) || previous.contains(key))
-
})
-
};
-
-
let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys);
-
let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new());
-
-
let mut sounds_currently_enabled = sounds_enabled.load(Ordering::Relaxed);
-
if hotkey_active && !hotkey_was_active {
-
sounds_currently_enabled = !sounds_currently_enabled;
-
sounds_enabled.store(sounds_currently_enabled, Ordering::Relaxed);
-
}
-
-
// Only process sound logic if sounds are enabled
-
if sounds_currently_enabled {
-
// Track when keys were first pressed
-
for key in &currently_held_keys {
-
if !previously_held_keys.contains(key) {
-
// Key just pressed, record the time and play initial sound
-
key_press_times.insert(*key, std::time::Instant::now());
-
play_sound_for_key(*key);
-
}
-
}
-
-
// Remove timing info for released keys
-
key_press_times.retain(|key, _| currently_held_keys.contains(key));
-
-
// Play repeating sounds every 50ms, but only after initial delay
-
if last_sound_time.elapsed() >= repeat_interval {
-
let now = std::time::Instant::now();
-
for key in &currently_held_keys {
-
if is_modifier_key(key) {
-
continue;
-
}
-
if let Some(press_time) = key_press_times.get(key) {
-
// Only repeat if key has been held longer than initial delay
-
if now.duration_since(*press_time) >= initial_delay {
-
play_sound_for_key(*key);
-
}
-
}
-
}
-
last_sound_time = now;
-
}
-
} else {
-
// Clear key press times when sounds are disabled to avoid stale data
-
key_press_times.clear();
-
}
-
-
previously_held_keys = currently_held_keys;
-
-
// Buffer inputs at lower interval (5ms)
-
thread::sleep(Duration::from_millis(5));
}
}