···
#![windows_subsystem = "windows"]
4
-
use std::{collections::HashMap, time::Duration};
3
+
use std::collections::HashSet;
4
+
use std::{collections::HashMap, thread, time::Duration};
6
-
use device_query::{DeviceEvents, DeviceEventsHandler, Keycode};
use quad_snd::{AudioContext, Sound};
7
+
#[cfg(feature = "tray")]
8
+
use trayicon::{MenuBuilder, TrayIconBuilder};
13
+
#[cfg(feature = "tray")]
14
+
#[derive(PartialEq, Clone)]
10
-
let ctx = AudioContext::new();
11
-
let sounds = std::fs::read_dir("sounds")
12
-
.expect("cant read sounds")
14
-
let p = f.ok()?.path();
15
-
let n = p.file_stem()?.to_string_lossy().into_owned();
16
-
(n != "LICENSE" && n != "README").then(|| {
19
-
Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")),
22
+
#[cfg(feature = "tray")]
23
+
let on_icon = trayicon::Icon::from_buffer(
24
+
Box::new(std::fs::read("osuclack.ico").unwrap()).leak(),
29
+
#[cfg(feature = "tray")]
30
+
let off_icon = trayicon::Icon::from_buffer(
31
+
Box::new(std::fs::read("osuclack_mute.ico").unwrap()).leak(),
36
+
#[cfg(feature = "tray")]
37
+
let (tray_tx, tray_rx) = std::sync::mpsc::channel();
38
+
#[cfg(feature = "tray")]
39
+
let mut tray_icon = TrayIconBuilder::new()
40
+
.tooltip("osuclack")
41
+
.icon(on_icon.clone())
42
+
.on_click(TrayEvents::ToggleSound)
43
+
.on_right_click(TrayEvents::ShowMenu)
44
+
.menu(MenuBuilder::new().item("quit", TrayEvents::Quit))
46
+
let tray_tx = tray_tx.clone();
47
+
move |e| tray_tx.send(e.clone()).unwrap()
23
-
.collect::<HashMap<String, Sound>>();
24
-
let play_sound = |name: &str| {
25
-
let sound = sounds.get(name).unwrap();
26
-
sound.play(&ctx, Default::default());
29
-
let (tx, rx) = mpsc::channel();
52
+
let _t = thread::spawn(move || {
53
+
let ctx = AudioContext::new();
54
+
let sounds = std::fs::read_dir("sounds")
55
+
.expect("cant read sounds")
57
+
let p = f.ok()?.path();
58
+
let n = p.file_stem()?.to_string_lossy().into_owned();
59
+
(n != "LICENSE" && n != "README").then(|| {
62
+
Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")),
66
+
.collect::<HashMap<String, Sound>>();
67
+
let play_sound = |name: &str| {
68
+
let sound = sounds.get(name).unwrap();
69
+
sound.play(&ctx, Default::default());
71
+
let play_sound_for_key = |key: u16| match key {
72
+
KEY_CAPSLOCK => play_sound("caps"),
73
+
KEY_DELETE | KEY_BACKSPACE => play_sound("delete"),
74
+
KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT => play_sound("movement"),
76
+
let no = fastrand::u8(1..=4);
77
+
play_sound(&format!("press-{no}"));
31
-
let query = DeviceEventsHandler::new(Duration::from_millis(10)).expect("already running");
32
-
let _guard = query.on_key_down(move |key| {
33
-
tx.send(*key).expect("tx");
81
+
let mut input = create_input().expect("Failed to initialize input system");
82
+
let mut previously_held_keys = HashSet::<u16>::new();
83
+
let mut key_press_times = HashMap::<u16, std::time::Instant>::new();
84
+
let mut last_sound_time = std::time::Instant::now();
85
+
let mut sound_enabled = true;
37
-
if let Ok(key) = rx.recv() {
39
-
Keycode::CapsLock => play_sound("caps"),
40
-
Keycode::Delete | Keycode::Backspace => play_sound("delete"),
41
-
Keycode::Up | Keycode::Down | Keycode::Left | Keycode::Right => {
42
-
play_sound("movement")
87
+
let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat
88
+
let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms
91
+
let currently_held_keys = input.query_keymap();
93
+
// Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
94
+
let hotkey_combo = [
95
+
[KEY_LEFTCTRL, KEY_RIGHTCTRL], // Either left or right control
96
+
[KEY_LEFTALT, KEY_RIGHTALT], // Either left or right alt
97
+
[KEY_LEFTSHIFT, KEY_RIGHTSHIFT], // Either left or right shift
98
+
[KEY_C, KEY_C], // C key (duplicated for array consistency)
101
+
let check_hotkey = |current: &HashSet<u16>, previous: &HashSet<u16>| {
102
+
hotkey_combo.iter().all(|key_group| {
105
+
.any(|key| current.contains(key) || previous.contains(key))
109
+
let hotkey_active = check_hotkey(¤tly_held_keys, &previously_held_keys);
110
+
let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new());
112
+
#[cfg(feature = "tray")]
113
+
if hotkey_active && !hotkey_was_active {
114
+
tray_tx.send(TrayEvents::ToggleSound).unwrap();
117
+
if hotkey_active && !hotkey_was_active {
118
+
sound_enabled = !sound_enabled;
121
+
// handle tray events
122
+
#[cfg(feature = "tray")]
123
+
if let Ok(event) = tray_rx.try_recv() {
125
+
TrayEvents::ToggleSound => {
126
+
sound_enabled = !sound_enabled;
128
+
.set_icon(sound_enabled.then_some(&on_icon).unwrap_or(&off_icon))
131
+
TrayEvents::Quit => {
132
+
std::process::exit(0);
134
+
TrayEvents::ShowMenu => {
135
+
tray_icon.show_menu().unwrap();
45
-
let no = fastrand::u8(1..=4);
46
-
play_sound(&format!("press-{no}"));
140
+
// Only process sound logic if sounds are enabled
142
+
// Track when keys were first pressed
143
+
for key in ¤tly_held_keys {
144
+
if !previously_held_keys.contains(key) {
145
+
// Key just pressed, record the time and play initial sound
146
+
key_press_times.insert(*key, std::time::Instant::now());
147
+
play_sound_for_key(*key);
151
+
// Remove timing info for released keys
152
+
key_press_times.retain(|key, _| currently_held_keys.contains(key));
154
+
// Play repeating sounds every 50ms, but only after initial delay
155
+
if last_sound_time.elapsed() >= repeat_interval {
156
+
let now = std::time::Instant::now();
157
+
for key in ¤tly_held_keys {
158
+
if is_modifier_key(*key) {
161
+
if let Some(press_time) = key_press_times.get(key) {
162
+
// Only repeat if key has been held longer than initial delay
163
+
if now.duration_since(*press_time) >= initial_delay {
164
+
play_sound_for_key(*key);
168
+
last_sound_time = now;
171
+
// Clear key press times when sounds are disabled to avoid stale data
172
+
key_press_times.clear();
175
+
previously_held_keys = currently_held_keys;
177
+
// Buffer inputs at lower interval (5ms)
178
+
thread::sleep(Duration::from_millis(5));
50
-
println!("handling new event");
182
+
#[cfg(feature = "tray")]
185
+
use winapi::um::winuser;
188
+
let mut msg = mem::MaybeUninit::uninit();
189
+
let bret = winuser::GetMessageA(msg.as_mut_ptr(), 0 as _, 0, 0);
191
+
winuser::TranslateMessage(msg.as_ptr());
192
+
winuser::DispatchMessageA(msg.as_ptr());
198
+
#[cfg(not(feature = "tray"))]
199
+
_t.join().unwrap();