Files
Unlucky-Launcher/crates/app-iced/src/platform/win32/transparency/exstyle.rs
T
AskaEth 8bfabafe8a fix(app-iced): M147 修掉 Linux 侧 clippy 的 14 条错与写死平台的 α 断言
两条红的成因都是「Windows-only 的东西没有平台门」:Linux + `--all-targets` + `-D warnings`
三件事凑齐时,Windows 专有的 DWM/穿透代码在 Linux 上变成未用导入 + 死代码。

clippy(14 条错,docker 内 exit 101 → 0):
- dwm.rs `set_preference` 的 `#[cfg(not(windows))]` 分支以 `return` 结尾 ⇒ needless_return;
  那个块本来就是该 cfg 下函数体的尾巴,去掉 `return`。
- dwm.rs 三常量 + 四读函数在非 Windows 上没有使用者(唯一调用方 `probe::run_one` 的
  Windows 版被门着,`transparency.rs` 只 re-export 三项)⇒ dead_code。用**模块级**
  `#![cfg_attr(not(target_os = "windows"), allow(dead_code))]`:
  · 逐项 `cfg(target_os = "windows")` 会把它们从 Linux 构建里删掉,本文件那几条与平台
    无关的单测(属性号照 Win32 头文件、无效句柄不许表现成成功)就连编译都过不了;
  · 逐项 `cfg_attr` 要多 7 行,本文件 394 行 → 会顶破 guards.yml 的 400 行红线(现 398 行)。
  代价写明在文件头:非 Windows 上本模块的 dead_code 不再报。
- exstyle.rs `use super::raw_ex_style`:唯一使用者 `click_through` 自己按平台门着 ⇒ 补 cfg。
- probe.rs 的 `use super::dwm::{…}`:`CornerOutcome` 与平台无关(`ProbeRow` 字段 + 非 Windows
  的 `empty_row`),其余九个符号只服务于 Windows 的 `run_one` ⇒ 拆成两条 use。
- window_alpha.rs `use …RawWindowHandle`:只有 `RawWindowHandle::Win32` 那条臂用它 ⇒ 补 cfg。

test(docker 内 622 passed / 1 failed → 623 passed / 0 failed):
`opaque_or_refused_alpha_never_touches_the_window_style` 里「无效句柄 ⇒ Failed」是按 Windows
语义写死的;非 Windows 走 `transparency::set_layered` 的空实现,如实返回 `Unsupported`。
按平台拆成两条断言(Windows 断 `Failed`,其它断 `Unsupported`),两边都不许表现成
`Applied`/`Opaque`。顺带说明:`dispatch` 的 `layered=false` 那半条断言与平台无关,保留在
两条之外照跑。

验证(本机 Windows + Docker Debian 12,命令与 ci.yml 逐字一致):
- docker `cargo clippy --workspace --all-targets -- -D warnings` → exit 0
- docker `cargo test -p unlucky-app-iced --lib` → 623 passed / 0 failed
- Windows `cargo check -p unlucky-app-iced --all-targets` → 无 error/warning
- Windows `cargo test -p unlucky-app-iced --lib` → 624 passed / 0 failed / 2 ignored
2026-11-06 20:06:56 +08:00

111 lines
5.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 取证用的**扩展样式探针**(M100 r1 P2-2):这个窗口是不是「点击穿透」。
//!
//! ## 本文件零 `unsafe`、零 `extern`
//!
//! 全仓 `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 完全相同)。
// 唯一使用者是下面的 `click_through`(它自己按平台门禁),非 Windows 上这个导入没人用
// ⇒ `unused_imports` 在 `-D warnings` 下升成错(M147)。
#[cfg(target_os = "windows")]
use super::raw_ex_style;
/// `WS_EX_TRANSPARENT`:**点击穿透**位(`0x0000_0020`)。
pub const WS_EX_TRANSPARENT: isize = 0x0000_0020;
/// 纯判据:扩展样式里 [`WS_EX_TRANSPARENT`] 在不在。
///
/// 单独拎成 `const fn` 是为了让「位判断写反」有断言盯着——`& WS_EX_TRANSPARENT != 0` 写成
/// `== 0` 是这类 getter 最经典的错,而且只会在「开/关恰好反向」时暴露(两边都"有输出")。
#[must_use]
pub const fn is_click_through_style(ex_style: isize) -> bool {
ex_style & WS_EX_TRANSPARENT != 0
}
/// 取证用:窗口**现在**是否点击穿透(`WS_EX_TRANSPARENT` 在扩展样式里)。
///
/// ## 为什么是三态而不是 `bool`
///
/// `None` = **没读到**(非 Windows / 句柄无效 / 句柄还没拿到)。这时调用方必须记 `unknown`,
/// **不许记成 `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=…`),于是「取证窗口到底
/// 有没有把指针输入关掉」每次运行都能自证,不依赖外部探针。
#[cfg(target_os = "windows")]
#[must_use]
pub fn click_through(hwnd: isize) -> Option<bool> {
if hwnd == 0 || !super::window_exists(hwnd) {
return None;
}
Some(is_click_through_style(raw_ex_style(hwnd)))
}
/// 非 Windows:**恒 `None`**。空实现不许冒充「没开穿透」(那就是把「不知道」写成「没有」)。
#[cfg(not(target_os = "windows"))]
#[must_use]
pub fn click_through(_hwnd: isize) -> Option<bool> {
None
}
#[cfg(test)]
mod tests {
use super::{click_through, is_click_through_style, WS_EX_TRANSPARENT};
use crate::platform::win32::transparency::WS_EX_LAYERED;
/// **抓得住**:`& WS_EX_TRANSPARENT != 0` 被写成 `== 0`(位判断反向)——这是这类 getter 最经典
/// 的错,而且只会在「开/关恰好互换」时暴露:日志里永远有值,只是意思反了。
/// **抓不住**:真实窗口上 winit 到底置没置这一位(那要出图日志里看 `passthrough_ext=on`)。
#[test]
fn the_click_through_bit_cannot_be_read_backwards() {
assert_eq!(WS_EX_TRANSPARENT, 0x0000_0020, "位值照 Win32 头文件");
assert!(is_click_through_style(WS_EX_TRANSPARENT));
assert!(
is_click_through_style(WS_EX_TRANSPARENT | WS_EX_LAYERED),
"winit 的鼠标穿透是两位一起置的,这种组合必须是 true"
);
assert!(
!is_click_through_style(WS_EX_LAYERED),
"只有分层位不算穿透——LWA_ALPHA 也加这一位,反向了会把正常窗口误判成穿透"
);
assert!(!is_click_through_style(0));
}
/// **抓得住**:「拿不到」被记成「没有」。句柄 0 / 无效句柄 / 非 Windows 必须是 `None`
/// (调用方据此写 `unknown`),不许用「读失败的 0」冒充「这一位没开」。
/// **抓不住**:一个**有效**窗口上的读数(那要真窗口,见 harness 的出图日志)。
#[test]
fn click_through_reports_unknown_instead_of_off_when_it_cannot_read() {
assert_eq!(click_through(0), None, "句柄 0 = 还没拿到窗口 ⇒ unknown");
assert_eq!(click_through(1), None, "无效句柄 ⇒ unknown(不许掉成 off)");
#[cfg(not(target_os = "windows"))]
assert_eq!(
click_through(0x1234),
None,
"非 Windows 的空实现必须恒 None,不许冒充 off"
);
}
}