1use std::fmt;
21use std::sync::Mutex;
22use std::sync::mpsc;
23
24use kbd::action::Action;
25use kbd::binding::BindingId;
26use kbd::binding::BindingOptions;
27use kbd::binding::RegisteredBinding;
28use kbd::hotkey::Hotkey;
29use kbd::hotkey::Modifier;
30use kbd::introspection::ActiveLayerInfo;
31use kbd::introspection::BindingInfo;
32use kbd::introspection::ConflictInfo;
33use kbd::key::Key;
34use kbd::layer::Layer;
35use kbd::layer::LayerName;
36
37use crate::Error;
38use crate::backend::Backend;
39use crate::binding_guard::BindingGuard;
40use crate::engine::Command;
41use crate::engine::CommandSender;
42use crate::engine::EngineRuntime;
43use crate::engine::GrabState;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46enum BackendSelection {
47 Auto,
48 Explicit(Backend),
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52enum GrabConfiguration {
53 Disabled,
54 Enabled,
55}
56
57#[derive(Debug)]
59pub struct HotkeyManagerBuilder {
60 backend: BackendSelection,
61 grab: GrabConfiguration,
62}
63
64impl Default for HotkeyManagerBuilder {
65 fn default() -> Self {
66 Self {
67 backend: BackendSelection::Auto,
68 grab: GrabConfiguration::Disabled,
69 }
70 }
71}
72
73impl HotkeyManagerBuilder {
74 #[must_use]
76 pub fn backend(mut self, backend: Backend) -> Self {
77 self.backend = BackendSelection::Explicit(backend);
78 self
79 }
80
81 #[must_use]
83 pub fn grab(mut self) -> Self {
84 self.grab = GrabConfiguration::Enabled;
85 self
86 }
87
88 pub fn build(self) -> Result<HotkeyManager, Error> {
98 let backend = resolve_backend(self.backend)?;
99 validate_grab_configuration(backend, self.grab)?;
100
101 let grab_state = create_grab_state(self.grab)?;
102 let runtime = EngineRuntime::spawn(grab_state)?;
103 let commands = runtime.commands();
104
105 Ok(HotkeyManager {
106 backend,
107 commands,
108 runtime: Mutex::new(Some(runtime)),
109 })
110 }
111}
112
113pub struct HotkeyManager {
115 backend: Backend,
116 commands: CommandSender,
117 runtime: Mutex<Option<EngineRuntime>>,
118}
119
120impl fmt::Debug for HotkeyManager {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 let running = self
123 .runtime
124 .lock()
125 .map(|guard| guard.is_some())
126 .unwrap_or(false);
127
128 f.debug_struct("HotkeyManager")
129 .field("backend", &self.backend)
130 .field("running", &running)
131 .finish_non_exhaustive()
132 }
133}
134
135impl HotkeyManager {
136 pub fn new() -> Result<Self, Error> {
143 Self::builder().build()
144 }
145
146 #[must_use]
148 pub fn builder() -> HotkeyManagerBuilder {
149 HotkeyManagerBuilder::default()
150 }
151
152 #[must_use]
154 pub const fn active_backend(&self) -> Backend {
155 self.backend
156 }
157
158 pub fn register<F>(&self, hotkey: impl Into<Hotkey>, callback: F) -> Result<BindingGuard, Error>
165 where
166 F: Fn() + Send + Sync + 'static,
167 {
168 self.register_action(hotkey.into(), Action::from(callback))
169 }
170
171 pub fn register_with_options(
181 &self,
182 hotkey: impl Into<Hotkey>,
183 action: impl Into<Action>,
184 options: BindingOptions,
185 ) -> Result<BindingGuard, Error> {
186 let id = BindingId::new();
187 let binding =
188 RegisteredBinding::new(id, hotkey.into(), action.into()).with_options(options);
189 let (reply_tx, reply_rx) = mpsc::channel();
190
191 self.commands.send(Command::Register {
192 binding,
193 reply: reply_tx,
194 })?;
195
196 match reply_rx.recv().map_err(|_| Error::ManagerStopped)? {
197 Ok(()) => Ok(BindingGuard::new(id, self.commands.clone())),
198 Err(error) => Err(error),
199 }
200 }
201
202 pub fn is_registered(&self, hotkey: impl Into<Hotkey>) -> Result<bool, Error> {
208 let hotkey = hotkey.into();
209 let (reply_tx, reply_rx) = mpsc::channel();
210
211 self.commands.send(Command::IsRegistered {
212 hotkey,
213 reply: reply_tx,
214 })?;
215
216 reply_rx.recv().map_err(|_| Error::ManagerStopped)
217 }
218
219 pub fn is_key_pressed(&self, key: Key) -> Result<bool, Error> {
225 let (reply_tx, reply_rx) = mpsc::channel();
226
227 self.commands.send(Command::IsKeyPressed {
228 key,
229 reply: reply_tx,
230 })?;
231
232 reply_rx.recv().map_err(|_| Error::ManagerStopped)
233 }
234
235 pub fn active_modifiers(&self) -> Result<Vec<Modifier>, Error> {
244 let (reply_tx, reply_rx) = mpsc::channel();
245
246 self.commands
247 .send(Command::ActiveModifiers { reply: reply_tx })?;
248
249 reply_rx.recv().map_err(|_| Error::ManagerStopped)
250 }
251
252 pub fn define_layer(&self, layer: Layer) -> Result<(), Error> {
263 let (reply_tx, reply_rx) = mpsc::channel();
264
265 self.commands.send(Command::DefineLayer {
266 layer,
267 reply: reply_tx,
268 })?;
269
270 reply_rx.recv().map_err(|_| Error::ManagerStopped)?
271 }
272
273 pub fn shutdown(self) -> Result<(), Error> {
282 self.shutdown_inner()
283 }
284
285 fn register_action(&self, hotkey: Hotkey, action: Action) -> Result<BindingGuard, Error> {
286 self.register_with_options(hotkey, action, BindingOptions::default())
287 }
288
289 fn shutdown_inner(&self) -> Result<(), Error> {
290 let mut runtime = self.runtime.lock().map_err(|_| Error::EngineError)?;
291 if let Some(runtime) = runtime.take() {
292 return runtime.shutdown();
293 }
294
295 Ok(())
296 }
297
298 pub fn push_layer(&self, name: impl Into<LayerName>) -> Result<(), Error> {
308 let (reply_tx, reply_rx) = mpsc::channel();
309
310 self.commands.send(Command::PushLayer {
311 name: name.into(),
312 reply: reply_tx,
313 })?;
314
315 reply_rx.recv().map_err(|_| Error::ManagerStopped)?
316 }
317
318 pub fn pop_layer(&self) -> Result<LayerName, Error> {
327 let (reply_tx, reply_rx) = mpsc::channel();
328
329 self.commands.send(Command::PopLayer { reply: reply_tx })?;
330
331 reply_rx.recv().map_err(|_| Error::ManagerStopped)?
332 }
333
334 pub fn toggle_layer(&self, name: impl Into<LayerName>) -> Result<(), Error> {
344 let (reply_tx, reply_rx) = mpsc::channel();
345
346 self.commands.send(Command::ToggleLayer {
347 name: name.into(),
348 reply: reply_tx,
349 })?;
350
351 reply_rx.recv().map_err(|_| Error::ManagerStopped)?
352 }
353
354 pub fn list_bindings(&self) -> Result<Vec<BindingInfo>, Error> {
364 let (reply_tx, reply_rx) = mpsc::channel();
365
366 self.commands
367 .send(Command::ListBindings { reply: reply_tx })?;
368
369 reply_rx.recv().map_err(|_| Error::ManagerStopped)
370 }
371
372 pub fn bindings_for_key(
381 &self,
382 hotkey: impl Into<Hotkey>,
383 ) -> Result<Option<BindingInfo>, Error> {
384 let (reply_tx, reply_rx) = mpsc::channel();
385
386 self.commands.send(Command::BindingsForKey {
387 hotkey: hotkey.into(),
388 reply: reply_tx,
389 })?;
390
391 reply_rx.recv().map_err(|_| Error::ManagerStopped)
392 }
393
394 pub fn active_layers(&self) -> Result<Vec<ActiveLayerInfo>, Error> {
402 let (reply_tx, reply_rx) = mpsc::channel();
403
404 self.commands
405 .send(Command::ActiveLayers { reply: reply_tx })?;
406
407 reply_rx.recv().map_err(|_| Error::ManagerStopped)
408 }
409
410 pub fn conflicts(&self) -> Result<Vec<ConflictInfo>, Error> {
422 let (reply_tx, reply_rx) = mpsc::channel();
423
424 self.commands.send(Command::Conflicts { reply: reply_tx })?;
425
426 reply_rx.recv().map_err(|_| Error::ManagerStopped)
427 }
428}
429
430impl Drop for HotkeyManager {
431 fn drop(&mut self) {
432 let _ = self.shutdown_inner();
433 }
434}
435
436fn resolve_backend(selection: BackendSelection) -> Result<Backend, Error> {
437 match selection {
438 BackendSelection::Auto => Ok(Backend::Evdev),
439 BackendSelection::Explicit(backend) => validate_explicit_backend(backend),
440 }
441}
442
443#[allow(clippy::unnecessary_wraps)]
444fn validate_explicit_backend(backend: Backend) -> Result<Backend, Error> {
445 match backend {
446 Backend::Evdev => Ok(Backend::Evdev),
447 }
448}
449
450#[allow(clippy::unnecessary_wraps)]
451fn validate_grab_configuration(_backend: Backend, _grab: GrabConfiguration) -> Result<(), Error> {
452 Ok(())
453}
454
455fn create_grab_state(grab: GrabConfiguration) -> Result<GrabState, Error> {
456 match grab {
457 GrabConfiguration::Disabled => Ok(GrabState::Disabled),
458 GrabConfiguration::Enabled => {
459 #[cfg(feature = "grab")]
460 {
461 let forwarder = crate::engine::forwarder::UinputForwarder::new()?;
462 Ok(GrabState::Enabled {
463 forwarder: Box::new(forwarder),
464 })
465 }
466 #[cfg(not(feature = "grab"))]
467 {
468 Err(Error::UnsupportedFeature)
469 }
470 }
471 }
472}