1#![windows_subsystem = "windows"]
2
3use std::collections::HashSet;
4use std::{collections::HashMap, time::Duration};
5
6use device_query::{DeviceState, Keycode};
7use quad_snd::{AudioContext, Sound};
8
9fn main() {
10 let ctx = AudioContext::new();
11 let sounds = std::fs::read_dir("sounds")
12 .expect("cant read sounds")
13 .flat_map(|f| {
14 let p = f.ok()?.path();
15 let n = p.file_stem()?.to_string_lossy().into_owned();
16 (n != "LICENSE" && n != "README").then(|| {
17 (
18 n,
19 Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")),
20 )
21 })
22 })
23 .collect::<HashMap<String, Sound>>();
24 let play_sound = |name: &str| {
25 let sound = sounds.get(name).unwrap();
26 sound.play(&ctx, Default::default());
27 };
28 let play_sound_for_key = |key: Keycode| match key {
29 Keycode::CapsLock => play_sound("caps"),
30 Keycode::Delete | Keycode::Backspace => play_sound("delete"),
31 Keycode::Up | Keycode::Down | Keycode::Left | Keycode::Right => play_sound("movement"),
32 _ => {
33 let no = fastrand::u8(1..=4);
34 play_sound(&format!("press-{no}"));
35 }
36 };
37
38 let state = DeviceState::new();
39 let mut previously_held_keys = HashSet::<Keycode>::new();
40 let mut key_press_times = HashMap::<Keycode, std::time::Instant>::new();
41 let mut last_sound_time = std::time::Instant::now();
42 let mut sounds_enabled = true; // Toggle for enabling/disabling sounds
43
44 let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat
45 let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms
46
47 loop {
48 let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect();
49
50 // Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
51 let hotkey_combo = [
52 [Keycode::LControl, Keycode::RControl], // Either left or right control
53 [Keycode::LAlt, Keycode::RAlt], // Either left or right alt
54 [Keycode::LShift, Keycode::RShift], // Either left or right shift
55 [Keycode::C, Keycode::C], // C key (duplicated for array consistency)
56 ];
57
58 let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| {
59 hotkey_combo.iter().all(|key_group| {
60 key_group
61 .iter()
62 .any(|key| current.contains(key) || previous.contains(key))
63 })
64 };
65
66 let hotkey_active = check_hotkey(¤tly_held_keys, &previously_held_keys);
67 let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new());
68
69 if hotkey_active && !hotkey_was_active {
70 sounds_enabled = !sounds_enabled;
71 }
72
73 // Only process sound logic if sounds are enabled
74 if sounds_enabled {
75 // Track when keys were first pressed
76 for key in ¤tly_held_keys {
77 if !previously_held_keys.contains(key) {
78 // Key just pressed, record the time and play initial sound
79 key_press_times.insert(*key, std::time::Instant::now());
80 play_sound_for_key(*key);
81 }
82 }
83
84 // Remove timing info for released keys
85 key_press_times.retain(|key, _| currently_held_keys.contains(key));
86
87 // Play repeating sounds every 50ms, but only after initial delay
88 if last_sound_time.elapsed() >= repeat_interval {
89 let now = std::time::Instant::now();
90 for key in ¤tly_held_keys {
91 if is_modifier_key(key) {
92 continue;
93 }
94 if let Some(press_time) = key_press_times.get(key) {
95 // Only repeat if key has been held longer than initial delay
96 if now.duration_since(*press_time) >= initial_delay {
97 play_sound_for_key(*key);
98 }
99 }
100 }
101 last_sound_time = now;
102 }
103 } else {
104 // Clear key press times when sounds are disabled to avoid stale data
105 key_press_times.clear();
106 }
107
108 previously_held_keys = currently_held_keys;
109
110 // Buffer inputs at lower interval (5ms)
111 std::thread::sleep(Duration::from_millis(5));
112 }
113}
114
115fn is_modifier_key(key: &Keycode) -> bool {
116 matches!(
117 key,
118 Keycode::LShift
119 | Keycode::RShift
120 | Keycode::LControl
121 | Keycode::RControl
122 | Keycode::RAlt
123 | Keycode::LAlt
124 | Keycode::RMeta
125 | Keycode::LMeta
126 | Keycode::CapsLock
127 )
128}