···
#![windows_subsystem = "windows"]
use std::collections::HashSet;
-
atomic::{AtomicBool, Ordering},
use device_query::{DeviceState, Keycode};
use quad_snd::{AudioContext, Sound};
···
-
// 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(),
···
.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())
-
let Ok(event) = tray_rx.recv() else {
-
TrayEvents::ToggleSound => {
-
let new = !sounds_enabled.load(Ordering::Relaxed);
-
sounds_enabled.store(new, Ordering::Relaxed);
-
.set_icon(new.then_some(&on_icon).unwrap_or(&off_icon))
-
TrayEvents::ShowMenu => {
-
tray_icon.show_menu().unwrap();
···
-
fn audio_key_thread(sounds_enabled: Arc<AtomicBool>) {
-
let ctx = AudioContext::new();
-
let sounds = std::fs::read_dir("sounds")
-
.expect("cant read sounds")
-
let p = f.ok()?.path();
-
let n = p.file_stem()?.to_string_lossy().into_owned();
-
(n != "LICENSE" && n != "README").then(|| {
-
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
-
let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect();
-
// Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
-
[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| {
-
.any(|key| current.contains(key) || previous.contains(key))
-
let hotkey_active = check_hotkey(¤tly_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 ¤tly_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 ¤tly_held_keys {
-
if is_modifier_key(key) {
-
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);
-
// 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));