chore: 增强悬浮窗与文本注入的诊断日志
- 文本注入失败时记录 GetLastError(排查插入失败,如 err=5 为权限/UIPI) - 悬浮窗 present 日志增加运行时长、GDI/USER 句柄数、可见性(排查长时间运行后悬浮窗不显示)
This commit is contained in:
1 parent
56ca218cf5
commit
dffb6081f8
2 files changed
+33
-5
No files matched your search
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user