use async_trait::async_trait;
use dashmap::DashMap;
use parking_lot::RwLock;
use rdev::{listen, EventType, Key};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use tauri::AppHandle;
use tauri_plugin_global_shortcut::{
Code, GlobalShortcutExt, Modifiers, Shortcut as TauriShortcut, ShortcutState,
};
use tracing::{debug, error, info, warn};
use zerolaunch_plugin_api::host::HostApiError;
use zerolaunch_plugin_api::services::hotkey::{
CallbackRegistration, Hotkey, HotkeyCallback, HotkeyEvent, HotkeyEventFilter, HotkeyManager,
};
pub struct WindowsHotkeyManager {
app_handle: Arc<AppHandle>,
event_callback: Arc<RwLock<Option<HotkeyCallback>>>,
double_ctrl_enabled: Arc<AtomicBool>,
is_listening: Arc<AtomicBool>,
double_ctrl_thread_started: Arc<AtomicBool>,
callbacks: Arc<DashMap<String, CallbackRegistration>>,
}
impl WindowsHotkeyManager {
pub fn new(app_handle: Arc<AppHandle>) -> Self {
let _ = app_handle.plugin(tauri_plugin_global_shortcut::Builder::new().build());
Self {
app_handle,
event_callback: Arc::new(RwLock::new(None)),
double_ctrl_enabled: Arc::new(AtomicBool::new(false)),
is_listening: Arc::new(AtomicBool::new(false)),
double_ctrl_thread_started: Arc::new(AtomicBool::new(false)),
callbacks: Arc::new(DashMap::new()),
}
}
fn convert_to_tauri_shortcut(&self, hotkey: &Hotkey) -> Result<TauriShortcut, HostApiError> {
let code = if hotkey.key.len() == 1 {
let first_char =
hotkey
.key
.chars()
.next()
.ok_or_else(|| HostApiError::ExecutionFailed {
service: "hotkey".to_string(),
reason: "快捷键字符串为空".to_string(),
})?;
match first_char {
'a'..='z' | 'A'..='Z' => {
let uppercase = first_char.to_uppercase().next().ok_or_else(|| {
HostApiError::ExecutionFailed {
service: "hotkey".to_string(),
reason: "字符转换大写失败".to_string(),
}
})?;
let offset = uppercase as u8 - b'A';
match offset {
0 => Some(Code::KeyA),
1 => Some(Code::KeyB),
2 => Some(Code::KeyC),
3 => Some(Code::KeyD),
4 => Some(Code::KeyE),
5 => Some(Code::KeyF),
6 => Some(Code::KeyG),
7 => Some(Code::KeyH),
8 => Some(Code::KeyI),
9 => Some(Code::KeyJ),
10 => Some(Code::KeyK),
11 => Some(Code::KeyL),
12 => Some(Code::KeyM),
13 => Some(Code::KeyN),
14 => Some(Code::KeyO),
15 => Some(Code::KeyP),
16 => Some(Code::KeyQ),
17 => Some(Code::KeyR),
18 => Some(Code::KeyS),
19 => Some(Code::KeyT),
20 => Some(Code::KeyU),
21 => Some(Code::KeyV),
22 => Some(Code::KeyW),
23 => Some(Code::KeyX),
24 => Some(Code::KeyY),
25 => Some(Code::KeyZ),
_ => None,
}
}
'0'..='9' => {
let offset = first_char as u8 - b'0';
match offset {
0 => Some(Code::Digit0),
1 => Some(Code::Digit1),
2 => Some(Code::Digit2),
3 => Some(Code::Digit3),
4 => Some(Code::Digit4),
5 => Some(Code::Digit5),
6 => Some(Code::Digit6),
7 => Some(Code::Digit7),
8 => Some(Code::Digit8),
9 => Some(Code::Digit9),
_ => None,
}
}
_ => None,
}
} else {
match hotkey.key.as_str() {
"Space" => Some(Code::Space),
"Tab" => Some(Code::Tab),
"CapsLock" => Some(Code::CapsLock),
_ => None,
}
};
let Some(code) = code else {
return Err(HostApiError::ExecutionFailed {
service: "hotkey".to_string(),
reason: format!("无效的按键: {}", hotkey.key),
});
};
let mut modifiers = None;
if hotkey.ctrl || hotkey.alt || hotkey.shift || hotkey.meta {
let mut mods = Modifiers::empty();
if hotkey.ctrl {
mods |= Modifiers::CONTROL;
}
if hotkey.alt {
mods |= Modifiers::ALT;
}
if hotkey.shift {
mods |= Modifiers::SHIFT;
}
if hotkey.meta {
mods |= Modifiers::META;
}
modifiers = Some(mods);
}
Ok(TauriShortcut::new(modifiers, code))
}
fn start_double_ctrl_listener(&self) {
if self
.double_ctrl_thread_started
.swap(true, Ordering::Relaxed)
{
return;
}
let callback_ref = self.event_callback.clone();
let enabled_ref = self.double_ctrl_enabled.clone();
thread::spawn(move || {
info!("Starting Double Ctrl listener");
let mut last_ctrl_press = Instant::now();
let mut press_count = 0;
let mut last_key_was_release = false;
if let Err(error) = listen(move |event| {
if !enabled_ref.load(Ordering::Relaxed) {
press_count = 0;
last_key_was_release = false;
return;
}
match event.event_type {
EventType::KeyPress(Key::ControlLeft)
| EventType::KeyPress(Key::ControlRight) => {
let now = Instant::now();
if now.duration_since(last_ctrl_press) < Duration::from_millis(400) {
if press_count == 1 && last_key_was_release {
press_count = 2;
} else {
press_count = 1;
}
} else {
press_count = 1;
}
last_ctrl_press = now;
last_key_was_release = false;
if press_count == 2 {
press_count = 0;
if let Some(callback) = callback_ref.read().as_ref() {
callback(HotkeyEvent::DoubleCtrl);
}
}
}
EventType::KeyRelease(Key::ControlLeft)
| EventType::KeyRelease(Key::ControlRight) => {
last_key_was_release = true;
}
EventType::KeyPress(_) => {
press_count = 0;
last_key_was_release = false;
}
_ => {}
}
}) {
error!("Double Ctrl listener error: {:?}", error);
}
});
}
}
#[async_trait]
impl HotkeyManager for WindowsHotkeyManager {
async fn register_hotkey(&self, hotkey: &Hotkey) -> Result<(), HostApiError> {
let tauri_shortcut = self.convert_to_tauri_shortcut(hotkey)?;
let callback_ref = self.event_callback.clone();
let hotkey_clone = hotkey.clone();
let hotkey_debug = hotkey.clone();
self.app_handle
.global_shortcut()
.on_shortcut(tauri_shortcut, move |_app, _shortcut, event| {
if let ShortcutState::Pressed = event.state() {
debug!("全局快捷键按下: {:?}", hotkey_debug);
if let Some(callback) = callback_ref.read().as_ref() {
debug!("分发快捷键事件: {:?}", hotkey_debug);
callback(HotkeyEvent::GlobalHotkey(hotkey_clone.clone()));
} else {
warn!("快捷键按下但无事件分发器: {:?}", hotkey_debug);
}
}
})
.map_err(|e| HostApiError::ExecutionFailed {
service: "hotkey".to_string(),
reason: format!("注册快捷键失败: {:?}", e),
})?;
Ok(())
}
async fn unregister_hotkey(&self, hotkey: &Hotkey) -> Result<(), HostApiError> {
let tauri_shortcut = self.convert_to_tauri_shortcut(hotkey)?;
let _ = self.app_handle.global_shortcut().unregister(tauri_shortcut);
Ok(())
}
async fn unregister_all(&self) -> Result<(), HostApiError> {
let _ = self.app_handle.global_shortcut().unregister_all();
Ok(())
}
async fn set_double_ctrl_enabled(&self, enabled: bool) -> Result<(), HostApiError> {
self.double_ctrl_enabled.store(enabled, Ordering::Relaxed);
Ok(())
}
async fn start_listening(&self) -> Result<(), HostApiError> {
let was_listening = self.is_listening.swap(true, Ordering::Relaxed);
let callbacks = self.callbacks.clone();
let callback_count = callbacks.len();
info!("启动按键监听,已注册 {} 个回调", callback_count);
let dispatcher: HotkeyCallback = Arc::new(move |event| {
debug!("按键事件分发: {:?}, 回调总数: {}", event, callback_count);
let mut matched = 0;
for entry in callbacks.iter() {
let registration = entry.value();
if matches_filter(®istration.filter, &event) {
debug!(" 匹配回调: {}", registration.id);
(registration.callback)(event.clone());
matched += 1;
}
}
if matched == 0 {
debug!(" 无匹配回调");
}
});
*self.event_callback.write() = Some(dispatcher);
if !was_listening {
info!("首次启动按键监听");
self.start_double_ctrl_listener();
} else {
info!("更新按键分发器");
}
Ok(())
}
async fn stop_listening(&self) -> Result<(), HostApiError> {
*self.event_callback.write() = None;
self.is_listening.store(false, Ordering::Relaxed);
Ok(())
}
fn is_listening(&self) -> bool {
self.is_listening.load(Ordering::Relaxed)
}
fn register_callback(&self, id: &str, filter: HotkeyEventFilter, callback: HotkeyCallback) {
self.callbacks.insert(
id.to_string(),
CallbackRegistration {
id: id.to_string(),
filter,
callback,
},
);
}
fn unregister_callback(&self, id: &str) {
self.callbacks.remove(id);
}
}
fn matches_filter(filter: &HotkeyEventFilter, event: &HotkeyEvent) -> bool {
match (filter, event) {
(HotkeyEventFilter::All, _) => true,
(
HotkeyEventFilter::GlobalHotkey(filter_hotkey),
HotkeyEvent::GlobalHotkey(event_hotkey),
) => filter_hotkey == event_hotkey,
(HotkeyEventFilter::DoubleCtrl, HotkeyEvent::DoubleCtrl) => true,
_ => false,
}
}