when osu sound i guess

feat: tray icon

ptr.pet f274fa4f fcfe3ce9

verified
+164
Cargo.lock
···
]
[[package]]
+
name = "bitflags"
+
version = "2.9.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+
+
[[package]]
+
name = "block2"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
+
dependencies = [
+
"objc2",
+
]
+
+
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
]
[[package]]
+
name = "dispatch2"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+
dependencies = [
+
"bitflags",
+
"objc2",
+
]
+
+
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
+
name = "objc2"
+
version = "0.6.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc"
+
dependencies = [
+
"objc2-encode",
+
]
+
+
[[package]]
+
name = "objc2-app-kit"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
+
dependencies = [
+
"bitflags",
+
"block2",
+
"libc",
+
"objc2",
+
"objc2-cloud-kit",
+
"objc2-core-data",
+
"objc2-core-foundation",
+
"objc2-core-graphics",
+
"objc2-core-image",
+
"objc2-foundation",
+
"objc2-quartz-core",
+
]
+
+
[[package]]
+
name = "objc2-cloud-kit"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d"
+
dependencies = [
+
"bitflags",
+
"objc2",
+
"objc2-foundation",
+
]
+
+
[[package]]
+
name = "objc2-core-data"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d"
+
dependencies = [
+
"bitflags",
+
"objc2",
+
"objc2-foundation",
+
]
+
+
[[package]]
+
name = "objc2-core-foundation"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
+
dependencies = [
+
"bitflags",
+
"dispatch2",
+
"objc2",
+
]
+
+
[[package]]
+
name = "objc2-core-graphics"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4"
+
dependencies = [
+
"bitflags",
+
"dispatch2",
+
"objc2",
+
"objc2-core-foundation",
+
"objc2-io-surface",
+
]
+
+
[[package]]
+
name = "objc2-core-image"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e"
+
dependencies = [
+
"objc2",
+
"objc2-foundation",
+
]
+
+
[[package]]
+
name = "objc2-encode"
+
version = "4.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+
[[package]]
+
name = "objc2-foundation"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
+
dependencies = [
+
"bitflags",
+
"block2",
+
"libc",
+
"objc2",
+
"objc2-core-foundation",
+
]
+
+
[[package]]
+
name = "objc2-io-surface"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
+
dependencies = [
+
"bitflags",
+
"objc2",
+
"objc2-core-foundation",
+
]
+
+
[[package]]
+
name = "objc2-quartz-core"
+
version = "0.3.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5"
+
dependencies = [
+
"bitflags",
+
"objc2",
+
"objc2-foundation",
+
]
+
+
[[package]]
name = "ogg"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"device_query",
"fastrand",
"quad-snd",
+
"trayicon",
+
"winapi",
]
[[package]]
···
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
+
]
+
+
[[package]]
+
name = "trayicon"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09786cfec3e032ab0536dfe8a84b015661e89c74b0c9278ad67c65a55270e0e2"
+
dependencies = [
+
"objc2",
+
"objc2-app-kit",
+
"objc2-foundation",
+
"winapi",
]
[[package]]
+10
Cargo.toml
···
device_query = "4.0.1"
fastrand = "2.3.0"
quad-snd = "0.2.8"
+
trayicon = "0.3.0"
+
winapi = { version = "0.3.9", features = [
+
"winuser",
+
"windef",
+
"minwindef",
+
"shellapi",
+
"libloaderapi",
+
"commctrl",
+
"basetsd",
+
] }
osuclack.ico

This is a binary file and will not be displayed.

osuclack_mute.ico

This is a binary file and will not be displayed.

+95 -5
src/main.rs
···
#![windows_subsystem = "windows"]
use std::collections::HashSet;
-
use std::{collections::HashMap, time::Duration};
+
use std::{
+
collections::HashMap,
+
sync::{
+
Arc,
+
atomic::{AtomicBool, Ordering},
+
},
+
thread,
+
time::Duration,
+
};
use device_query::{DeviceState, Keycode};
use quad_snd::{AudioContext, Sound};
+
use trayicon::{MenuBuilder, TrayIconBuilder};
+
+
#[derive(PartialEq, Clone)]
+
enum TrayEvents {
+
ShowMenu,
+
ToggleSound,
+
Quit,
+
}
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,
+
None,
+
)
+
.unwrap();
+
let off_icon = trayicon::Icon::from_buffer(
+
Box::new(std::fs::read("osuclack_mute.ico").unwrap()).leak(),
+
None,
+
None,
+
)
+
.unwrap();
+
let (tray_tx, tray_rx) = std::sync::mpsc::channel();
+
let mut tray_icon = TrayIconBuilder::new()
+
.tooltip("osuclack")
+
.icon(on_icon.clone())
+
.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())
+
.build()
+
.unwrap();
+
+
thread::spawn(move || {
+
loop {
+
let Ok(event) = tray_rx.recv() else {
+
continue;
+
};
+
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();
+
}
+
TrayEvents::Quit => {
+
std::process::exit(0);
+
}
+
TrayEvents::ShowMenu => {
+
tray_icon.show_menu().unwrap();
+
}
+
}
+
}
+
});
+
+
loop {
+
use std::mem;
+
use winapi::um::winuser;
+
+
unsafe {
+
let mut msg = mem::MaybeUninit::uninit();
+
let bret = winuser::GetMessageA(msg.as_mut_ptr(), 0 as _, 0, 0);
+
if bret > 0 {
+
winuser::TranslateMessage(msg.as_ptr());
+
winuser::DispatchMessageA(msg.as_ptr());
+
} else {
+
break;
+
}
+
}
+
}
+
}
+
+
fn audio_key_thread(sounds_enabled: Arc<AtomicBool>) {
let ctx = AudioContext::new();
let sounds = std::fs::read_dir("sounds")
.expect("cant read sounds")
···
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 sounds_enabled = true; // Toggle for enabling/disabling sounds
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 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_enabled = !sounds_enabled;
+
sounds_currently_enabled = !sounds_currently_enabled;
+
sounds_enabled.store(sounds_currently_enabled, Ordering::Relaxed);
}
// Only process sound logic if sounds are enabled
-
if sounds_enabled {
+
if sounds_currently_enabled {
// Track when keys were first pressed
for key in &currently_held_keys {
if !previously_held_keys.contains(key) {
···
previously_held_keys = currently_held_keys;
// Buffer inputs at lower interval (5ms)
-
std::thread::sleep(Duration::from_millis(5));
+
thread::sleep(Duration::from_millis(5));
}
}