1#![warn(
2 clippy::complexity,
3 clippy::correctness,
4 clippy::perf,
5 clippy::style,
6 missing_docs,
7 rust_2018_idioms
8)]
9#![forbid(clippy::incompatible_msrv)]
10#![cfg_attr(not(feature = "std"), no_std)]
11
12extern crate alloc;
19
20cfg_if::cfg_if! {
21 if #[cfg(not(feature = "std"))] {
22 mod other;
23 use self::other as platform;
24 } else if #[cfg(windows)] {
25 mod windows;
26 use self::windows as platform;
27 } else if #[cfg(target_os = "linux")] {
28 mod linux;
29 use self::linux as platform;
30 } else if #[cfg(target_os = "macos")] {
31 mod macos;
32 use self::macos as platform;
33 } else if #[cfg(all(target_family = "wasm", target_os = "unknown", feature = "wasm-web"))] {
34 mod wasm_web;
35 use self::wasm_web as platform;
36 } else {
37 mod other;
38 use self::other as platform;
39 }
40}
41
42mod hotkey;
43mod key_code;
44mod modifiers;
45use core::fmt;
46
47pub use self::{hotkey::*, key_code::*, modifiers::*};
48
49#[repr(transparent)]
51pub struct Hook(platform::Hook);
52
53#[derive(Copy, Clone, PartialEq, Eq, Hash)]
57pub enum ConsumePreference {
58 NoPreference,
60 PreferConsume,
62 PreferNoConsume,
64 MustConsume,
66 MustNotConsume,
69}
70
71impl Hook {
72 pub fn new() -> Result<Self> {
75 Ok(Self(platform::Hook::new(ConsumePreference::NoPreference)?))
76 }
77
78 pub fn with_consume_preference(consume: ConsumePreference) -> Result<Self> {
81 Ok(Self(platform::Hook::new(consume)?))
82 }
83
84 pub fn register<F>(&self, hotkey: Hotkey, callback: F) -> Result<()>
86 where
87 F: FnMut() + Send + 'static,
88 {
89 self.0.register(hotkey, callback)
90 }
91
92 pub fn unregister(&self, hotkey: Hotkey) -> Result<()> {
94 self.0.unregister(hotkey)
95 }
96}
97
98pub type Result<T> = core::result::Result<T, Error>;
100
101#[derive(Debug)]
103#[non_exhaustive]
104pub enum Error {
105 UnmatchedPreference,
107 AlreadyRegistered,
109 NotRegistered,
111 Platform(platform::Error),
113}
114
115#[cfg(feature = "std")]
117impl std::error::Error for Error {}
118
119impl fmt::Display for Error {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 f.write_str(match self {
122 Self::UnmatchedPreference => {
123 "The consume preference could not be met on the current platform."
124 }
125 Self::AlreadyRegistered => "The hotkey was already registered.",
126 Self::NotRegistered => "The hotkey to unregister was not registered.",
127 Self::Platform(e) => return fmt::Display::fmt(e, f),
128 })
129 }
130}
131
132#[cfg(not(all(target_family = "wasm", target_os = "unknown", feature = "wasm-web")))]
133const _: () = {
134 #[allow(unused)]
135 const fn assert_thread_safe<T: Send + Sync>() {}
136 assert_thread_safe::<Hook>();
137};
138
139#[cfg(test)]
140mod tests {
141 use std::{thread, time::Duration};
142
143 use super::*;
144
145 #[test]
146 fn test() {
147 let hook = Hook::new().unwrap();
148
149 hook.register(KeyCode::Numpad1.with_modifiers(Modifiers::SHIFT), || {
150 println!("A")
151 })
152 .unwrap();
153 println!("Press Shift + Numpad1");
154 thread::sleep(Duration::from_secs(5));
155 hook.unregister(KeyCode::Numpad1.with_modifiers(Modifiers::SHIFT))
156 .unwrap();
157
158 hook.register(KeyCode::KeyN.into(), || println!("B"))
159 .unwrap();
160 println!("Press KeyN");
161 thread::sleep(Duration::from_secs(5));
162 hook.unregister(KeyCode::KeyN.into()).unwrap();
163
164 hook.register(KeyCode::Numpad1.into(), || println!("C"))
165 .unwrap();
166 println!("Press Numpad1");
167 thread::sleep(Duration::from_secs(5));
168 hook.unregister(KeyCode::Numpad1.into()).unwrap();
169 }
170
171 #[test]
172 fn resolve() {
173 let hook = Hook::new().unwrap();
174
175 println!("ß: {}", KeyCode::Minus.resolve(&hook));
177 println!("ü: {}", KeyCode::BracketLeft.resolve(&hook));
178 println!("#: {}", KeyCode::Backslash.resolve(&hook));
179 println!("+: {}", KeyCode::BracketRight.resolve(&hook));
180 println!("z: {}", KeyCode::KeyY.resolve(&hook));
181 println!("^: {}", KeyCode::Backquote.resolve(&hook));
182 println!("<: {}", KeyCode::IntlBackslash.resolve(&hook));
183 println!("Yen: {}", KeyCode::IntlYen.resolve(&hook));
184 println!("Enter: {}", KeyCode::Enter.resolve(&hook));
185 println!("Space: {}", KeyCode::Space.resolve(&hook));
186 println!("Tab: {}", KeyCode::Tab.resolve(&hook));
187 println!("Numpad0: {}", KeyCode::Numpad0.resolve(&hook));
188 }
189}