+164
Cargo.lock
+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
+10
Cargo.toml
osuclack.ico
osuclack.ico
This is a binary file and will not be displayed.
osuclack_mute.ico
osuclack_mute.ico
This is a binary file and will not be displayed.
+95
-5
src/main.rs
+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(¤tly_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 ¤tly_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