CI / fmt (rustfmt --check) (push) Waiting to run
CI / clippy (ubuntu-latest) (push) Waiting to run
CI / clippy (windows-latest) (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
CI / build release (ubuntu-latest) (push) Waiting to run
CI / build release (windows-latest) (push) Waiting to run
CI / runtime smoke (ubuntu x64, xvfb) (push) Waiting to run
CI / runtime smoke (macos arm64, probe) (push) Skipped
CI / update-e2e (windows x64) (push) Waiting to run
GPU evidence / GPU 判据自测(不需 GPU;跳过 ≠ 已验证) (push) Waiting to run
GPU evidence / GPU 证据 — 探测可用产物与硬件适配器 (push) Waiting to run
Guards / core 零 UI 守门 (push) Waiting to run
Guards / presenter 零 UI 守门 (push) Waiting to run
Guards / design 零第三方依赖守门 (push) Waiting to run
Guards / unsafe 落点守门(集合必须恰好等于期望) (push) Waiting to run
Guards / 文件行数 ≤400 守门 (push) Waiting to run
Guards / 依赖许可与来源(cargo-deny) (push) Waiting to run
Release guard / 发布标记语义守门(push tag → 稳定版) (push) Waiting to run
Signing status(播报,不是门禁) / 判据自测(三行/退出码/变异重放/接线) (push) Waiting to run
Signing status(播报,不是门禁) / 签名就绪度(播报) (push) Waiting to run
Release / 选槽位(默认四个:Windows 两档 + Linux 两档) (push) Waiting to run
本批内容见 CHANGELOG.md 的 0.2.4 段。按 D41 用孤儿快照推 main,保留「公开史只有里程碑」的模型。
117 lines
5.7 KiB
Rust
117 lines
5.7 KiB
Rust
//! Unlucky-Launcher 桌面壳(`unlucky_app`):Slint UI + Metro 视觉。
|
||
//!
|
||
//! 边界(决策 D3 / D16):本 crate 是**唯一**允许依赖 UI 框架的 crate;
|
||
//! 它只经「快照 + 事件流 + 注入的显示服务」消费 core/agent,不直接触碰其内部类型
|
||
//! (P0 的 host 是 `mock` 假后端,`unlucky_presenter::dto` 是唯一跨界类型集合)。
|
||
//!
|
||
//! 渲染策略(`DevDocs/PlanDoc/01c-native-gui-options.md` §6):
|
||
//! GL 优先(femtovg),启动参数 `--software-renderer` 或 Win7 老机探测失败时切 CPU 软渲染。
|
||
//!
|
||
//! 主题与外观(D17 / 09 §101-113):
|
||
//! - 主题三态 `appearance.theme_mode ∈ {auto, light, dark}`(出厂 auto);
|
||
//! - 「外观(高级)」四件(背景图 / 面板半透明度 / 玻璃拟态 / 强调色)出厂全关;
|
||
//! - 全部走 `core::setting` 的 schema(`unlucky_presenter::settings_bridge`),AI 可写并且**热切**
|
||
//! (tick 里 1s 一次 mtime 比对);系统主题由 `core::platform::theme_probe` 对齐 + 兜底轮询。
|
||
//!
|
||
//! 一个二进制两种用法(M82):`unlucky-launcher` 不带子命令时起 GUI;带子命令
|
||
//! (`instances` / `install` / `launch` / `accounts` / `sessions` / `logs` / `diag` /
|
||
//! `config` / `java` / `agent`)时直接转给 `unlucky_cli::run`,**不初始化任何 UI**。
|
||
//! 子命令表不在这里抄一份——唯一真相源是 `crates/cli` 的解析器(见
|
||
//! `unlucky_cli::cli::looks_like_cli`)。
|
||
//! 零 UI 依赖的 `ull` 仍然保留:它连 fontconfig 都不需要,给服务器 / CI / Termux 用。
|
||
//!
|
||
//! 架构分层(本文件只有一个职责:分发 + 把窗口拉起来):
|
||
//! - `state.rs` / `prefs.rs` / `router.rs` / `dialogs.rs` / `toast.rs`:纯 Rust 状态(可单测,无 Slint)
|
||
//! - `dto/`:跨边界 DTO 与事件/指令
|
||
//! - `conv.rs`:状态 → Slint 视图模型
|
||
//! - `ui_bind.rs` + `callbacks.rs`:窗口/状态/后端缝合 + 回调接线
|
||
//! - `settings_bridge.rs`:`config/launcher.json` 的读写与热切(core::setting 的 schema API)
|
||
//! - `bg_image.rs` + `png.rs`:背景图/模糊层加工与截图编码(09 §101/§103 的预生成手法)
|
||
//! - `mock.rs` + `mock_data.rs`:P0 假后端(脚本化事件)
|
||
//!
|
||
//! 关于 unsafe:本 crate 不写 unsafe,workspace lint 已设 `unsafe_code = "deny"`。
|
||
//! 这里不能升级成 `#![forbid(unsafe_code)]`——Slint 生成的代码自带 `#[allow(unsafe_code)]`,
|
||
//! 而 forbid 无法被内层 allow 覆盖(会直接编译失败)。同理 Slint 生成类型不带文档注释,
|
||
//! 只能在 crate 级放开 `missing_docs`(库 crate 仍由 workspace lint 保持 warn)。
|
||
// 关于 dead_code:P0 骨架里有一批「已定义、等接线」的公共 API——事件通道 / 偏好键登记 /
|
||
// 诊断文案 / mock 数据构造器等,它们被单测覆盖但业务侧要 P1 才用到。逐条 `#[allow]`
|
||
// 会把噪音藏进实现细节,这里统一放开并在 P1 接线时逐个转正(届时删掉本行即可暴露真死码)。
|
||
#![allow(missing_docs, dead_code)]
|
||
|
||
use std::process::ExitCode;
|
||
|
||
mod appearance_bind;
|
||
mod bg_image;
|
||
mod callbacks;
|
||
mod callbacks_pages;
|
||
mod conv;
|
||
mod page_sync;
|
||
mod png;
|
||
mod screenshot;
|
||
mod theme_runtime;
|
||
mod ui_bind;
|
||
|
||
slint::include_modules!();
|
||
|
||
fn main() -> ExitCode {
|
||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||
if let Some(code) = run_cli_if_requested(&args) {
|
||
return code;
|
||
}
|
||
match run_gui() {
|
||
Ok(()) => ExitCode::SUCCESS,
|
||
Err(error) => {
|
||
eprintln!("[run] {error}");
|
||
ExitCode::FAILURE
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 把窗口拉起来(分发之后的唯一路径)。
|
||
fn run_gui() -> Result<(), slint::PlatformError> {
|
||
apply_renderer_flag();
|
||
|
||
let app = ui_bind::AppUi::with_options(MainWindow::new()?, screenshot::app_options());
|
||
|
||
// 截图验收通道(M41 视觉验收;无交互桌面/无 GPU 环境下的唯一可靠取图方式:
|
||
// 不截屏幕,而是让 Slint 把当前场景渲染进内存像素缓冲再自己编码 PNG)。
|
||
// P3 追加页面选择与中间态(安装进度 / 启动事件流),见 `crate::screenshot`。
|
||
if let Some(options) = screenshot::shot_options() {
|
||
screenshot::capture(&app, &options)?;
|
||
return Ok(());
|
||
}
|
||
|
||
app.start_timer();
|
||
app.run()
|
||
}
|
||
|
||
/// 子命令命中 CLI 就跑 CLI 并以它的退出码收尾;返回 `None` 表示「这不是 CLI 调用,去开窗」。
|
||
///
|
||
/// 这条路径上**不构造窗口、不探后端、不扫字体、不依赖任何图形库**(这些库缺失时同一条命令照样
|
||
/// 跑完,实测见 packaging/README.md §7.4)。`unlucky-launcher launch …` 的耗时因此与首帧无关——
|
||
/// 这句是结构性判断,**没有重量**(原因见 §10-8)。
|
||
///
|
||
/// 「这条命令行算不算 CLI 调用」的判据不在这里重复:`unlucky_cli::cli::looks_like_cli` 是唯一
|
||
/// 一处(M82 复审去重),两个 GUI 二进制都只调它,改语义不会漂。
|
||
fn run_cli_if_requested(args: &[String]) -> Option<ExitCode> {
|
||
if !unlucky_cli::cli::looks_like_cli(args) {
|
||
return None;
|
||
}
|
||
let mut stdout = std::io::stdout();
|
||
let mut stderr = std::io::stderr();
|
||
let code = unlucky_cli::run(args, &mut stdout, &mut stderr);
|
||
let _ = std::io::Write::flush(&mut stdout);
|
||
let _ = std::io::Write::flush(&mut stderr);
|
||
Some(ExitCode::from(u8::try_from(code).unwrap_or(2)))
|
||
}
|
||
|
||
/// Win7 老机 / 无可用 OpenGL 驱动时的软渲染兜底开关(01c §6 的 Slint 核心卖点)。
|
||
fn apply_renderer_flag() {
|
||
let software = std::env::args()
|
||
.skip(1)
|
||
.any(|arg| arg == "--software-renderer");
|
||
if software {
|
||
std::env::set_var("SLINT_BACKEND", "winit-software");
|
||
}
|
||
}
|