diff --git a/Cargo.lock b/Cargo.lock index cc2c45b..1cfe70b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1241,6 +1241,7 @@ dependencies = [ "dr-ui", "env_logger", "log", + "winresource", ] [[package]] @@ -8387,6 +8388,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml 1.1.4+spec-1.1.0", + "version_check", +] + [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/apps/darkroom-desktop/Cargo.toml b/apps/darkroom-desktop/Cargo.toml index dc62547..49e18a9 100644 --- a/apps/darkroom-desktop/Cargo.toml +++ b/apps/darkroom-desktop/Cargo.toml @@ -15,5 +15,13 @@ anyhow.workspace = true env_logger.workspace = true log.workspace = true +# The Windows resource block — icon and version — compiled in by build.rs. +# Unconditional rather than under `[target.'cfg(windows)']`, because a cfg on +# a build-dependency is evaluated against the *host* — the machine running +# the build script — and this is built for Windows from Linux. The script +# itself returns before touching the crate on every other target. +[build-dependencies] +winresource = "0.1" + [features] default = [] diff --git a/apps/darkroom-desktop/build.rs b/apps/darkroom-desktop/build.rs new file mode 100644 index 0000000..59a4778 --- /dev/null +++ b/apps/darkroom-desktop/build.rs @@ -0,0 +1,58 @@ +//! TRACES: FR-PLAT-WIN-2 +//! The Windows resource block: icon and version, compiled into the executable. +//! +//! Windows takes an application's icon and its "Details" tab from a resource +//! inside the `.exe`, not from a `.desktop` file, so without this the installed +//! program shows the generic executable icon in Explorer, the Start Menu and +//! the taskbar, and reports no version. Nothing here runs for any other +//! target: the whole body is behind the target-OS check, and the crate that +//! does the work is a build-dependency only. +//! +//! The icon is the same PNG every other platform uses, wrapped into an `.ico` +//! in `OUT_DIR` rather than committed: an ICO entry may *be* a PNG (Vista and +//! later read them directly), so the wrapper is a 22-byte header and the +//! file's bytes, and a generated binary stays out of the tree. + +use std::io::Write as _; +use std::path::PathBuf; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + + let png = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../ui/dr-ui/ui/app-icon.png"); + println!("cargo:rerun-if-changed={}", png.display()); + let bytes = std::fs::read(&png).expect("read app-icon.png"); + let ico = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("darkroom.ico"); + write_png_ico(&ico, &bytes, 256).expect("write darkroom.ico"); + + let mut res = winresource::WindowsResource::new(); + res.set_icon(ico.to_str().unwrap()); + res.set("ProductName", "DarkRoom"); + res.set("FileDescription", "DarkRoom"); + res.set("LegalCopyright", "GPL-3.0-or-later"); + // Cross-compiling: `winresource` looks for a `windres` for the target and + // the Windows image names it explicitly, for the same reason the Android + // image names its linkers. + if let Ok(windres) = std::env::var("WINDRES") { + res.set_windres_path(&windres); + } + res.compile().expect("compile the Windows resource block"); +} + +/// One PNG image as an `.ico`. `edge` is the PNG's width and height; 256 is +/// written as 0 per the format. +fn write_png_ico(path: &std::path::Path, png: &[u8], edge: u32) -> std::io::Result<()> { + let mut f = std::fs::File::create(path)?; + let dim = if edge >= 256 { 0u8 } else { edge as u8 }; + // ICONDIR: reserved, type 1 (icon), one image. + f.write_all(&[0, 0, 1, 0, 1, 0])?; + // ICONDIRENTRY: width, height, palette 0, reserved, planes 1, bpp 32, + // byte length, offset (6 + 16). + f.write_all(&[dim, dim, 0, 0, 1, 0, 32, 0])?; + f.write_all(&(png.len() as u32).to_le_bytes())?; + f.write_all(&22u32.to_le_bytes())?; + f.write_all(png) +} diff --git a/apps/darkroom-desktop/src/main.rs b/apps/darkroom-desktop/src/main.rs index a418e42..07d6655 100644 --- a/apps/darkroom-desktop/src/main.rs +++ b/apps/darkroom-desktop/src/main.rs @@ -1,12 +1,32 @@ //! DarkRoom desktop entry point. //! //! darkroom-desktop ... +//! darkroom-desktop --version + +// TRACES: FR-PLAT-WIN-2 +// A GUI-subsystem executable, or Windows opens a console window behind the +// application for the life of the process. Release only: the console is where +// the log goes when there is no file, and a debug build is run from one. +// `--version` still prints under this — stdout is simply not attached when +// launched from Explorer, which is not where anyone asks for a version. +#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] use std::path::PathBuf; use dr_plat::diagnostics::Installed; fn main() -> anyhow::Result<()> { + // TRACES: FR-PLAT-WIN-3 + // Before the logger, the crash hook and everything else: this exists so a + // build made on a machine that cannot run the application — the Linux CI + // producing the Windows binary, checked under Wine — has an exit that + // proves the executable starts without opening a window or touching the + // user's directories (docs/windows.md §6). + if std::env::args().nth(1).as_deref() == Some("--version") { + println!("darkroom-desktop {}", env!("CARGO_PKG_VERSION")); + return Ok(()); + } + // Built rather than `init`ed, so the same logger can be handed to the // diagnostics tee: `env_logger` keeps writing to stderr exactly as before, // and every record it accepts is also appended to the on-disk log diff --git a/docs/traceability.md b/docs/traceability.md index 5ff50d9..67bc27b 100644 --- a/docs/traceability.md +++ b/docs/traceability.md @@ -9,17 +9,17 @@ Denominators are parsed from [`requirements.md`](requirements.md) at run time, n | Metric | Value | |---|---| -| Source files scanned | 350 | -| TRACES tags found | 1377 | +| Source files scanned | 351 | +| TRACES tags found | 1380 | | Requirements defined | 190 | -| Requirements covered | 134 | -| **Coverage** | **70.5%** (134/190) | +| Requirements covered | 136 | +| **Coverage** | **71.6%** (136/190) | ### By type | Type | Covered | Defined | |---|---|---| -| FR | 101 | 135 | +| FR | 103 | 135 | | NFR | 30 | 49 | | R | 3 | 6 | @@ -119,6 +119,8 @@ _None._ | FR-PLAT-AND-6 | [`apps/darkroom-android/src/lib.rs:397`](../apps/darkroom-android/src/lib.rs#L397) | | FR-PLAT-LIN-1 | [`core/dr-types/src/settings.rs:1`](../core/dr-types/src/settings.rs#L1), [`platform/dr-plat/src/storage.rs:344`](../platform/dr-plat/src/storage.rs#L344), [`ui/dr-ui/src/lib.rs:1122`](../ui/dr-ui/src/lib.rs#L1122), [`ui/dr-ui/src/preset_store.rs:1`](../ui/dr-ui/src/preset_store.rs#L1), [`ui/dr-ui/src/settings_store.rs:1`](../ui/dr-ui/src/settings_store.rs#L1) | | FR-PLAT-LIN-2 | [`platform/dr-plat/src/display.rs:1`](../platform/dr-plat/src/display.rs#L1), [`platform/dr-plat/src/display/wayland.rs:1`](../platform/dr-plat/src/display/wayland.rs#L1), [`platform/dr-plat/src/display/x11.rs:1`](../platform/dr-plat/src/display/x11.rs#L1) | +| FR-PLAT-WIN-2 | [`apps/darkroom-desktop/build.rs:1`](../apps/darkroom-desktop/build.rs#L1), [`apps/darkroom-desktop/src/main.rs:6`](../apps/darkroom-desktop/src/main.rs#L6) | +| FR-PLAT-WIN-3 | [`apps/darkroom-desktop/src/main.rs:19`](../apps/darkroom-desktop/src/main.rs#L19) | | FR-PLG-2 | [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/expr.rs:152`](../core/dr-pipeline/src/declared/expr.rs#L152), [`core/dr-pipeline/src/declared/expr.rs:1`](../core/dr-pipeline/src/declared/expr.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:82`](../core/dr-pipeline/src/declared/mod.rs#L82), [`core/dr-pipeline/src/descriptor.rs:15`](../core/dr-pipeline/src/descriptor.rs#L15), [`core/dr-pipeline/src/descriptor.rs:734`](../core/dr-pipeline/src/descriptor.rs#L734), [`core/dr-pipeline/src/operation.rs:232`](../core/dr-pipeline/src/operation.rs#L232), [`core/dr-pipeline/tests/declared_parity.rs:1`](../core/dr-pipeline/tests/declared_parity.rs#L1), [`core/dr-pipeline/tests/declared_parity.rs:240`](../core/dr-pipeline/tests/declared_parity.rs#L240), [`core/dr-pipeline/tests/declared_parity.rs:305`](../core/dr-pipeline/tests/declared_parity.rs#L305), [`core/dr-pipeline/tests/declared_parity.rs:358`](../core/dr-pipeline/tests/declared_parity.rs#L358), [`core/dr-pipeline/tests/declared_parity.rs:418`](../core/dr-pipeline/tests/declared_parity.rs#L418) | | FR-PLG-2d | [`core/dr-pipeline/src/declared/decl.rs:112`](../core/dr-pipeline/src/declared/decl.rs#L112), [`core/dr-pipeline/src/declared/decl.rs:154`](../core/dr-pipeline/src/declared/decl.rs#L154), [`core/dr-pipeline/src/declared/decl.rs:1`](../core/dr-pipeline/src/declared/decl.rs#L1), [`core/dr-pipeline/src/declared/decl.rs:422`](../core/dr-pipeline/src/declared/decl.rs#L422), [`core/dr-pipeline/src/declared/decl.rs:67`](../core/dr-pipeline/src/declared/decl.rs#L67), [`core/dr-pipeline/src/declared/mod.rs:1`](../core/dr-pipeline/src/declared/mod.rs#L1), [`core/dr-pipeline/src/declared/mod.rs:384`](../core/dr-pipeline/src/declared/mod.rs#L384), [`core/dr-pipeline/src/declared/mod.rs:403`](../core/dr-pipeline/src/declared/mod.rs#L403) | | FR-PLG-8 | [`core/dr-pipeline/src/sidecar.rs:2207`](../core/dr-pipeline/src/sidecar.rs#L2207), [`core/dr-pipeline/src/sidecar.rs:2240`](../core/dr-pipeline/src/sidecar.rs#L2240) | @@ -170,7 +172,7 @@ _None._ ## Not yet tagged -56 of 190 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built. +54 of 190 requirements have no implementation tag. Expected while the codebase is young; each should gain one as it is built.
Show untagged requirements @@ -185,8 +187,6 @@ _None._ - FR-PLAT-AND-1 - FR-PLAT-LIN-3 - FR-PLAT-WIN-1 -- FR-PLAT-WIN-2 -- FR-PLAT-WIN-3 - FR-PLG-1 - FR-PLG-10 - FR-PLG-11