use std::sync::Arc; use tauri::Emitter; use tokio::sync::{mpsc, Mutex}; use magnotia_hotkey::{EvdevHotkeyListener, HotkeyCombo, HotkeyEvent}; /// Managed state for the evdev hotkey listener. pub struct HotkeyState { listener: Arc>>, } impl HotkeyState { pub fn new() -> Self { Self { listener: Arc::new(Mutex::new(None)), } } } /// Check whether the current session is running on Wayland. #[tauri::command] pub fn is_wayland_session() -> bool { std::env::var("WAYLAND_DISPLAY").is_ok() || std::env::var("XDG_SESSION_TYPE") .map(|v| v == "wayland") .unwrap_or(false) } /// Check whether evdev hotkey capture is available (user in `input` group, etc.). #[tauri::command] pub fn check_hotkey_access() -> Result<(), String> { magnotia_hotkey::check_evdev_access() } /// Start the evdev global hotkey listener. Emits "magnotia:hotkey-pressed" and /// "magnotia:hotkey-released" events to the frontend. /// /// If a listener is already running, it is stopped first. #[tauri::command] pub async fn start_evdev_hotkey( app: tauri::AppHandle, state: tauri::State<'_, HotkeyState>, hotkey: String, ) -> Result<(), String> { let combo = HotkeyCombo::from_tauri_str(&hotkey) .ok_or_else(|| format!("Cannot parse hotkey: {hotkey}"))?; // Stop existing listener if any let mut guard = state.listener.lock().await; if let Some(existing) = guard.take() { existing.stop().await; } let (event_tx, mut event_rx) = mpsc::channel::(64); let listener = EvdevHotkeyListener::start(combo, event_tx); *guard = Some(listener); drop(guard); // Forward evdev events to Tauri event bus let app_clone = app.clone(); tokio::spawn(async move { while let Some(event) = event_rx.recv().await { match event { HotkeyEvent::Pressed => { let _ = app_clone.emit("magnotia:hotkey-pressed", ()); } HotkeyEvent::Released => { let _ = app_clone.emit("magnotia:hotkey-released", ()); } } } }); Ok(()) } /// Update the hotkey combo on a running listener. #[tauri::command] pub async fn update_evdev_hotkey( state: tauri::State<'_, HotkeyState>, hotkey: String, ) -> Result<(), String> { let combo = HotkeyCombo::from_tauri_str(&hotkey) .ok_or_else(|| format!("Cannot parse hotkey: {hotkey}"))?; let guard = state.listener.lock().await; if let Some(ref listener) = *guard { listener.set_hotkey(combo); Ok(()) } else { Err("Hotkey listener not running".to_string()) } } /// Stop the evdev hotkey listener. #[tauri::command] pub async fn stop_evdev_hotkey(state: tauri::State<'_, HotkeyState>) -> Result<(), String> { let mut guard = state.listener.lock().await; if let Some(listener) = guard.take() { listener.stop().await; } Ok(()) }