chore: 增强悬浮窗与文本注入的诊断日志

- 文本注入失败时记录 GetLastError(排查插入失败,如 err=5 为权限/UIPI)
- 悬浮窗 present 日志增加运行时长、GDI/USER 句柄数、可见性(排查长时间运行后悬浮窗不显示)
This commit is contained in:
crosswk committed 2026-07-16 11:02:02 +08:00
1 parent 56ca218cf5
commit dffb6081f8
2 files changed
+33 -5

No files matched your search

+6 -2
View File
@@ -471,13 +471,17 @@ unsafe fn do_inject(target: HWND, focus: HWND, text: &str, restore_clipboard: bo
make_key_input(vk_ctrl, KEYEVENTF_KEYUP),
];
let sent = SendInput(&inputs, std::mem::size_of::<INPUT>() as i32);
// 立刻抓 GetLastError(后面的 sleep/release_modifiers 会把它冲掉)。
// sent=0 且 err=5(ERROR_ACCESS_DENIED) => UIPI 拦截:目标窗口权限比 SayIt 高
// (目标以管理员运行而 SayIt 没有),解决办法是让 SayIt 以管理员身份运行。
let last_err = windows::Win32::Foundation::GetLastError().0;
std::thread::sleep(std::time::Duration::from_millis(10));
release_modifiers();
let detail = format!(
"sent={} class={} target={} focus={} textLen={} fgOk={}",
sent, focus_class, target.0 as isize, focus.0 as isize, text.len(), fg_ok
"sent={} err={} class={} target={} focus={} textLen={} fgOk={}",
sent, last_err, focus_class, target.0 as isize, focus.0 as isize, text.len(), fg_ok
);
// SendInput Ctrl+V is fire-and-forget. For Chromium-class windows we
+27 -3
View File
@@ -2,7 +2,7 @@ use serde_json::{json, Map, Value};
use std::sync::atomic::{AtomicI64, AtomicU64, Ordering};
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use std::time::{Duration, Instant};
use tauri::{AppHandle, Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
use crate::commands::system::write_log_line;
@@ -48,6 +48,8 @@ pub struct WindowState {
last_ack_generation: AtomicU64,
last_ack_at_ms: AtomicI64,
recovery_started_show_id: AtomicU64,
/// 进程/状态创建时间,用于日志记录运行时长(配合资源计数排查“长时间运行后悬浮窗失效”)
created_at: Instant,
}
impl WindowState {
pub fn new() -> Self {
@@ -64,6 +66,7 @@ impl WindowState {
last_ack_generation: AtomicU64::new(0),
last_ack_at_ms: AtomicI64::new(0),
recovery_started_show_id: AtomicU64::new(0),
created_at: Instant::now(),
}
}
@@ -83,9 +86,11 @@ impl WindowState {
self.recovery_started_show_id.store(0, Ordering::SeqCst);
let state_name = self.latest_state_name();
let (gdi, user_obj) = gui_resource_counts();
let overlay_visible = app.get_webview_window("overlay").and_then(|o| o.is_visible().ok());
write_log_line(&format!(
"[overlay-health] present show_id={} state={} generation=0",
show_id, state_name,
"[overlay-health] present show_id={} state={} generation=0 uptimeSec={} gdi={} user={} overlayVisible={:?}",
show_id, state_name, self.created_at.elapsed().as_secs(), gdi, user_obj, overlay_visible,
));
if self.ensure_visible(app, show_id) {
@@ -511,6 +516,25 @@ fn spawn_render_watchdog(app: AppHandle, show_id: u64) {
});
}
/// 当前进程的 GDI / USER 句柄数。长时间运行后若这两个数持续增长,
/// 说明有句柄泄漏——是“悬浮窗用久后不再出现、重启即好”的典型根因。
#[cfg(windows)]
fn gui_resource_counts() -> (u32, u32) {
// GetGuiResources 在不同 windows crate 版本里的模块路径不稳定,直接用 FFI 声明最稳。
// GR_GDIOBJECTS=0,GR_USEROBJECTS=1;当前进程用伪句柄 (HANDLE)-1。
#[link(name = "user32")]
extern "system" {
fn GetGuiResources(hprocess: *mut core::ffi::c_void, uiflags: u32) -> u32;
}
let cur_proc = -1isize as *mut core::ffi::c_void;
unsafe { (GetGuiResources(cur_proc, 0), GetGuiResources(cur_proc, 1)) }
}
#[cfg(not(windows))]
fn gui_resource_counts() -> (u32, u32) {
(0, 0)
}
fn compact_json(value: &Value) -> String {
serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string())
}