fix(m182): 非 Windows 圆角判据回答「由谁负责」,并给 macOS 直角三条源码级证据
用户第三轮 Mac 验收看到四角直角 + `source=square os=unknown`。原来非 Windows 一律 落到 `UnknownOs`,把「Windows 版本读不出来」与「这台根本不是 Windows」糊成同一格。 - `platform/host.rs`:新增 `HostOs`——平台族是一个**值**而不是散落的 `cfg`。 这样三行真值表能在本机(只有 Windows)一次验完,`cfg` 只留在真要 FFI 的地方。 - `plan_corner` 拆成 `plan_corner_for(policy, host, os, backend)` + 保持原签名的薄包装 (`app/update/appearance.rs` 在 scope 外,调用点一字未改)。 - `CornerSource` 加第三档 `Compositor`(跟随合成器,我们既不下发也不自绘); `CornerExclusion` 加 `borderless`(macOS)、`client-side`(Linux)、`unsupported`。 - 日志加 `host=`,非 Windows 的 `os=` 由 `unknown` 改 `n/a`(那栏是 Windows 版本, 在 macOS/Linux 上不是「没读到」而是「跟本机无关」)。 - `platform/macos/mod.rs`:macOS 为什么是直角的**三环证据**(我们的 `decorations: false` → winit `Borderless`(无 `Titled`)→ 窗口服务器只裁 titled 窗;winit macOS 后端 `cornerRadius` 命中 0),以及「迁移原生标题栏」的未实现配方与阻塞点(iced 0.14 未转发 winit 的 `titlebar_buttons_hidden`,缩放键按不掉)。 - `platform/linux/mod.rs`:xdg-shell 无圆角概念、wl_surface 区域只能是矩形、XShape 只能 切硬边 ⇒ CSD 窗没人替我们裁,D46 排除自绘 ⇒ 直角。没把 DWM 判据搬过去(单测钉着 「非 Windows 不许出现 dwm」)。 - `design/shape.rs`:删掉「macOS 走 `layer.cornerRadius`」这句过期描述(D46 不许自绘裁切)。 - `dwm.rs` 的内联测试挪到 `dwm/tests.rs`(原文件 398 行,加第三档映射会顶破 400 行红线)。 验证:cargo fmt --check / clippy -D warnings / `cargo test -p unlucky-app-iced --lib` 684 passed;三次定向变异各自变红(macOS 行改回 Square/UnknownOs → 4 红;非 Windows `os=` 改回 unknown → 1 红;Linux 原因码复制成 borderless → 4 红)。 Windows 侧无语义改动;`cargo test --lib m116 -- --ignored --nocapture` 复跑读回仍是 `pref=2 readback=? read_hr=0x80070057`(写入被接受、回读不可用),**不作为「圆角已生效」的证据**。
This commit is contained in:
1 parent
2a004a5754
commit
0451b3f972
11 files changed
+793
-192
No files matched your search
@@ -102,11 +102,16 @@ const E_INVALIDARG: u32 = 0x8007_0057;
|
||||
/// `Dwm` → [`DWMWCP_ROUND`];`Square` → [`DWMWCP_DONOTROUND`]。
|
||||
/// 刻意**不用**默认值(`0`):那在 Win11 上等于「请 DWM 自己决定」,而无边框窗口
|
||||
/// 到底算不算「有框」由 DWM 的启发式决定——那正是我们要么拿到、要么明确放弃的东西。
|
||||
///
|
||||
/// M182 的 `Compositor` 档(跟随合成器)在 Windows 上**不可达**(`plan_corner_for` 不产出它,
|
||||
/// 单测钉着这一点);真要出现,正确的落点是默认值 `0`(= 让系统自己决定),
|
||||
/// 而不是被当成「强制直角」——否则 `source=compositor` 那行日志会与下发值自相矛盾。
|
||||
#[must_use]
|
||||
pub const fn preference_for(source: super::super::corner::CornerSource) -> u32 {
|
||||
use super::super::corner::CornerSource;
|
||||
match source {
|
||||
CornerSource::Dwm => DWMWCP_ROUND,
|
||||
CornerSource::Compositor => 0,
|
||||
CornerSource::Square => DWMWCP_DONOTROUND,
|
||||
}
|
||||
}
|
||||
@@ -282,94 +287,7 @@ pub fn style_summary(hwnd: isize) -> String {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
apply_corner, corner_preference_hr, preference_for, CornerOutcome,
|
||||
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_DONOTROUND,
|
||||
DWMWCP_ROUND,
|
||||
};
|
||||
use crate::platform::win32::corner::{CornerPlan, CornerSource};
|
||||
use unlucky_presenter::prefs::WindowsVersion;
|
||||
|
||||
/// **抓得住**:属性号写错(34 写成 33 / 37 写成 36 —— 这两个都在 Win11 上存在,改错不会崩,
|
||||
/// 只会静默读出别的东西)、两个偏好值互换。
|
||||
/// **抓不住**:这些值在真窗口上是否真的生效(那要探针读数,见 `probe` 与报告)。
|
||||
#[test]
|
||||
fn attribute_numbers_follow_the_win32_header() {
|
||||
assert_eq!(DWMWA_WINDOW_CORNER_PREFERENCE, 34);
|
||||
assert_eq!(DWMWA_VISIBLE_FRAME_BORDER_THICKNESS, 37);
|
||||
assert_eq!(DWMWCP_ROUND, 2);
|
||||
assert_eq!(DWMWCP_DONOTROUND, 1);
|
||||
}
|
||||
|
||||
/// **抓得住**:`preference_for` 把圆角/直角映射反了(这一格反了就是「设置里选直角反而更圆」)。
|
||||
#[test]
|
||||
fn the_preference_mapping_cannot_be_swapped() {
|
||||
assert_eq!(preference_for(CornerSource::Dwm), DWMWCP_ROUND);
|
||||
assert_eq!(preference_for(CornerSource::Square), DWMWCP_DONOTROUND);
|
||||
}
|
||||
|
||||
/// 无效句柄:**不下发**、也不 panic。句柄 0 走 `NoWindow`(还没拿到窗口 ≠ 平台不支持)。
|
||||
#[test]
|
||||
fn a_missing_window_is_reported_not_guessed() {
|
||||
let plan = CornerPlan {
|
||||
source: CornerSource::Dwm,
|
||||
excluded: None,
|
||||
os: Some(WindowsVersion::new(10, 0, 26_100)),
|
||||
};
|
||||
assert_eq!(apply_corner(0, &plan), CornerOutcome::NoWindow);
|
||||
assert_eq!(corner_preference_hr(0).0, None);
|
||||
// 1 号句柄永远无效:非 Windows 上是「平台不支持」,Windows 上是 DWM 的失败码,
|
||||
// 两种都不许表现成「成功」。
|
||||
let outcome = apply_corner(1, &plan);
|
||||
assert!(
|
||||
matches!(
|
||||
outcome,
|
||||
CornerOutcome::Failed { .. }
|
||||
| CornerOutcome::Unsupported { .. }
|
||||
| CornerOutcome::UnsupportedPlatform
|
||||
),
|
||||
"无效句柄应报失败,实际 {outcome:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// 日志尾巴三态可区分:设了 / 设了但读不回 / 属性不支持。
|
||||
#[test]
|
||||
fn the_log_tail_separates_applied_from_unsupported() {
|
||||
let applied = CornerOutcome::Applied {
|
||||
preference: DWMWCP_ROUND,
|
||||
readback: Some(DWMWCP_ROUND),
|
||||
read_hresult: 0,
|
||||
};
|
||||
assert!(
|
||||
applied.describe().starts_with("pref=2 readback=2"),
|
||||
"{applied:?}"
|
||||
);
|
||||
|
||||
let blind = CornerOutcome::Applied {
|
||||
preference: DWMWCP_ROUND,
|
||||
readback: None,
|
||||
read_hresult: 0x8000_4005,
|
||||
};
|
||||
assert!(
|
||||
blind.describe().contains("readback=?"),
|
||||
"{}",
|
||||
blind.describe()
|
||||
);
|
||||
|
||||
let old = CornerOutcome::Unsupported {
|
||||
hresult: 0x8007_0057,
|
||||
};
|
||||
assert!(
|
||||
old.describe().contains("attribute unsupported"),
|
||||
"{}",
|
||||
old.describe()
|
||||
);
|
||||
assert!(CornerOutcome::UnsupportedPlatform
|
||||
.describe()
|
||||
.contains("platform=unsupported"));
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
||||
/// **抓得住**:`frame_label` 把 `WS_POPUP` 与 `WS_OVERLAPPEDWINDOW` 判反(那会让日志把
|
||||
/// 「我们自己的窗口」写成「普通窗口」,这行日志的全部价值就是分辨这个)。
|
||||
|
||||
Reference in new issue
Block a user