fix(app-iced): M100 r1 返工 —— 一次一采样 + OS 层密闭指标 + 文档口径订正
三条复审意见 + 六条 p3,全部落在原有文件里(另加两个拆分文件)。 P2-1 截图落盘「最后写入者说了算」(真竞态) `Message::Frame` 以前在 `frames >= CAPTURE_AT_FRAME` 之后每帧都发一次 `window::screenshot`, `Captured` 每次都覆盖同一文件才 `exit()`;已派发的采样不会因 exit 撤回 ⇒ 落盘内容是调度结果 (复审实测 12 次里 7 次写了两次;把窗口在两次采样之间改成 900×600,落盘就是 900×600)。 现在闸门 = `should_request_capture(frames, dispatched, capture_mode)`,派发即置位、只开一次; `Captured` 计数落盘次数并打进日志。实测 12/12 次「[shot] -> 文件」行恒为 1、`落盘次数=1`。 P2-2 密闭证据换成 OS 层 `platform/win32/transparency.rs` 加安全 getter `click_through`(unsafe 仍只在既有唯一落点); 该文件因超 400 行又拆出 `transparency/exstyle.rs`(子模块继承父模块的 `#![allow(unsafe_code)]`, `unsafe-scope` 门禁判据不受影响)。日志三态 `[caps] passthrough_ext=on|off|unknown`: `None` 记 `unknown`,绝不记 `off`。实测默认 on 12/12、`--passthrough=off` 为 off 3/3。 P2-3 头注释「下放」→「新增」,并订正「接真控件改 3 个文件」 (section 文件里的 body 绊线 + 该 section 的 `GROUP` 文件 + `tests/mod.rs` 跨 section 绊线)。 p3 六条:shell 断言说明改准(默认值那条归 cli 的测试)、harness 文档给「指针事件=0」加对照限定、 数字订正(纯搬移 234 / 新增 134)、三文件口径、ai+about 对本文件的两处必改、`Rest` 与真 Disabled 撞语义写进「抓不住」。 文档口径:「8 连跑全稳」不再当密闭证据——同 argv 开/关穿透 hash 相同(实测 0d7b4f5b 两边一样), 它只说明「帧静止 + 落盘确定」。密闭主证据是 `[caps] passthrough_ext=`。 顺带:`run()` 从 `harness.rs` 拆到 `harness/app.rs`(272 / 190 行)。搬移中丢过一条既有断言 `ensure_layered_refuses_a_missing_or_invalid_window`,被编译器 unused-import 警告抓出并复原。
This commit is contained in:
1 parent
2061296615
commit
380fac0324
13 files changed
+545
-201
No files matched your search
@@ -44,6 +44,10 @@
|
||||
// D36:这是全仓唯一的 unsafe 落点,CI 的 `unsafe-scope` job 会比对文件集合。
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
mod exstyle;
|
||||
|
||||
pub use exstyle::{click_through, is_click_through_style, WS_EX_TRANSPARENT};
|
||||
|
||||
use unlucky_presenter::prefs::WINDOW_ALPHA_MIN;
|
||||
|
||||
/// Win32 的 `LWA_ALPHA` 标志位:只改整窗 α(`0x2`)。
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
//! 取证用的**扩展样式探针**(M100 r1 P2-2):这个窗口是不是「点击穿透」。
|
||||
//!
|
||||
//! 为什么单开一个文件:`transparency.rs` 撞 400 行硬线(361 → 447)。`unsafe` 的**放行属性仍只有
|
||||
//! `transparency.rs` 顶部那一个**——lint 级别沿模块树继承,子模块直接继承父模块的
|
||||
//! `#![allow(unsafe_code)]`,所以本文件**不许**再写那行属性(CI 的 `unsafe-scope` 判据是
|
||||
//! 「带放行属性的文件集合恰好等于 `transparency.rs`」)。
|
||||
//!
|
||||
//! 判据来源:winit 0.30 在 Windows 把 `set_cursor_hittest(false)` 落成
|
||||
//! `WS_EX_TRANSPARENT | WS_EX_LAYERED`(`window_state.rs`)。所以读回这一位,就是
|
||||
//! 「取证窗口真的把命中测试关掉了」的 OS 侧证据——比「出图 hash 不变」有判别力得多
|
||||
//! (实测:同 argv 开/关穿透,hash 完全相同)。
|
||||
|
||||
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」冒充「没这一位」。
|
||||
///
|
||||
/// ## 用途
|
||||
///
|
||||
/// 出图时把它打进日志(`pages::harness` 的 `[caps] passthrough_ext=…`),于是「取证窗口到底
|
||||
/// 有没有把指针输入关掉」每次运行都能自证,不依赖外部探针。
|
||||
#[cfg(target_os = "windows")]
|
||||
#[must_use]
|
||||
pub fn click_through(hwnd: isize) -> Option<bool> {
|
||||
if hwnd == 0 {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: `IsWindow` 只按值接受句柄,不持有指针、无所有权转移;无效句柄返回 0。
|
||||
if unsafe { IsWindow(hwnd) } == 0 {
|
||||
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(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};
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user