when osu sound i guess

Compare changes

Choose any two refs to compare.

+1
.gitignore
···
/target
+
osuclack
+241
Cargo.lock
···
]
[[package]]
+
name = "bitflags"
+
version = "2.9.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+
+
[[package]]
+
name = "bitvec"
+
version = "1.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+
dependencies = [
+
"funty",
+
"radium",
+
"tap",
+
"wyz",
+
]
+
+
[[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"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
+
name = "cfg-if"
+
version = "1.0.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+
[[package]]
+
name = "cfg_aliases"
+
version = "0.2.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"readmouse",
"windows",
"x11",
+
]
+
+
[[package]]
+
name = "dispatch2"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
+
dependencies = [
+
"bitflags",
+
"objc2",
+
]
+
+
[[package]]
+
name = "evdev"
+
version = "0.13.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "25b686663ba7f08d92880ff6ba22170f1df4e83629341cba34cf82cd65ebea99"
+
dependencies = [
+
"bitvec",
+
"cfg-if",
+
"libc",
+
"nix",
]
[[package]]
···
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
+
name = "funty"
+
version = "2.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+
[[package]]
name = "hound"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
+
name = "nix"
+
version = "0.29.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
+
dependencies = [
+
"bitflags",
+
"cfg-if",
+
"cfg_aliases",
+
"libc",
+
]
+
+
[[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"
···
version = "0.1.0"
dependencies = [
"device_query",
+
"evdev",
"fastrand",
+
"libc",
"quad-snd",
+
"trayicon",
+
"winapi",
]
[[package]]
···
]
[[package]]
+
name = "radium"
+
version = "0.7.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
+
[[package]]
name = "readkey"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
+
]
+
+
[[package]]
+
name = "tap"
+
version = "1.0.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+
[[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]]
···
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+
[[package]]
+
name = "wyz"
+
version = "0.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+
dependencies = [
+
"tap",
+
]
[[package]]
name = "x11"
+20 -1
Cargo.toml
···
version = "0.1.0"
edition = "2024"
+
[features]
+
tray = ["dep:trayicon", "dep:winapi"]
+
[dependencies]
-
device_query = "4.0.1"
fastrand = "2.3.0"
quad-snd = "0.2.8"
+
trayicon = { version = "0.3.0", optional = true }
+
winapi = { version = "0.3.9", features = [
+
"winuser",
+
"windef",
+
"minwindef",
+
"shellapi",
+
"libloaderapi",
+
"commctrl",
+
"basetsd",
+
], optional = true }
+
+
[target.'cfg(target_os = "linux")'.dependencies]
+
evdev = "0.13.2"
+
libc = "0.2"
+
+
[target.'cfg(not(target_os = "linux"))'.dependencies]
+
device_query = "4.0.1"
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.

+254
src/input.rs
···
+
#[cfg(target_os = "linux")]
+
mod linux {
+
use evdev::{Device, EventSummary, KeyCode};
+
use std::collections::HashSet;
+
use std::fs::{self, OpenOptions};
+
use std::os::unix::io::AsRawFd;
+
use std::path::Path;
+
+
pub struct EvdevInput {
+
devices: Vec<Device>,
+
held_keys: HashSet<u16>,
+
}
+
+
impl EvdevInput {
+
pub fn new() -> std::io::Result<Self> {
+
let mut devices = Vec::new();
+
+
// Find all keyboard devices in /dev/input
+
let input_dir = Path::new("/dev/input");
+
if let Ok(entries) = fs::read_dir(input_dir) {
+
for entry in entries.flatten() {
+
let path = entry.path();
+
if let Some(name) = path.file_name() {
+
if name.to_string_lossy().starts_with("event") {
+
if let Ok(file) = OpenOptions::new().read(true).open(&path) {
+
// Set non-blocking mode
+
let fd = file.as_raw_fd();
+
unsafe {
+
let flags = libc::fcntl(fd, libc::F_GETFL, 0);
+
if flags != -1 {
+
libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK);
+
}
+
}
+
+
if let Ok(device) = Device::from_fd(file.into()) {
+
// Check if this device supports keyboard events
+
if device.supported_keys().map_or(false, |keys| {
+
keys.contains(KeyCode::KEY_A) || keys.contains(KeyCode::KEY_SPACE)
+
}) {
+
devices.push(device);
+
}
+
}
+
}
+
}
+
}
+
}
+
}
+
+
if devices.is_empty() {
+
eprintln!("Warning: No keyboard devices found. Make sure you have read permissions for /dev/input/event* devices.");
+
eprintln!("You may need to add your user to the 'input' group or run with appropriate permissions.");
+
}
+
+
Ok(Self {
+
devices,
+
held_keys: HashSet::new(),
+
})
+
}
+
+
pub fn query_keymap(&mut self) -> HashSet<u16> {
+
// Process events from all devices
+
for device in &mut self.devices {
+
while let Ok(events) = device.fetch_events() {
+
for event in events {
+
if let EventSummary::Key(_, key, value) = event.destructure() {
+
let code = key.code();
+
match value {
+
0 => {
+
// Key released
+
self.held_keys.remove(&code);
+
}
+
1 | 2 => {
+
// Key pressed (1) or repeated (2)
+
self.held_keys.insert(code);
+
}
+
_ => {}
+
}
+
}
+
}
+
}
+
}
+
+
self.held_keys.clone()
+
}
+
}
+
+
// Key code constants
+
#[allow(dead_code)]
+
pub const KEY_A: u16 = KeyCode::KEY_A.code();
+
pub const KEY_C: u16 = KeyCode::KEY_C.code();
+
pub const KEY_CAPSLOCK: u16 = KeyCode::KEY_CAPSLOCK.code();
+
pub const KEY_DELETE: u16 = KeyCode::KEY_DELETE.code();
+
pub const KEY_BACKSPACE: u16 = KeyCode::KEY_BACKSPACE.code();
+
pub const KEY_UP: u16 = KeyCode::KEY_UP.code();
+
pub const KEY_DOWN: u16 = KeyCode::KEY_DOWN.code();
+
pub const KEY_LEFT: u16 = KeyCode::KEY_LEFT.code();
+
pub const KEY_RIGHT: u16 = KeyCode::KEY_RIGHT.code();
+
pub const KEY_LEFTSHIFT: u16 = KeyCode::KEY_LEFTSHIFT.code();
+
pub const KEY_RIGHTSHIFT: u16 = KeyCode::KEY_RIGHTSHIFT.code();
+
pub const KEY_LEFTCTRL: u16 = KeyCode::KEY_LEFTCTRL.code();
+
pub const KEY_RIGHTCTRL: u16 = KeyCode::KEY_RIGHTCTRL.code();
+
pub const KEY_LEFTALT: u16 = KeyCode::KEY_LEFTALT.code();
+
pub const KEY_RIGHTALT: u16 = KeyCode::KEY_RIGHTALT.code();
+
pub const KEY_LEFTMETA: u16 = KeyCode::KEY_LEFTMETA.code();
+
pub const KEY_RIGHTMETA: u16 = KeyCode::KEY_RIGHTMETA.code();
+
+
pub fn is_modifier_key(key: u16) -> bool {
+
matches!(
+
key,
+
KEY_LEFTSHIFT
+
| KEY_RIGHTSHIFT
+
| KEY_LEFTCTRL
+
| KEY_RIGHTCTRL
+
| KEY_RIGHTALT
+
| KEY_LEFTALT
+
| KEY_RIGHTMETA
+
| KEY_LEFTMETA
+
| KEY_CAPSLOCK
+
)
+
}
+
}
+
+
#[cfg(not(target_os = "linux"))]
+
mod fallback {
+
use device_query::{DeviceQuery, DeviceState, Keycode};
+
use std::collections::HashSet;
+
+
pub struct DeviceQueryInput {
+
state: DeviceState,
+
}
+
+
impl DeviceQueryInput {
+
pub fn new() -> Self {
+
Self {
+
state: DeviceState::new(),
+
}
+
}
+
+
pub fn query_keymap(&mut self) -> HashSet<u16> {
+
self.state
+
.query_keymap()
+
.into_iter()
+
.map(keycode_to_u16)
+
.collect()
+
}
+
}
+
+
fn keycode_to_u16(keycode: Keycode) -> u16 {
+
// Map device_query keycodes to arbitrary u16 values
+
// We use values that won't collide with Linux key codes
+
match keycode {
+
Keycode::A => 1000,
+
Keycode::B => 1001,
+
Keycode::C => 1002,
+
Keycode::D => 1003,
+
Keycode::E => 1004,
+
Keycode::F => 1005,
+
Keycode::G => 1006,
+
Keycode::H => 1007,
+
Keycode::I => 1008,
+
Keycode::J => 1009,
+
Keycode::K => 1010,
+
Keycode::L => 1011,
+
Keycode::M => 1012,
+
Keycode::N => 1013,
+
Keycode::O => 1014,
+
Keycode::P => 1015,
+
Keycode::Q => 1016,
+
Keycode::R => 1017,
+
Keycode::S => 1018,
+
Keycode::T => 1019,
+
Keycode::U => 1020,
+
Keycode::V => 1021,
+
Keycode::W => 1022,
+
Keycode::X => 1023,
+
Keycode::Y => 1024,
+
Keycode::Z => 1025,
+
Keycode::CapsLock => 1026,
+
Keycode::Delete => 1027,
+
Keycode::Backspace => 1028,
+
Keycode::Up => 1029,
+
Keycode::Down => 1030,
+
Keycode::Left => 1031,
+
Keycode::Right => 1032,
+
Keycode::LShift => 1033,
+
Keycode::RShift => 1034,
+
Keycode::LControl => 1035,
+
Keycode::RControl => 1036,
+
Keycode::LAlt => 1037,
+
Keycode::RAlt => 1038,
+
Keycode::LMeta => 1039,
+
Keycode::RMeta => 1040,
+
_ => {
+
// For any other key, use a hash of the debug string
+
use std::collections::hash_map::DefaultHasher;
+
use std::hash::{Hash, Hasher};
+
let mut hasher = DefaultHasher::new();
+
format!("{:?}", keycode).hash(&mut hasher);
+
(hasher.finish() as u16).wrapping_add(2000)
+
}
+
}
+
}
+
+
// Key code constants - matching the values in keycode_to_u16
+
#[allow(dead_code)]
+
pub const KEY_A: u16 = 1000;
+
pub const KEY_C: u16 = 1002;
+
pub const KEY_CAPSLOCK: u16 = 1026;
+
pub const KEY_DELETE: u16 = 1027;
+
pub const KEY_BACKSPACE: u16 = 1028;
+
pub const KEY_UP: u16 = 1029;
+
pub const KEY_DOWN: u16 = 1030;
+
pub const KEY_LEFT: u16 = 1031;
+
pub const KEY_RIGHT: u16 = 1032;
+
pub const KEY_LEFTSHIFT: u16 = 1033;
+
pub const KEY_RIGHTSHIFT: u16 = 1034;
+
pub const KEY_LEFTCTRL: u16 = 1035;
+
pub const KEY_RIGHTCTRL: u16 = 1036;
+
pub const KEY_LEFTALT: u16 = 1037;
+
pub const KEY_RIGHTALT: u16 = 1038;
+
pub const KEY_LEFTMETA: u16 = 1039;
+
pub const KEY_RIGHTMETA: u16 = 1040;
+
+
pub fn is_modifier_key(key: u16) -> bool {
+
matches!(
+
key,
+
KEY_LEFTSHIFT
+
| KEY_RIGHTSHIFT
+
| KEY_LEFTCTRL
+
| KEY_RIGHTCTRL
+
| KEY_RIGHTALT
+
| KEY_LEFTALT
+
| KEY_RIGHTMETA
+
| KEY_LEFTMETA
+
| KEY_CAPSLOCK
+
)
+
}
+
}
+
+
#[cfg(target_os = "linux")]
+
pub use linux::*;
+
+
#[cfg(not(target_os = "linux"))]
+
pub use fallback::*;
+
+
#[cfg(target_os = "linux")]
+
pub type Input = linux::EvdevInput;
+
+
#[cfg(not(target_os = "linux"))]
+
pub type Input = fallback::DeviceQueryInput;
+
+
pub fn create_input() -> std::io::Result<Input> {
+
Input::new()
+
}
+170 -98
src/main.rs
···
#![windows_subsystem = "windows"]
use std::collections::HashSet;
-
use std::{collections::HashMap, time::Duration};
+
use std::{collections::HashMap, thread, time::Duration};
-
use device_query::{DeviceState, Keycode};
use quad_snd::{AudioContext, Sound};
+
#[cfg(feature = "tray")]
+
use trayicon::{MenuBuilder, TrayIconBuilder};
+
+
mod input;
+
use input::*;
+
+
#[cfg(feature = "tray")]
+
#[derive(PartialEq, Clone)]
+
enum TrayEvents {
+
ShowMenu,
+
ToggleSound,
+
Quit,
+
}
fn main() {
-
let ctx = AudioContext::new();
-
let sounds = std::fs::read_dir("sounds")
-
.expect("cant read sounds")
-
.flat_map(|f| {
-
let p = f.ok()?.path();
-
let n = p.file_stem()?.to_string_lossy().into_owned();
-
(n != "LICENSE" && n != "README").then(|| {
-
(
-
n,
-
Sound::load(&ctx, &std::fs::read(p).expect("can't load sound")),
-
)
-
})
+
#[cfg(feature = "tray")]
+
let on_icon = trayicon::Icon::from_buffer(
+
Box::new(std::fs::read("osuclack.ico").unwrap()).leak(),
+
None,
+
None,
+
)
+
.unwrap();
+
#[cfg(feature = "tray")]
+
let off_icon = trayicon::Icon::from_buffer(
+
Box::new(std::fs::read("osuclack_mute.ico").unwrap()).leak(),
+
None,
+
None,
+
)
+
.unwrap();
+
#[cfg(feature = "tray")]
+
let (tray_tx, tray_rx) = std::sync::mpsc::channel();
+
#[cfg(feature = "tray")]
+
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({
+
let tray_tx = tray_tx.clone();
+
move |e| tray_tx.send(e.clone()).unwrap()
})
-
.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}"));
-
}
-
};
+
.build()
+
.unwrap();
-
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 mut sounds_enabled = true; // Toggle for enabling/disabling sounds
+
let _t = thread::spawn(move || {
+
let ctx = AudioContext::new();
+
let sounds = std::fs::read_dir("sounds")
+
.expect("cant read sounds")
+
.flat_map(|f| {
+
let p = f.ok()?.path();
+
let n = p.file_stem()?.to_string_lossy().into_owned();
+
(n != "LICENSE" && n != "README").then(|| {
+
(
+
n,
+
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: u16| match key {
+
KEY_CAPSLOCK => play_sound("caps"),
+
KEY_DELETE | KEY_BACKSPACE => play_sound("delete"),
+
KEY_UP | KEY_DOWN | KEY_LEFT | KEY_RIGHT => play_sound("movement"),
+
_ => {
+
let no = fastrand::u8(1..=4);
+
play_sound(&format!("press-{no}"));
+
}
+
};
-
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 mut input = create_input().expect("Failed to initialize input system");
+
let mut previously_held_keys = HashSet::<u16>::new();
+
let mut key_press_times = HashMap::<u16, std::time::Instant>::new();
+
let mut last_sound_time = std::time::Instant::now();
+
let mut sound_enabled = true;
-
loop {
-
let currently_held_keys: HashSet<Keycode> = state.query_keymap().into_iter().collect();
+
let initial_delay = Duration::from_millis(500); // Wait 500ms before starting to repeat
+
let repeat_interval = Duration::from_millis(50); // Then repeat every 50ms
-
// Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
-
let hotkey_combo = [
-
[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)
-
];
+
loop {
+
let currently_held_keys = input.query_keymap();
-
let check_hotkey = |current: &HashSet<Keycode>, previous: &HashSet<Keycode>| {
-
hotkey_combo.iter().all(|key_group| {
-
key_group
-
.iter()
-
.any(|key| current.contains(key) || previous.contains(key))
-
})
-
};
+
// Check for toggle hotkey (Ctrl + Alt + L/R Shift + C)
+
let hotkey_combo = [
+
[KEY_LEFTCTRL, KEY_RIGHTCTRL], // Either left or right control
+
[KEY_LEFTALT, KEY_RIGHTALT], // Either left or right alt
+
[KEY_LEFTSHIFT, KEY_RIGHTSHIFT], // Either left or right shift
+
[KEY_C, KEY_C], // C key (duplicated for array consistency)
+
];
-
let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys);
-
let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new());
+
let check_hotkey = |current: &HashSet<u16>, previous: &HashSet<u16>| {
+
hotkey_combo.iter().all(|key_group| {
+
key_group
+
.iter()
+
.any(|key| current.contains(key) || previous.contains(key))
+
})
+
};
-
if hotkey_active && !hotkey_was_active {
-
sounds_enabled = !sounds_enabled;
-
}
+
let hotkey_active = check_hotkey(&currently_held_keys, &previously_held_keys);
+
let hotkey_was_active = check_hotkey(&previously_held_keys, &HashSet::new());
-
// Only process sound logic if sounds are enabled
-
if sounds_enabled {
-
// Track when keys were first pressed
-
for key in &currently_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);
+
#[cfg(feature = "tray")]
+
if hotkey_active && !hotkey_was_active {
+
tray_tx.send(TrayEvents::ToggleSound).unwrap();
+
}
+
+
if hotkey_active && !hotkey_was_active {
+
sound_enabled = !sound_enabled;
+
}
+
+
// handle tray events
+
#[cfg(feature = "tray")]
+
if let Ok(event) = tray_rx.try_recv() {
+
match event {
+
TrayEvents::ToggleSound => {
+
sound_enabled = !sound_enabled;
+
tray_icon
+
.set_icon(sound_enabled.then_some(&on_icon).unwrap_or(&off_icon))
+
.unwrap();
+
}
+
TrayEvents::Quit => {
+
std::process::exit(0);
+
}
+
TrayEvents::ShowMenu => {
+
tray_icon.show_menu().unwrap();
+
}
}
}
-
// 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();
+
// Only process sound logic if sounds are enabled
+
if sound_enabled {
+
// Track when keys were first pressed
for key in &currently_held_keys {
-
if is_modifier_key(key) {
-
continue;
+
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);
}
-
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);
+
}
+
+
// 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 &currently_held_keys {
+
if is_modifier_key(*key) {
+
continue;
+
}
+
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);
+
}
}
}
+
last_sound_time = now;
}
-
last_sound_time = now;
+
} else {
+
// Clear key press times when sounds are disabled to avoid stale data
+
key_press_times.clear();
}
-
} else {
-
// 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));
}
+
});
-
previously_held_keys = currently_held_keys;
+
#[cfg(feature = "tray")]
+
loop {
+
use std::mem;
+
use winapi::um::winuser;
-
// Buffer inputs at lower interval (5ms)
-
std::thread::sleep(Duration::from_millis(5));
+
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 is_modifier_key(key: &Keycode) -> bool {
-
matches!(
-
key,
-
Keycode::LShift
-
| Keycode::RShift
-
| Keycode::LControl
-
| Keycode::RControl
-
| Keycode::RAlt
-
| Keycode::LAlt
-
| Keycode::RMeta
-
| Keycode::LMeta
-
| Keycode::CapsLock
-
)
+
#[cfg(not(feature = "tray"))]
+
_t.join().unwrap();
}