diff --git a/crates/app-iced/src/platform/win32/transparency.rs b/crates/app-iced/src/platform/win32/transparency.rs index 9854812..5b74c56 100644 --- a/crates/app-iced/src/platform/win32/transparency.rs +++ b/crates/app-iced/src/platform/win32/transparency.rs @@ -191,6 +191,20 @@ fn has_layered(hwnd: isize) -> bool { raw_ex_style(hwnd) & WS_EX_LAYERED != 0 } +/// 句柄是否指向一个**存在**的窗口(全仓唯一 `unsafe` 落点的一部分)。 +/// +/// 为什么在这里而不是在取证探针那个文件里:`transparency.rs` 是全仓**唯一**允许 `unsafe` 的 +/// 文件(`unsafe-scope` 判据 = 「带 `#![allow(unsafe_code)]` 的文件集合恰好等于本文件」)。 +/// 探针用到 FFI 时就只能**把 FFI 搬进来**,不能让 `unsafe` 溜到没有标记的文件里—— +/// 那会让门禁照样绿、而「唯一落点」这个性质已经破了(M100 r1 复审实测抓到的形状)。 +/// +/// `0` / 无效句柄 → `false`(Win32 的 `IsWindow` 对无效句柄返回 0)。 +#[cfg(target_os = "windows")] +pub(super) fn window_exists(hwnd: isize) -> bool { + // SAFETY: `IsWindow` 只按值接受句柄,不持有指针、无所有权转移;无效句柄返回 0。 + (unsafe { IsWindow(hwnd) }) != 0 +} + /// 把整窗 α 下发到窗口。 /// /// `alpha` 一般直接来自 [`super::super::effective_window_alpha`](`None` = 拒绝整窗穿透)。 @@ -215,6 +229,12 @@ pub fn apply_window_alpha(hwnd: isize, alpha: Option) -> AlphaOutcome { #[cfg(target_os = "windows")] #[link(name = "user32")] extern "system" { + /// `IsWindow`:句柄是否指向一个存在的窗口。 + /// + /// 为什么需要它(M100 r1):`GetWindowLongPtrW` 对**无效句柄**返回 0,与「扩展样式全 0」 + /// 不可区分。取证探针要分「拿不到」与「读到了没有这一位」,所以先验一次句柄。 + fn IsWindow(hwnd: isize) -> i32; + /// `SetLayeredWindowAttributes`:窗口必须已带 `WS_EX_LAYERED`,α 才生效。 /// /// 签名照 Win32 头文件:`BOOL SetLayeredWindowAttributes(HWND, COLORREF, BYTE, DWORD)`。 diff --git a/crates/app-iced/src/platform/win32/transparency/exstyle.rs b/crates/app-iced/src/platform/win32/transparency/exstyle.rs index 83c646f..82c8ab4 100644 --- a/crates/app-iced/src/platform/win32/transparency/exstyle.rs +++ b/crates/app-iced/src/platform/win32/transparency/exstyle.rs @@ -1,11 +1,20 @@ //! 取证用的**扩展样式探针**(M100 r1 P2-2):这个窗口是不是「点击穿透」。 //! -//! 为什么单开一个文件:`transparency.rs` 撞 400 行硬线(361 → 447)。`unsafe` 的**放行属性仍只有 -//! `transparency.rs` 顶部那一个**——lint 级别沿模块树继承,子模块直接继承父模块的 -//! `#![allow(unsafe_code)]`,所以本文件**不许**再写那行属性(CI 的 `unsafe-scope` 判据是 -//! 「带放行属性的文件集合恰好等于 `transparency.rs`」)。 +//! ## 本文件零 `unsafe`、零 `extern` //! -//! 判据来源:winit 0.30 在 Windows 把 `set_cursor_hittest(false)` 落成 +//! 全仓 `unsafe` 只允许出现在 [`super`](`transparency.rs`)一个文件里,判据是 +//! 「**带放行属性**的文件集合恰好等于它」。所以探针需要的 FFI 一律由 `transparency.rs` 提供 +//! **安全包装**([`super::window_exists`] / [`super::raw_ex_style`]),本文件只做纯判断、 +//! 三态映射与测试。 +//! +//! ⚠️ **别把 FFI 写回来**(M100 r1 复审实测抓过一次):lint 级别沿模块树继承,所以在这里写 +//! 一个 `unsafe` 块 + `extern` 声明也**编得过**,`unsafe-scope` 门禁也照样绿(它只数「带放行 +//! 属性的文件」)——但「全仓唯一允许 unsafe 的文件」这个性质就破了。 +//! 属性的作用是可审计性,不是给 CI 看的。 +//! +//! ## 判据来源 +//! +//! winit 0.30 在 Windows 把 `set_cursor_hittest(false)` 落成 //! `WS_EX_TRANSPARENT | WS_EX_LAYERED`(`window_state.rs`)。所以读回这一位,就是 //! 「取证窗口真的把命中测试关掉了」的 OS 侧证据——比「出图 hash 不变」有判别力得多 //! (实测:同 argv 开/关穿透,hash 完全相同)。 @@ -32,6 +41,12 @@ pub const fn is_click_through_style(ex_style: isize) -> bool { /// **不许记成 `off`**——「拿不到」和「没有」是两件事,混为一谈就是假绿(本项目在别处栽过: /// 扫描读不全时报「没有」)。所以这里宁可 `None`,也不用「读失败的 0」冒充「没这一位」。 /// +/// ## 为什么本文件零 `unsafe`、零 `extern` +/// +/// `unsafe` 全仓只在 [`super`](`transparency.rs`)那一个文件里。探针需要的 FFI +/// (`IsWindow` / `GetWindowLongPtrW`)都由 `super` 提供安全包装 +/// ([`super::window_exists`] / [`super::raw_ex_style`]),本文件只做**纯判断**。 +/// /// ## 用途 /// /// 出图时把它打进日志(`pages::harness` 的 `[caps] passthrough_ext=…`),于是「取证窗口到底 @@ -39,11 +54,7 @@ pub const fn is_click_through_style(ex_style: isize) -> bool { #[cfg(target_os = "windows")] #[must_use] pub fn click_through(hwnd: isize) -> Option { - if hwnd == 0 { - return None; - } - // SAFETY: `IsWindow` 只按值接受句柄,不持有指针、无所有权转移;无效句柄返回 0。 - if unsafe { IsWindow(hwnd) } == 0 { + if hwnd == 0 || !super::window_exists(hwnd) { return None; } Some(is_click_through_style(raw_ex_style(hwnd))) @@ -56,16 +67,6 @@ pub fn click_through(_hwnd: isize) -> Option { None } -#[cfg(target_os = "windows")] -#[link(name = "user32")] -extern "system" { - /// `IsWindow`:句柄是否指向一个存在的窗口(读扩展样式前的门槛)。 - /// - /// 为什么要先问一句:`GetWindowLongPtrW` 对无效句柄返回 0,与「扩展样式全 0」不可区分—— - /// 不先验一次,[`click_through`] 就会把「读不到」说成「没开穿透」。 - fn IsWindow(hwnd: isize) -> i32; -} - #[cfg(test)] mod tests { use super::{click_through, is_click_through_style, WS_EX_TRANSPARENT};