//! Глобальные горячие клавиши (docs/client-tauri.md §5). //! //! Push-to-talk работает при неактивном окне: системный перехват ловит нажатие //! и отпускание выбранной комбинации и передаёт состояние в веб-клиент, а тот //! включает микрофон (в самом клиенте должен быть выбран режим «Push-to-talk»). //! Дополнительно доступны переключение микрофона и «не слышу». use tauri::{AppHandle, Manager, Runtime}; use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut, ShortcutState}; use crate::bridge::{self, WebMessage}; use crate::settings::Settings; use crate::state::AppState; /// Обработчик нажатий: плагин вызывает его для любой зарегистрированной комбинации. pub fn on_shortcut(app: &AppHandle, shortcut: &Shortcut, event: tauri_plugin_global_shortcut::ShortcutEvent) { let settings = app.state::().settings(); let pressed = event.state == ShortcutState::Pressed; let ptt = parse(&settings.ptt_shortcut); if ptt.as_ref() == Some(shortcut) { bridge::send(app, &WebMessage::Ptt { active: pressed }); return; } // Остальные шорткаты срабатывают по нажатию, чтобы не дублировать действие. if !pressed { return; } if parse(&settings.mute_shortcut).as_ref() == Some(shortcut) { bridge::send(app, &WebMessage::ToggleMic); return; } if parse(&settings.deafen_shortcut).as_ref() == Some(shortcut) { bridge::send(app, &WebMessage::ToggleDeafen); } } /// Перерегистрирует комбинации после изменения настроек. pub fn apply(app: &AppHandle, settings: &Settings) -> Result<(), String> { let shortcuts = app.global_shortcut(); shortcuts .unregister_all() .map_err(|error| format!("не удалось снять старые шорткаты: {error}"))?; for spec in [ &settings.ptt_shortcut, &settings.mute_shortcut, &settings.deafen_shortcut, ] { if spec.trim().is_empty() { continue; } shortcuts .register(spec.as_str()) .map_err(|error| format!("комбинация «{spec}» не зарегистрирована: {error}"))?; } Ok(()) } /// Разбирает строку комбинации; пустая строка и мусор дают `None`. fn parse(spec: &str) -> Option { let trimmed = spec.trim(); if trimmed.is_empty() { return None; } trimmed.parse::().ok() }