用户第三轮 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`(写入被接受、回读不可用),**不作为「圆角已生效」的证据**。
116 lines
5.3 KiB
Rust
116 lines
5.3 KiB
Rust
//! 启动阶段的**验收诊断编排**:把 `app/boot.rs` 的 `run()` 收成「探测 → 字体 → 打印 → 起 iced」。
|
||
//!
|
||
//! ## 为什么单独一个文件(M98 r1 p2-1 的根因)
|
||
//!
|
||
//! 第一版把 `[window] requested … resizable=true decorations=false transparent=false` 直接写在
|
||
//! 格式化串里,而 `boot.rs` 给窗口的 `settings` 是另一处常量 —— 两者**没有任何绑定**。定向变异
|
||
//! 把 `settings.decorations` 改成 `true`,371 个测试全绿,而验收包里 `[caps] decorations=true` 与
|
||
//! `[window] decorations=false` 会互相矛盾(踩坑记录 U1:常量冒充观测结果)。
|
||
//!
|
||
//! 现在:窗口设置由 [`window_settings`] **一处**产生,日志由 [`report_window`] 从**同一个
|
||
//! `&window::Settings`** 取字段。钉死它的守门测试在 `boot/tests.rs`(`window_settings_*`)。
|
||
//!
|
||
//! 另一件事:所有「跑没跑」都要能如实说。降级重开那一轮没计时就写 `probe=skipped`,
|
||
//! 不许拿 `0ms` 冒充一次实测(M98 r1 p3-1)。
|
||
|
||
use std::time::Duration;
|
||
|
||
use iced::window;
|
||
|
||
use unlucky_design::color::SchemeFlavor;
|
||
use unlucky_design::typography::Platform as FontPlatform;
|
||
|
||
use crate::cli::Options;
|
||
use crate::probe::Probe;
|
||
use crate::renderer::{acceptance, Decision};
|
||
|
||
use super::MIN_SIZE;
|
||
use crate::app::APP_TITLE;
|
||
|
||
/// 产品窗口设置(**唯一产地**:`launch()` 用它开窗,`[window]` / `[caps]` 从它取字段)。
|
||
///
|
||
/// `ULL_WINDOW_SIZE` 的覆盖在 `boot.rs` 解析成 `size` 后从这里进来 —— 日志与真窗口永远同源。
|
||
pub(super) fn window_settings(size: (f32, f32)) -> window::Settings {
|
||
window::Settings {
|
||
size: iced::Size::new(size.0, size.1),
|
||
min_size: Some(MIN_SIZE),
|
||
// 自绘三键(M93):窗口**不许**带系统装饰,否则 macOS 上会出现原生标题栏 + 自绘三键两套。
|
||
//
|
||
// M182:这一行**同时**是「macOS 四角是直角」的原因——winit 见到 `decorations: false`
|
||
// 会给出 `NSWindowStyleMask::Borderless`(没有 `Titled`),而窗口服务器只给 titled 窗
|
||
// 做圆角裁剪。链条与「要不要迁移原生标题栏」的口径写在 `platform::macos` 的模块文档里,
|
||
// 改动这一行之前先读那一页(迁移必须连同撤掉自绘三键一起做,否则两套控件打架)。
|
||
decorations: false,
|
||
resizable: true,
|
||
// M93 / D36:整窗 α 需要 `WS_EX_LAYERED`,但这里给不了——`window::Settings` 没有扩展样式
|
||
// 字段,写它只有 `Get/SetWindowLongPtrW`(`unsafe`,全仓只允许出现在
|
||
// `platform/win32/transparency.rs`)。所以 α 下发在拿到句柄之后(`Message::WindowHandle`)。
|
||
transparent: false,
|
||
..window::Settings::default()
|
||
}
|
||
}
|
||
|
||
/// `[window] requested` + `[caps]` 两行,**都从同一个 `settings` 取**(不给手抄字面量的机会)。
|
||
pub(super) fn report_window(settings: &window::Settings, scale: Option<f32>, size_source: &str) {
|
||
let facts = acceptance::WindowFacts::from_settings(settings);
|
||
acceptance::report_window(&facts, scale, size_source, Some(APP_TITLE));
|
||
acceptance::report_caps(
|
||
std::env::current_exe().ok().as_deref(),
|
||
facts.transparent,
|
||
facts.resizable,
|
||
facts.decorations,
|
||
);
|
||
}
|
||
|
||
/// 第一段:`[acceptance] on` + `[backend] plan` +(验收模式)`[env]` / `[i18n]`。
|
||
pub(super) fn report_boot(options: &Options, probe: &Probe, decision: &Decision) {
|
||
if acceptance::enabled() {
|
||
acceptance::announce();
|
||
}
|
||
acceptance::report_backend_plan(decision, options.backend);
|
||
if acceptance::enabled() {
|
||
acceptance::report_startup(probe, options.scale_factor, options.screenshot.is_some());
|
||
}
|
||
}
|
||
|
||
/// 第二段:`[font] chain` + `[perf] boot`(都要 `ULL_ACCEPTANCE=1`)。
|
||
///
|
||
/// `probe = None` = 这一轮**没有探测**(既不是 `auto` 也没开验收)⇒ 打 `probe=skipped`。
|
||
pub(super) fn report_font(
|
||
platform: FontPlatform,
|
||
probe: Option<Duration>,
|
||
font: Duration,
|
||
to_run: Duration,
|
||
) {
|
||
if acceptance::enabled() {
|
||
acceptance::report_font_chain(platform);
|
||
acceptance::report_perf_boot(probe, font, to_run);
|
||
}
|
||
}
|
||
|
||
/// 第三段:`[theme]`(恒开:它解释的是「这次主题凭什么是这个」)。
|
||
pub(super) fn report_theme(options: &Options, flavor: SchemeFlavor) {
|
||
let (source, follows) = acceptance::theme_source(
|
||
options.screenshot_theme.is_some(),
|
||
options.theme.is_some(),
|
||
options.screenshot.is_some(),
|
||
);
|
||
let explicit = (!follows).then_some(if flavor.is_dark() { "dark" } else { "light" });
|
||
acceptance::report_theme(source, explicit, follows);
|
||
}
|
||
|
||
/// 第四段:`[backend] final`(恒开)+ `[perf] session`(验收模式)。
|
||
///
|
||
/// `error` = 具名失败原因(`None` = 起来了);`via` 见 `renderer::acceptance::failure_via`。
|
||
/// `refuse` / `none` 两条路**什么都没跑起来** ⇒ `final=none`;只有 `respawn` 才有「换到哪个
|
||
/// 后端」可言(那一格是兜底名,子进程自己还会再打一遍自己的 plan/final)。
|
||
pub(super) fn report_outcome(
|
||
decision: &Decision,
|
||
error: Option<String>,
|
||
via: &str,
|
||
mode: &str,
|
||
elapsed: Duration,
|
||
) {
|
||
acceptance::report_outcome(decision, error.as_deref(), via, mode, elapsed);
|
||
}
|