zerolaunch-platform-windows 0.2.2

Windows platform implementations of zerolaunch-plugin-api traits.
use async_trait::async_trait;
use dashmap::DashMap;
use parking_lot::{Mutex, RwLock};
use std::cell::Cell;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tauri::AppHandle;
use tauri_plugin_global_shortcut::{
    Code, GlobalShortcutExt, Modifiers, Shortcut as TauriShortcut, ShortcutState,
};
use tracing::{debug, info, warn};
use windows::Win32::UI::Input::KeyboardAndMouse::{VK_LCONTROL, VK_RCONTROL};
use zerolaunch_plugin_api::host::HostApiError;
use zerolaunch_plugin_api::services::hotkey::{
    CallbackRegistration, Hotkey, HotkeyCallback, HotkeyEvent, HotkeyEventFilter, HotkeyManager,
};

use crate::raw_keyboard::{RawKeyEvent, RawKeyboardListener};

/// 双击 Ctrl 判定窗口:两次 Ctrl 按下需落在此间隔内。
const DOUBLE_CTRL_WINDOW: Duration = Duration::from_millis(400);
/// 判定为「Ctrl」的虚拟键码。
/// 原始输入(RAWKEYBOARD.VKey)上报的是通用 VK_CONTROL(0x11,由扫描码映射而来),
/// 左右区分码只在消息层出现,故两者都接受。
const DOUBLE_CTRL_KEYS: [u16; 3] = [0x11, VK_LCONTROL.0, VK_RCONTROL.0];

/// Windows 平台按键管理器实现。
/// 使用 `tauri-plugin-global-shortcut` 注册全局快捷键;双击 Ctrl 用原始输入(RIDEV_INPUTSINK)
/// 监听,只在功能启用且监听开启时读取全局按键,不安装任何输入钩子。
pub struct WindowsHotkeyManager {
    app_handle: Arc<AppHandle>,
    /// 事件回调,通过 Arc<RwLock> 支持动态更新
    event_callback: Arc<RwLock<Option<HotkeyCallback>>>,
    /// 双击 Ctrl 监听是否启用
    double_ctrl_enabled: AtomicBool,
    /// 是否正在监听
    is_listening: Arc<AtomicBool>,
    /// 双击 Ctrl 的原始输入监听器;未启用时为 None,进程内不读取任何全局输入
    double_ctrl_listener: Mutex<Option<RawKeyboardListener>>,
    /// 上层回调注册表
    callbacks: Arc<DashMap<String, CallbackRegistration>>,
}

impl WindowsHotkeyManager {
    /// 创建新的 WindowsHotkeyManager 实例。
    /// 构造时会尝试加载 `tauri-plugin-global-shortcut` plugin(如果尚未加载)。
    pub fn new(app_handle: Arc<AppHandle>) -> Self {
        // 尝试加载 plugin;若旧系统已加载则忽略错误
        let _ = app_handle.plugin(tauri_plugin_global_shortcut::Builder::new().build());

        Self {
            app_handle,
            event_callback: Arc::new(RwLock::new(None)),
            double_ctrl_enabled: AtomicBool::new(false),
            is_listening: Arc::new(AtomicBool::new(false)),
            double_ctrl_listener: Mutex::new(None),
            callbacks: Arc::new(DashMap::new()),
        }
    }

    /// 将 Hotkey 转换为 Tauri 的 Shortcut。
    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))
    }

    /// 启动双击 Ctrl 的原始输入监听(已启动则忽略;仅在启用且处于监听态时启动)。
    /// 原始输入由系统异步投递,不介入输入管线、不修改前台应用键盘状态,也不需要低级钩子。
    /// 返回:无。启动失败只记录告警,不影响全局快捷键。
    fn start_double_ctrl_listener(&self) {
        if !self.double_ctrl_enabled.load(Ordering::Relaxed)
            || !self.is_listening.load(Ordering::Relaxed)
        {
            return;
        }
        let mut slot = self.double_ctrl_listener.lock();
        if slot.is_some() {
            return;
        }

        let callback_ref = self.event_callback.clone();
        let last_ctrl_press = Cell::new(Instant::now());
        let press_count = Cell::new(0u8);
        let last_key_was_release = Cell::new(false);

        let started = RawKeyboardListener::start(move |event: RawKeyEvent| {
            if !DOUBLE_CTRL_KEYS.contains(&event.vk) {
                // 其他按键按下时复位双击计数(与低级钩子实现一致)
                if event.is_down {
                    press_count.set(0);
                    last_key_was_release.set(false);
                }
                return;
            }
            if !event.is_down {
                last_key_was_release.set(true);
                return;
            }

            let now = Instant::now();
            if now.duration_since(last_ctrl_press.get()) < DOUBLE_CTRL_WINDOW {
                if press_count.get() == 1 && last_key_was_release.get() {
                    press_count.set(2);
                } else {
                    press_count.set(1);
                }
            } else {
                press_count.set(1);
            }
            last_ctrl_press.set(now);
            last_key_was_release.set(false);

            if press_count.get() == 2 {
                press_count.set(0);
                if let Some(callback) = callback_ref.read().as_ref() {
                    callback(HotkeyEvent::DoubleCtrl);
                }
            }
        });

        match started {
            Ok(listener) => {
                info!("双击 Ctrl 监听已启动(原始输入,无输入钩子)");
                *slot = Some(listener);
            }
            Err(e) => warn!("双击 Ctrl 监听启动失败: {e}"),
        }
    }

    /// 停止双击 Ctrl 的原始输入监听(注销原始输入注册并等待监听线程退出)。
    /// 返回:无。
    fn stop_double_ctrl_listener(&self) {
        if let Some(listener) = self.double_ctrl_listener.lock().take() {
            listener.stop();
            info!("双击 Ctrl 监听已停止");
        }
    }
}

#[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);
        if enabled {
            self.start_double_ctrl_listener();
        } else {
            // 关闭时立即注销原始输入:游戏模式下不再读取任何全局按键
            self.stop_double_ctrl_listener();
        }
        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(&registration.filter, &event) {
                    debug!("  匹配回调: {}", registration.id);
                    (registration.callback)(event.clone());
                    matched += 1;
                }
            }
            if matched == 0 {
                debug!("  无匹配回调");
            }
        });
        *self.event_callback.write() = Some(dispatcher);
        // 分发器就绪后再启动双击 Ctrl 监听,避免启动瞬间的事件无人接收
        self.start_double_ctrl_listener();
        if was_listening {
            info!("更新按键分发器");
        } else {
            info!("首次启动按键监听");
        }
        Ok(())
    }

    async fn stop_listening(&self) -> Result<(), HostApiError> {
        *self.event_callback.write() = None;
        self.is_listening.store(false, Ordering::Relaxed);
        self.stop_double_ctrl_listener();
        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,
    }
}