From 19573bc8ec9b1ee8128f435ebbf9e2f0c7c3a32d Mon Sep 17 00:00:00 2001 From: alanz Date: Sat, 6 Jun 2026 23:02:05 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20Add=20Caps=20Lock=20state=20indicat?= =?UTF-8?q?or=20=E6=B7=BB=E5=8A=A0=E5=A4=A7=E5=86=99=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=8C=87=E7=A4=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust_indicator/Cargo.toml | 1 + rust_indicator/src/config.rs | 28 ++++++++++++++++++++-------- rust_indicator/src/ime_detector.rs | 13 ++++++++++++- rust_indicator/src/main.rs | 10 +++++++--- rust_indicator/src/overlay.rs | 14 ++++++++++++-- 5 files changed, 52 insertions(+), 14 deletions(-) diff --git a/rust_indicator/Cargo.toml b/rust_indicator/Cargo.toml index c51dd8c..ecb7ba4 100644 --- a/rust_indicator/Cargo.toml +++ b/rust_indicator/Cargo.toml @@ -8,6 +8,7 @@ windows = { version = "0.58", features = [ "Win32_Foundation", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input_Ime", + "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Accessibility", "Win32_Graphics_Gdi", "Win32_Graphics_GdiPlus", diff --git a/rust_indicator/src/config.rs b/rust_indicator/src/config.rs index 5955b96..d6e6db8 100644 --- a/rust_indicator/src/config.rs +++ b/rust_indicator/src/config.rs @@ -19,6 +19,7 @@ pub struct Config { pub caret_enable: bool, pub caret_color_cn: u32, pub caret_color_en: u32, + pub caret_color_en_upper: u32, pub caret_size: i32, pub caret_offset_x: i32, pub caret_offset_y: i32, @@ -27,6 +28,7 @@ pub struct Config { pub mouse_enable: bool, pub mouse_color_cn: u32, pub mouse_color_en: u32, + pub mouse_color_en_upper: u32, pub mouse_size: i32, pub mouse_offset_x: i32, pub mouse_offset_y: i32, @@ -41,15 +43,17 @@ impl Default for Config { poll_track_interval_ms: 10, tray_enable: true, caret_enable: true, - caret_color_cn: parse_color("#FF7800A0"), - caret_color_en: parse_color("#0078FF30"), + caret_color_cn: parse_color("#FF0000A0"), + caret_color_en: parse_color("#0000FFA0"), + caret_color_en_upper: parse_color("#008000A0"), caret_size: 8, caret_offset_x: 0, caret_offset_y: 0, caret_show_en: true, mouse_enable: true, - mouse_color_cn: parse_color("#FF7800A0"), - mouse_color_en: parse_color("#0078FF30"), + mouse_color_cn: parse_color("#FF0000A0"), + mouse_color_en: parse_color("#0000FFA0"), + mouse_color_en_upper: parse_color("#008000A0"), mouse_size: 8, mouse_offset_x: 2, mouse_offset_y: 18, @@ -138,6 +142,7 @@ fn load_config() -> Config { } if let Some(v) = get("caret", "color_cn") { config.caret_color_cn = v.parse_color(); } if let Some(v) = get("caret", "color_en") { config.caret_color_en = v.parse_color(); } + if let Some(v) = get("caret", "color_en_upper") { config.caret_color_en_upper = v.parse_color(); } if let Some(v) = get("caret", "size") { if let Ok(n) = v.parse() { config.caret_size = n; } } if let Some(v) = get("caret", "offset_x") { if let Ok(n) = v.parse() { config.caret_offset_x = n; } } if let Some(v) = get("caret", "offset_y") { if let Ok(n) = v.parse() { config.caret_offset_y = n; } } @@ -158,6 +163,7 @@ fn load_config() -> Config { } if let Some(v) = get("mouse", "color_cn") { config.mouse_color_cn = v.parse_color(); } if let Some(v) = get("mouse", "color_en") { config.mouse_color_en = v.parse_color(); } + if let Some(v) = get("mouse", "color_en_upper") { config.mouse_color_en_upper = v.parse_color(); } if let Some(v) = get("mouse", "size") { if let Ok(n) = v.parse() { config.mouse_size = n; } } if let Some(v) = get("mouse", "offset_x") { if let Ok(n) = v.parse() { config.mouse_offset_x = n; } } if let Some(v) = get("mouse", "offset_y") { if let Ok(n) = v.parse() { config.mouse_offset_y = n; } } @@ -190,9 +196,12 @@ track_interval_ms = 10 # 位置追踪间隔 (ms) enable = true # 是否显示托盘图标 (false 时完全后台运行,只能通过任务管理器结束) [caret] +# 颜色格式:#RRGGBBAA +# RR=红, GG=绿, BB=蓝, AA=透明度(00=透明, FF=不透明, A0=63%不透明) enable = true # 是否启用文本光标提示 -color_cn = "#FF7800A0" # 中文状态颜色 (#RRGGBBAA) -color_en = "#0078FF30" # 英文状态颜色 +color_cn = "#FF0000A0" # 中文状态颜色(红) +color_en = "#0000FFA0" # 英文小写状态颜色(蓝) +color_en_upper = "#008000A0" # 英文大写状态颜色(绿) size = 8 # 提示球大小 offset_x = 0 offset_y = 0 @@ -200,8 +209,9 @@ show_en = true # 英文状态下是否显示 [mouse] enable = true # 是否开启鼠标提示 -color_cn = "#FF7800A0" # 中文状态颜色 -color_en = "#0078FF30" # 英文状态颜色 +color_cn = "#FF0000A0" # 中文状态颜色(红) +color_en = "#0000FFA0" # 英文小写状态颜色(蓝) +color_en_upper = "#008000A0" # 英文大写状态颜色(绿) size = 8 # 提示球大小 offset_x = 2 offset_y = 18 @@ -223,6 +233,7 @@ pub fn tray_enable() -> bool { get().tray_enable } pub fn caret_enable() -> bool { get().caret_enable } pub fn caret_color_cn() -> u32 { get().caret_color_cn } pub fn caret_color_en() -> u32 { get().caret_color_en } +pub fn caret_color_en_upper() -> u32 { get().caret_color_en_upper } pub fn caret_size() -> i32 { get().caret_size } pub fn caret_offset_x() -> i32 { get().caret_offset_x } pub fn caret_offset_y() -> i32 { get().caret_offset_y } @@ -230,6 +241,7 @@ pub fn caret_show_en() -> bool { get().caret_show_en } pub fn mouse_enable() -> bool { get().mouse_enable } pub fn mouse_color_cn() -> u32 { get().mouse_color_cn } pub fn mouse_color_en() -> u32 { get().mouse_color_en } +pub fn mouse_color_en_upper() -> u32 { get().mouse_color_en_upper } pub fn mouse_size() -> i32 { get().mouse_size } pub fn mouse_offset_x() -> i32 { get().mouse_offset_x } pub fn mouse_offset_y() -> i32 { get().mouse_offset_y } diff --git a/rust_indicator/src/ime_detector.rs b/rust_indicator/src/ime_detector.rs index 3db231e..24b4ce9 100644 --- a/rust_indicator/src/ime_detector.rs +++ b/rust_indicator/src/ime_detector.rs @@ -2,6 +2,7 @@ use windows::Win32::Foundation::HWND; use windows::Win32::UI::Input::Ime::ImmGetDefaultIMEWnd; +use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState; use windows::Win32::UI::WindowsAndMessaging::{ GetForegroundWindow, GetGUIThreadInfo, GetWindowThreadProcessId, SendMessageTimeoutW, GUITHREADINFO, SMTO_ABORTIFHUNG, @@ -70,7 +71,7 @@ fn send_message_timeout(hwnd: HWND, msg: u32, wparam: usize, lparam: isize) -> O pub fn is_chinese_mode() -> bool { let hwnd = get_focused_window(); let ime_hwnd = get_ime_window(hwnd); - + if ime_hwnd.0.is_null() { return false; } @@ -88,3 +89,13 @@ pub fn is_chinese_mode() -> bool { false } + +/// 检测 Caps Lock 是否开启 +pub fn is_caps_lock_on() -> bool { + unsafe { + // VK_CAPITAL = 0x14 + // GetKeyState 返回值的低字节为 1 表示开启 + let state = GetKeyState(0x14); + (state & 0x0001) != 0 + } +} diff --git a/rust_indicator/src/main.rs b/rust_indicator/src/main.rs index 7c7849f..3567a1d 100644 --- a/rust_indicator/src/main.rs +++ b/rust_indicator/src/main.rs @@ -16,7 +16,7 @@ use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, LoadIconW, IDI_APPLI use caret_detector::CaretDetector; use cursor_detector::CursorDetector; -use ime_detector::is_chinese_mode; +use ime_detector::{is_caps_lock_on, is_chinese_mode}; use overlay::IndicatorOverlay; use tray::TrayManager; @@ -104,6 +104,7 @@ fn run_detector_loop(running: Arc) { config::caret_size(), config::caret_color_cn(), config::caret_color_en(), + config::caret_color_en_upper(), config::caret_offset_x(), config::caret_offset_y(), )) @@ -117,6 +118,7 @@ fn run_detector_loop(running: Arc) { config::mouse_size(), config::mouse_color_cn(), config::mouse_color_en(), + config::mouse_color_en_upper(), config::mouse_offset_x(), config::mouse_offset_y(), )) @@ -129,6 +131,7 @@ fn run_detector_loop(running: Arc) { let mut last_state_check_time = Instant::now(); let mut chinese_mode = false; + let mut caps_lock_on = false; let mut caret_active = false; let mut mouse_active = false; @@ -139,6 +142,7 @@ fn run_detector_loop(running: Arc) { // A. 状态检测 (100ms) if now.duration_since(last_state_check_time) >= state_interval { chinese_mode = is_chinese_mode(); + caps_lock_on = is_caps_lock_on(); // Caret 状态判断 if config::caret_enable() { @@ -182,7 +186,7 @@ fn run_detector_loop(running: Arc) { if config::caret_enable() && caret_active { if let Some(ref overlay) = caret_overlay { if let Some((x, y, h)) = caret_detector.get_caret_pos() { - overlay.update(x, y, chinese_mode, h); + overlay.update(x, y, chinese_mode, caps_lock_on, h); } } } @@ -193,7 +197,7 @@ fn run_detector_loop(running: Arc) { let mut pt = POINT::default(); unsafe { if GetCursorPos(&mut pt).is_ok() { - overlay.update(pt.x, pt.y, chinese_mode, 0); + overlay.update(pt.x, pt.y, chinese_mode, caps_lock_on, 0); } } } diff --git a/rust_indicator/src/overlay.rs b/rust_indicator/src/overlay.rs index 49eab74..6d56b26 100644 --- a/rust_indicator/src/overlay.rs +++ b/rust_indicator/src/overlay.rs @@ -39,6 +39,7 @@ pub struct IndicatorOverlay { size: i32, color_cn: u32, color_en: u32, + color_en_upper: u32, offset_x: i32, offset_y: i32, gdi_token: usize, @@ -51,6 +52,7 @@ impl IndicatorOverlay { size: i32, color_cn: u32, color_en: u32, + color_en_upper: u32, offset_x: i32, offset_y: i32, ) -> Self { @@ -62,6 +64,7 @@ impl IndicatorOverlay { size, color_cn, color_en, + color_en_upper, offset_x, offset_y, gdi_token, @@ -140,10 +143,17 @@ impl IndicatorOverlay { } /// 更新渲染内容和屏幕位置 - pub fn update(&self, x: i32, y: i32, is_chinese: bool, caret_h: i32) { - let color = if is_chinese { + /// is_chinese: 是否中文模式 + /// is_upper: 是否大写锁定(Caps Lock 开启) + pub fn update(&self, x: i32, y: i32, is_chinese: bool, is_upper: bool, caret_h: i32) { + let color = if is_upper { + // Caps Lock 开启,不管中英文都显示绿色 + self.color_en_upper + } else if is_chinese { + // Caps Lock 关闭 + 中文模式 → 红色 self.color_cn } else { + // Caps Lock 关闭 + 英文模式 → 蓝色 self.color_en }; From e85c6c031607617e021232fb33c9e391070b1ff7 Mon Sep 17 00:00:00 2001 From: alanz Date: Sun, 7 Jun 2026 22:09:10 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=B3=95=E5=9C=A8Ctrl+Space=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E8=8B=B1=E6=96=87=E7=8A=B6=E6=80=81=E5=90=8E=EF=BC=8C?= =?UTF-8?q?Ctrl+Shift=20=E5=88=87=E6=8D=A2=E8=BE=93=E5=85=A5=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=97=B6=E6=8C=87=E7=A4=BA=E5=99=A8=E9=A2=9C=E8=89=B2?= =?UTF-8?q?=E4=B8=8D=E6=9B=B4=E6=96=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 改进 IME 检测逻辑,结合 NATIVE 标志和键盘布局进行准确判断 - 添加调试控制台功能(可通过 config.toml 的 debug_console 开关控制) --- rust_indicator/src/config.rs | 13 ++- rust_indicator/src/ime_detector.rs | 145 ++++++++++++++++++++++++++--- 2 files changed, 143 insertions(+), 15 deletions(-) diff --git a/rust_indicator/src/config.rs b/rust_indicator/src/config.rs index d6e6db8..2f5f309 100644 --- a/rust_indicator/src/config.rs +++ b/rust_indicator/src/config.rs @@ -15,6 +15,7 @@ pub struct Config { pub poll_track_interval_ms: u64, pub tray_enable: bool, + pub debug_console: bool, pub caret_enable: bool, pub caret_color_cn: u32, @@ -42,6 +43,7 @@ impl Default for Config { poll_state_interval_ms: 100, poll_track_interval_ms: 10, tray_enable: true, + debug_console: false, caret_enable: true, caret_color_cn: parse_color("#FF0000A0"), caret_color_en: parse_color("#0000FFA0"), @@ -125,13 +127,20 @@ fn load_config() -> Config { if let Some(v) = get("poll", "state_interval_ms") { if let Ok(n) = v.parse() { config.poll_state_interval_ms = n; } } if let Some(v) = get("poll", "track_interval_ms") { if let Ok(n) = v.parse() { config.poll_track_interval_ms = n; } } - if let Some(v) = get("tray", "enable") { + if let Some(v) = get("tray", "enable") { match v.as_str() { "true" => config.tray_enable = true, "false" => config.tray_enable = false, _ => {} // 保持默认值 } } + if let Some(v) = get("tray", "debug_console") { + match v.as_str() { + "true" => config.debug_console = true, + "false" => config.debug_console = false, + _ => {} + } + } if let Some(v) = get("caret", "enable") { match v.as_str() { @@ -194,6 +203,7 @@ track_interval_ms = 10 # 位置追踪间隔 (ms) [tray] enable = true # 是否显示托盘图标 (false 时完全后台运行,只能通过任务管理器结束) +debug_console = false # 是否显示调试控制台 (用于开发调试) [caret] # 颜色格式:#RRGGBBAA @@ -230,6 +240,7 @@ pub fn get() -> &'static Config { CONFIG.get_or_init(load_config) } pub fn state_poll_interval_ms() -> u64 { get().poll_state_interval_ms } pub fn track_poll_interval_ms() -> u64 { get().poll_track_interval_ms } pub fn tray_enable() -> bool { get().tray_enable } +pub fn debug_console() -> bool { get().debug_console } pub fn caret_enable() -> bool { get().caret_enable } pub fn caret_color_cn() -> u32 { get().caret_color_cn } pub fn caret_color_en() -> u32 { get().caret_color_en } diff --git a/rust_indicator/src/ime_detector.rs b/rust_indicator/src/ime_detector.rs index 24b4ce9..3cfcab7 100644 --- a/rust_indicator/src/ime_detector.rs +++ b/rust_indicator/src/ime_detector.rs @@ -2,11 +2,13 @@ use windows::Win32::Foundation::HWND; use windows::Win32::UI::Input::Ime::ImmGetDefaultIMEWnd; -use windows::Win32::UI::Input::KeyboardAndMouse::GetKeyState; +use windows::Win32::UI::Input::KeyboardAndMouse::{GetKeyState, GetKeyboardLayout}; use windows::Win32::UI::WindowsAndMessaging::{ GetForegroundWindow, GetGUIThreadInfo, GetWindowThreadProcessId, SendMessageTimeoutW, GUITHREADINFO, SMTO_ABORTIFHUNG, }; +use windows::Win32::System::Console::{AllocConsole, SetConsoleTitleW}; +use windows::core::w; /// IME 控制消息 const WM_IME_CONTROL: u32 = 0x283; @@ -14,6 +16,76 @@ const IMC_GETOPENSTATUS: usize = 0x5; const IMC_GETCONVERSIONMODE: usize = 0x1; const IME_CMODE_NATIVE: u32 = 0x0001; +/// 键盘布局 ID 常量 +const LAYOUT_ZH_CN: u32 = 0x0804; // 中文 (中国) +const LAYOUT_ZH_TW: u32 = 0x0404; // 中文 (台湾) + +/// 调试状态结构体 +#[derive(Clone, PartialEq)] +struct DebugState { + ime_hwnd: isize, + open_status: usize, + conversion_mode: usize, + has_native: bool, + layout_id: u32, + caps_lock: bool, +} + +impl Default for DebugState { + fn default() -> Self { + Self { + ime_hwnd: 0, + open_status: 0, + conversion_mode: 0, + has_native: false, + layout_id: 0, + caps_lock: false, + } + } +} + +/// 全局调试状态 +static mut LAST_STATE: Option = None; +static mut DEBUG_LINE_NUM: u32 = 0; +static mut CONSOLE_INITIALIZED: bool = false; + +/// 初始化调试控制台 +fn init_debug_console() { + unsafe { + if !CONSOLE_INITIALIZED && crate::config::debug_console() { + let _ = AllocConsole(); + let _ = SetConsoleTitleW(w!("IME Indicator Debug Console")); + CONSOLE_INITIALIZED = true; + } + } +} + +/// 打印调试状态 +fn print_debug_state(state: &DebugState, result: bool) { + unsafe { + DEBUG_LINE_NUM += 1; + + let layout_str = match state.layout_id { + 0x0409 => "0x0409(EN)", + 0x0804 => "0x0804(ZH)", + 0x0404 => "0x0404(TW)", + _ => &format!("0x{:04x}(?)", state.layout_id)[..], + }; + + println!( + "[{:03}] hwnd:{:#010x} open:{} conv:{} NATIVE:{} layout:{} caps:{} => {}", + DEBUG_LINE_NUM, + state.ime_hwnd, + state.open_status, + state.conversion_mode, + state.has_native, + layout_str, + state.caps_lock, + if result { "CN" } else { "EN" } + ); + } +} + /// 获取当前焦点窗口 fn get_focused_window() -> HWND { unsafe { @@ -41,6 +113,16 @@ fn get_focused_window() -> HWND { } } +/// 获取键盘布局 ID +fn get_keyboard_layout_id() -> u32 { + unsafe { + let hwnd = get_focused_window(); + let thread_id = GetWindowThreadProcessId(hwnd, None); + let hkl = GetKeyboardLayout(thread_id); + (hkl.0 as usize & 0xFFFF) as u32 + } +} + /// 获取 IME 默认窗口句柄 fn get_ime_window(hwnd: HWND) -> HWND { unsafe { ImmGetDefaultIMEWnd(hwnd) } @@ -69,32 +151,67 @@ fn send_message_timeout(hwnd: HWND, msg: u32, wparam: usize, lparam: isize) -> O /// 检测是否为中文输入模式 pub fn is_chinese_mode() -> bool { + init_debug_console(); + let hwnd = get_focused_window(); let ime_hwnd = get_ime_window(hwnd); - if ime_hwnd.0.is_null() { - return false; - } + // 收集调试状态 + let caps_lock = unsafe { (GetKeyState(0x14) & 0x0001) != 0 }; + let mut state = DebugState { + ime_hwnd: ime_hwnd.0 as isize, + caps_lock, + ..Default::default() + }; + + let mut has_native = false; + let mut open_status = 0; - // 获取 IME 开启状态 - let open_status = send_message_timeout(ime_hwnd, WM_IME_CONTROL, IMC_GETOPENSTATUS, 0); - if open_status.unwrap_or(0) == 0 { - return false; + // 检查 IME 状态 + if !ime_hwnd.0.is_null() { + if let Some(conversion_mode) = send_message_timeout(ime_hwnd, WM_IME_CONTROL, IMC_GETCONVERSIONMODE, 0) { + state.conversion_mode = conversion_mode; + has_native = (conversion_mode as u32 & IME_CMODE_NATIVE) != 0; + state.has_native = has_native; + } + + if let Some(open) = send_message_timeout(ime_hwnd, WM_IME_CONTROL, IMC_GETOPENSTATUS, 0) { + open_status = open; + state.open_status = open; + } } - // 获取转换模式并检测是否包含 NATIVE 标志 (中文) - if let Some(conversion_mode) = send_message_timeout(ime_hwnd, WM_IME_CONTROL, IMC_GETCONVERSIONMODE, 0) { - return (conversion_mode as u32 & IME_CMODE_NATIVE) != 0; + let layout_id = get_keyboard_layout_id(); + state.layout_id = layout_id; + + // 检测逻辑 + let result = if has_native { + layout_id == LAYOUT_ZH_CN || layout_id == LAYOUT_ZH_TW + } else if open_status == 1 { + layout_id == LAYOUT_ZH_CN || layout_id == LAYOUT_ZH_TW + } else { + false + }; + + // 调试输出 + unsafe { + let should_print = match &LAST_STATE { + None => true, + Some(last) => state != *last, + }; + + if should_print { + print_debug_state(&state, result); + LAST_STATE = Some(state.clone()); + } } - false + result } /// 检测 Caps Lock 是否开启 pub fn is_caps_lock_on() -> bool { unsafe { - // VK_CAPITAL = 0x14 - // GetKeyState 返回值的低字节为 1 表示开启 let state = GetKeyState(0x14); (state & 0x0001) != 0 } From 92644387cf42bbeb07c65ba50f8dce79e0189dde Mon Sep 17 00:00:00 2001 From: alanz Date: Sun, 7 Jun 2026 22:18:45 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20YOUTUBE=E5=85=A8=E5=B1=8F=E6=92=AD?= =?UTF-8?q?=E6=94=BE=E4=B8=8D=E4=BC=9A=E6=9C=89=E6=8C=87=E7=A4=BA=E5=99=A8?= =?UTF-8?q?=E6=B5=AE=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust_indicator/src/fullscreen_detector.rs | 80 +++++++++++++++++++++++ rust_indicator/src/main.rs | 80 +++++++++++++++-------- 2 files changed, 133 insertions(+), 27 deletions(-) create mode 100644 rust_indicator/src/fullscreen_detector.rs diff --git a/rust_indicator/src/fullscreen_detector.rs b/rust_indicator/src/fullscreen_detector.rs new file mode 100644 index 0000000..6bfd82a --- /dev/null +++ b/rust_indicator/src/fullscreen_detector.rs @@ -0,0 +1,80 @@ +//! 全屏应用检测模块 +//! +//! 用于检测是否有应用程序处于全屏状态(如视频播放、游戏等), +//! 在全屏时自动隐藏 IME 指示器,避免遮挡视频内容。 + +use windows::Win32::Foundation::RECT; +use windows::Win32::UI::WindowsAndMessaging::{ + GetForegroundWindow, GetSystemMetrics, GetWindowRect, SM_CXSCREEN, SM_CYSCREEN, +}; + +/// 检测是否有应用程序处于全屏状态 +/// +/// 判断逻辑: +/// 1. 获取当前活动窗口 +/// 2. 检查窗口是否覆盖整个屏幕 +/// 3. 排除桌面窗口(Progman) +/// +/// # Returns +/// - `true` - 检测到全屏应用 +/// - `false` - 无全屏应用 +pub fn is_fullscreen_app() -> bool { + unsafe { + // 获取当前活动窗口 + let hwnd = GetForegroundWindow(); + + if hwnd.is_invalid() { + return false; + } + + // 获取窗口矩形 + let mut rect = RECT::default(); + if GetWindowRect(hwnd, &mut rect).is_err() { + return false; + } + + // 获取屏幕尺寸 + let screen_width = GetSystemMetrics(SM_CXSCREEN); + let screen_height = GetSystemMetrics(SM_CYSCREEN); + + // 检查窗口是否覆盖整个屏幕 + // 允许小幅偏差(某些播放器可能会有几像素的边框) + let tolerance = 2; + let covers_screen = (rect.left <= tolerance) + && (rect.top <= tolerance) + && (rect.right >= screen_width - tolerance) + && (rect.bottom >= screen_height - tolerance); + + if !covers_screen { + return false; + } + + // 排除桌面窗口 + // 获取窗口类名来判断是否是桌面 + let mut class_name = [0u16; 256]; + let _ = windows::Win32::UI::WindowsAndMessaging::GetClassNameW(hwnd, &mut class_name); + + let class_name_str = String::from_utf16_lossy( + &class_name[..class_name.iter().position(|&c| c == 0).unwrap_or(0)], + ); + + // 排除桌面和某些系统窗口 + if class_name_str.contains("Progman") || class_name_str.contains("WorkerW") { + return false; + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fullscreen_detection() { + // 基本的功能测试 + let result = is_fullscreen_app(); + println!("Fullscreen status: {}", result); + } +} diff --git a/rust_indicator/src/main.rs b/rust_indicator/src/main.rs index 3567a1d..a3c6c21 100644 --- a/rust_indicator/src/main.rs +++ b/rust_indicator/src/main.rs @@ -3,6 +3,7 @@ mod caret_detector; mod config; mod cursor_detector; +mod fullscreen_detector; mod ime_detector; mod overlay; mod tray; @@ -16,6 +17,7 @@ use windows::Win32::UI::WindowsAndMessaging::{GetCursorPos, LoadIconW, IDI_APPLI use caret_detector::CaretDetector; use cursor_detector::CursorDetector; +use fullscreen_detector::is_fullscreen_app; use ime_detector::{is_caps_lock_on, is_chinese_mode}; use overlay::IndicatorOverlay; use tray::TrayManager; @@ -134,6 +136,7 @@ fn run_detector_loop(running: Arc) { let mut caps_lock_on = false; let mut caret_active = false; let mut mouse_active = false; + let mut is_fullscreen = false; // 主线程负责消息循环,子线程负责检测 while running.load(Ordering::SeqCst) { @@ -141,22 +144,34 @@ fn run_detector_loop(running: Arc) { // A. 状态检测 (100ms) if now.duration_since(last_state_check_time) >= state_interval { + // 检测全屏状态 + is_fullscreen = is_fullscreen_app(); + chinese_mode = is_chinese_mode(); caps_lock_on = is_caps_lock_on(); // Caret 状态判断 if config::caret_enable() { if let Some(ref overlay) = caret_overlay { - let caret_pos = caret_detector.get_caret_pos(); - - let should_caret = caret_pos.is_some() && (chinese_mode || config::caret_show_en()); - if should_caret != caret_active { - caret_active = should_caret; + // 全屏时强制隐藏 + if is_fullscreen { if caret_active { - overlay.show(); - } else { + caret_active = false; overlay.hide(); } + } else { + // 非全屏时正常检测 + let caret_pos = caret_detector.get_caret_pos(); + + let should_caret = caret_pos.is_some() && (chinese_mode || config::caret_show_en()); + if should_caret != caret_active { + caret_active = should_caret; + if caret_active { + overlay.show(); + } else { + overlay.hide(); + } + } } } } @@ -164,15 +179,24 @@ fn run_detector_loop(running: Arc) { // Mouse 状态判断 if config::mouse_enable() { if let Some(ref overlay) = mouse_overlay { - let target_cursor = cursor_detector.is_target_cursor(); - let should_mouse = target_cursor && (chinese_mode || config::mouse_show_en()); - if should_mouse != mouse_active { - mouse_active = should_mouse; + // 全屏时强制隐藏 + if is_fullscreen { if mouse_active { - overlay.show(); - } else { + mouse_active = false; overlay.hide(); } + } else { + // 非全屏时正常检测 + let target_cursor = cursor_detector.is_target_cursor(); + let should_mouse = target_cursor && (chinese_mode || config::mouse_show_en()); + if should_mouse != mouse_active { + mouse_active = should_mouse; + if mouse_active { + overlay.show(); + } else { + overlay.hide(); + } + } } } } @@ -181,23 +205,25 @@ fn run_detector_loop(running: Arc) { } // B. 坐标追踪 - - // 1. 追踪文本光标 - if config::caret_enable() && caret_active { - if let Some(ref overlay) = caret_overlay { - if let Some((x, y, h)) = caret_detector.get_caret_pos() { - overlay.update(x, y, chinese_mode, caps_lock_on, h); + // 全屏时不追踪位置 + if !is_fullscreen { + // 1. 追踪文本光标 + if config::caret_enable() && caret_active { + if let Some(ref overlay) = caret_overlay { + if let Some((x, y, h)) = caret_detector.get_caret_pos() { + overlay.update(x, y, chinese_mode, caps_lock_on, h); + } } } - } - // 2. 追踪鼠标 - if config::mouse_enable() && mouse_active { - if let Some(ref overlay) = mouse_overlay { - let mut pt = POINT::default(); - unsafe { - if GetCursorPos(&mut pt).is_ok() { - overlay.update(pt.x, pt.y, chinese_mode, caps_lock_on, 0); + // 2. 追踪鼠标 + if config::mouse_enable() && mouse_active { + if let Some(ref overlay) = mouse_overlay { + let mut pt = POINT::default(); + unsafe { + if GetCursorPos(&mut pt).is_ok() { + overlay.update(pt.x, pt.y, chinese_mode, caps_lock_on, 0); + } } } }