From 8a203f0da7eb5085c3cc5ff9ebd4471c9cb28808 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Tue, 27 Oct 2026 05:14:51 -0700 Subject: [PATCH] feat(icon): a watermelon for every window The recorder window and the viewer carry an icon of their own: a slice of watermelon, drawn as komorebi-recorder/assets/watermelon.svg. `just icon` (assets/generate.py, with resvg from the devShell) renders the PNG the windows set and the ICO that Windows embeds. Until now eframe's default egui icon showed. macOS: eframe sets the PNG as the Dock icon while a window is open. The program is a bare executable and not an app bundle, so Finder is unchanged. Windows: eframe sets the same PNG as the window and taskbar icon. build.rs compiles the ICO into both executables with embed-resource (rc.exe on Windows, llvm-rc when cross-building from a Mac), and the installer shows it in Apps & features through ARPPRODUCTICON. The taskbar, Explorer and the installer have not been looked at on Windows: W4 and W5 in docs/dev/windows-checkpoints.md now say what to check. embed-resource adds seven crates to the lockfile and moves toml_datetime and toml_parser up by a patch release. --- CONTRIBUTING.md | 7 +- Cargo.lock | 103 ++++++++++++++++++++---- docs/PLATFORM.md | 1 + docs/dev/windows-checkpoints.md | 2 + flake.nix | 6 +- justfile | 4 + komorebi-recorder/Cargo.toml | 1 + komorebi-recorder/assets/generate.py | 44 ++++++++++ komorebi-recorder/assets/watermelon.ico | Bin 0 -> 34110 bytes komorebi-recorder/assets/watermelon.png | Bin 0 -> 17250 bytes komorebi-recorder/assets/watermelon.rc | 1 + komorebi-recorder/assets/watermelon.svg | 25 ++++++ komorebi-recorder/build.rs | 25 ++++++ komorebi-recorder/src/app.rs | 1 + komorebi-recorder/src/icon.rs | 99 +++++++++++++++++++++++ komorebi-recorder/src/lib.rs | 2 + komorebi-recorder/src/viewer/mod.rs | 1 + komorebi-recorder/tests/packaging.rs | 26 ++++++ wix/main.wxs | 4 + 19 files changed, 335 insertions(+), 17 deletions(-) create mode 100644 komorebi-recorder/assets/generate.py create mode 100644 komorebi-recorder/assets/watermelon.ico create mode 100644 komorebi-recorder/assets/watermelon.png create mode 100644 komorebi-recorder/assets/watermelon.rc create mode 100644 komorebi-recorder/assets/watermelon.svg create mode 100644 komorebi-recorder/src/icon.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 043dea8b..cd30ebcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,8 +47,8 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ## Development environment The project is built with Nix. `nix develop` (or direnv, whose `.envrc` is `use flake`) provides the -Rust toolchain with nightly `rustfmt`, `cargo-deny`, `cargo-nextest`, `ffmpeg`, `jq`, `just` and -`ripgrep`, and sets `KOMOREBI_MAC_SRC` and `KOMOREBI_WINDOWS_SRC` (with `_COMMIT`) to the komorebi +Rust toolchain with nightly `rustfmt`, `cargo-deny`, `cargo-nextest`, `ffmpeg`, `jq`, `just`, +`resvg` and `ripgrep`, and sets `KOMOREBI_MAC_SRC` and `KOMOREBI_WINDOWS_SRC` (with `_COMMIT`) to the komorebi sources pinned in `flake.lock`. The tests that read komorebi's source need them, so run `cargo` inside the shell. @@ -58,6 +58,9 @@ The Windows code is type-checked from a Mac with `nix develop .#windows`, whose On a Windows machine, the steps in [docs/dev/windows-checkpoints.md](docs/dev/windows-checkpoints.md) say what to run and what to look for. +The icon is `komorebi-recorder/assets/watermelon.svg`. After changing it, run `just icon` to render the PNG that +the windows set and the ICO that Windows embeds, and commit the three together. + ## Checks Before pushing, run diff --git a/Cargo.lock b/Cargo.lock index d9dd36bf..d2e7c5fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,7 +215,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -226,7 +226,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -928,7 +928,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1133,6 +1133,20 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "embed-resource" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6168487b5e7eb2673e6efe1200b1848016e436a6ca0b8031bf04bd9d493a249f" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml", + "vswhom", + "winreg", +] + [[package]] name = "endi" version = "1.1.1" @@ -1197,7 +1211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1918,6 +1932,7 @@ dependencies = [ "dispatch2", "dunce", "eframe", + "embed-resource", "epaint_default_fonts", "libc", "objc2 0.6.4", @@ -3030,7 +3045,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3187,6 +3202,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" +dependencies = [ + "serde_core", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3406,7 +3430,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3425,7 +3449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3527,10 +3551,25 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +name = "toml" +version = "1.1.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] @@ -3549,13 +3588,19 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" + [[package]] name = "tracing" version = "0.1.44" @@ -3633,7 +3678,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3707,6 +3752,26 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -4180,7 +4245,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4678,6 +4743,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/docs/PLATFORM.md b/docs/PLATFORM.md index 78efe185..47af36be 100644 --- a/docs/PLATFORM.md +++ b/docs/PLATFORM.md @@ -117,6 +117,7 @@ The recorder window and the viewer must not be in the video they are recording, |---|---|---| | left out of the video | by name: every process called `komorebi-recorder` is excluded from the capture | each window asks to be left out of captures with `WDA_EXCLUDEFROMCAPTURE` (Windows 10 version 2004 and later) | | left alone by komorebi | the windows have no title, which komorebi does not manage | the same, and `KOMOREBI_RECORDER_PROBE_WINDOW=1` makes a window ask komorebi whether it would manage it and write the answer to `komorebi-recorder-window-probe.txt` in the temp directory | +| icon | a slice of watermelon, which the Dock shows while a window is open (eframe sets it as the application's icon image). The program is a bare executable and not an app bundle, so Finder gives the file the generic executable icon | the same picture as the window and taskbar icon while a window is open, and compiled into both executables as a resource, which is what Explorer shows for the files; the installer shows it beside the program in Apps & features | | executables | `komorebi-recorder` | `komorebi-recorder.exe` (console: run commands with this one) and `komorebi-recorder-gui.exe` (no console window) | ## Degraded modes diff --git a/docs/dev/windows-checkpoints.md b/docs/dev/windows-checkpoints.md index be29b7dd..f80cead7 100644 --- a/docs/dev/windows-checkpoints.md +++ b/docs/dev/windows-checkpoints.md @@ -179,6 +179,7 @@ Status: can be run once W1 shows capture works. 1. Start komorebi, then open the viewer on a Windows session: `komorebi-recorder-gui.exe view C:\temp\w2`. It should open floating (not tiled into your layout) and be usable: moving, resizing, minimizing and closing it. If komorebi tiles it, say so. + Its taskbar button and Alt+Tab entry should show the watermelon icon. 2. Ask komorebi what it thinks of it. Set `KOMOREBI_RECORDER_PROBE_WINDOW=1` before starting the viewer (or the recorder window), wait a few seconds, and send back `%TEMP%\komorebi-recorder-window-probe.txt`. @@ -204,6 +205,7 @@ warn on the first run. That it runs proves the `PATH` entry. `Program Files\komorebi-recorder\bin` should hold `komorebi-recorder.exe` and `komorebi-recorder-gui.exe`. + In Explorer both executables should show the watermelon icon, and so should the entry in Apps & features. 2. Install a second build over it, such as the next nightly. It should upgrade in place: one entry in Apps & features, not two. diff --git a/flake.nix b/flake.nix index 131667b6..61c68960 100644 --- a/flake.nix +++ b/flake.nix @@ -58,7 +58,9 @@ || (pkgs.lib.hasSuffix "/komorebi-recorder/src/guide.md" path) || (pkgs.lib.hasInfix "/komorebi-recorder/tests/fixtures/" path) # the installer, which `tests/packaging.rs` checks - || (pkgs.lib.hasInfix "/wix/" path); + || (pkgs.lib.hasInfix "/wix/" path) + # the icon, which `src/icon.rs` embeds and `build.rs` compiles into the Windows executables + || (pkgs.lib.hasInfix "/komorebi-recorder/assets/" path); }; mkPackages = @@ -299,6 +301,8 @@ pkgs.jq pkgs.just pkgs.ripgrep + # `just icon` renders the watermelon + pkgs.resvg ]; }; diff --git a/justfile b/justfile index d1e7dbb4..ecf7a67f 100644 --- a/justfile +++ b/justfile @@ -56,6 +56,10 @@ record *args: build-windows: nix build .#komorebi-recorder-windows --out-link result-windows +# render the icon: komorebi-recorder/assets/watermelon.svg into the PNG the windows set and the ICO Windows embeds +icon: + python3 komorebi-recorder/assets/generate.py + deps: cargo update just depgen diff --git a/komorebi-recorder/Cargo.toml b/komorebi-recorder/Cargo.toml index 6d1b4e56..c11db88f 100644 --- a/komorebi-recorder/Cargo.toml +++ b/komorebi-recorder/Cargo.toml @@ -144,5 +144,6 @@ windows = { version = "0.62", features = [ ] } [build-dependencies] +embed-resource = "3" proc-macro2 = { version = "1", features = ["span-locations"] } syn = { version = "2", features = ["full", "visit"] } diff --git a/komorebi-recorder/assets/generate.py b/komorebi-recorder/assets/generate.py new file mode 100644 index 00000000..efb499c2 --- /dev/null +++ b/komorebi-recorder/assets/generate.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Renders watermelon.svg into the two files the program uses. + +watermelon.png (256 by 256) is the icon every window sets while the program runs, and +watermelon.ico is the icon Windows shows for the executables and for the installer. Needs +`resvg` on PATH, which `nix develop` provides; run it with `just icon`. +""" + +import struct +import subprocess +import tempfile +from pathlib import Path + +HERE = Path(__file__).resolve().parent +SIZES = [16, 24, 32, 48, 64, 128, 256] + + +def render(size, directory): + out = Path(directory) / f"{size}.png" + subprocess.run( + ["resvg", "-w", str(size), "-h", str(size), str(HERE / "watermelon.svg"), str(out)], + check=True, + ) + return out.read_bytes() + + +def ico(images): + """An ICO file whose entries are PNGs, which Windows has read since Vista. A size of 256 is + written as 0.""" + offset = 6 + 16 * len(images) + entries, data = b"", b"" + for size, png in images: + entries += struct.pack( + "xsTL*Gag0N4};03sFTB@ke7VE;`c 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 literal 0 HcmV?d00001 diff --git a/komorebi-recorder/assets/watermelon.png b/komorebi-recorder/assets/watermelon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a7bd3db41e2b4004b5dd624fe5972567bda075e GIT binary patch literal 17250 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 literal 0 HcmV?d00001 diff --git a/komorebi-recorder/assets/watermelon.rc b/komorebi-recorder/assets/watermelon.rc new file mode 100644 index 00000000..f6564ec3 --- /dev/null +++ b/komorebi-recorder/assets/watermelon.rc @@ -0,0 +1 @@ +1 ICON "watermelon.ico" diff --git a/komorebi-recorder/assets/watermelon.svg b/komorebi-recorder/assets/watermelon.svg new file mode 100644 index 00000000..baa8e0d1 --- /dev/null +++ b/komorebi-recorder/assets/watermelon.svg @@ -0,0 +1,25 @@ + + A slice of watermelon + + + + + + + + + + + + + + + + + + + + + + + diff --git a/komorebi-recorder/build.rs b/komorebi-recorder/build.rs index 12e32855..34e25141 100644 --- a/komorebi-recorder/build.rs +++ b/komorebi-recorder/build.rs @@ -20,6 +20,9 @@ //! `KOMOREBI_WINDOWS_COMMIT` for a build outside a checkout (nix). //! //! If a source is not there the catalog is empty and says so. +//! +//! The script also gives the Windows executables their icon, which Explorer reads from a resource in the file: see +//! [`embed_icon`]. use proc_macro2::TokenStream; use proc_macro2::TokenTree; @@ -482,11 +485,33 @@ fn index(flavor: &Flavor) -> String { out } +/// Compiles the icon into the executables when the target is Windows. The windows set their own icon while they run +/// (`src/icon.rs`), which is not what Explorer shows for the file, and the command-line build has no window at all. +/// +/// A Windows host compiles `assets/watermelon.rc` with `rc.exe`, and any other host with `llvm-rc`, as the Windows +/// build in `flake.nix` does. `llvm-rc` reads a copy of the file from the build directory, so the icon is found by +/// naming `assets` as an include directory and not by its place next to the `.rc`. +fn embed_icon() { + println!("cargo:rerun-if-changed=assets/watermelon.rc"); + println!("cargo:rerun-if-changed=assets/watermelon.ico"); + + if env::var("CARGO_CFG_TARGET_OS").is_ok_and(|target| target == "windows") { + embed_resource::compile( + "assets/watermelon.rc", + embed_resource::ParamsIncludeDirs(&["assets"]), + ) + .manifest_required() + .expect("compile the icon resource"); + } +} + fn main() { let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); println!("cargo:rerun-if-changed=build.rs"); + embed_icon(); + let out: String = FLAVORS.iter().map(index).collect(); fs::write(out_dir.join("log_catalog.rs"), out).expect("write log_catalog.rs"); diff --git a/komorebi-recorder/src/app.rs b/komorebi-recorder/src/app.rs index 6714de8f..8c587f42 100644 --- a/komorebi-recorder/src/app.rs +++ b/komorebi-recorder/src/app.rs @@ -170,6 +170,7 @@ pub fn run(args: RecordArgs) -> eyre::Result<()> { viewport: egui::ViewportBuilder::default() // no title: komorebi leaves untitled windows alone .with_title("") + .with_icon(crate::icon::window_icon()) .with_decorations(false) .with_inner_size([420.0, 380.0]) .with_min_inner_size([360.0, 260.0]) diff --git a/komorebi-recorder/src/icon.rs b/komorebi-recorder/src/icon.rs new file mode 100644 index 00000000..09cd58c5 --- /dev/null +++ b/komorebi-recorder/src/icon.rs @@ -0,0 +1,99 @@ +//! The icon of every window the program opens: a slice of watermelon. +//! +//! The picture is `assets/watermelon.svg`. `assets/generate.py` (`just icon`) renders it into `assets/watermelon.png`, +//! which this module embeds, and `assets/watermelon.ico`, which Windows shows for the executables (`build.rs` compiles +//! it into them) and for the installer. What each host does with the icon is in `docs/PLATFORM.md`. + +use eframe::egui; +use std::io::Cursor; + +/// The watermelon, 256 by 256 pixels of RGBA. +const PNG: &[u8] = include_bytes!("../assets/watermelon.png"); + +/// Returns the icon of the program's windows: a slice of watermelon on a transparent background, 256 by 256 pixels. +/// +/// The recorder window and the viewer pass it to [`egui::ViewportBuilder::with_icon`]. eframe sets it as the window and +/// taskbar icon on Windows and as the Dock icon on macOS. +/// +/// The picture is part of the binary, and a test holds that it decodes. If it ever did not, this returns an empty +/// [`egui::IconData`], which the window system takes to mean no icon: the window opens with the system's default. +/// +/// # Examples +/// +/// ```rust +/// let icon = komorebi_recorder::icon::window_icon(); +/// +/// assert_eq!((icon.width, icon.height), (256, 256)); +/// assert_eq!(icon.rgba.len(), 256 * 256 * 4); +/// +/// // the corner of the picture is transparent and its middle is not +/// assert_eq!(icon.rgba[3], 0); +/// assert_eq!(icon.rgba[(128 * 256 + 128) * 4 + 3], 255); +/// ``` +pub fn window_icon() -> egui::IconData { + decode().unwrap_or_default() +} + +/// The picture as RGBA, or nothing if it is not an RGBA PNG with eight bits a channel, which is what `generate.py` +/// writes. +fn decode() -> Option { + let mut reader = png::Decoder::new(Cursor::new(PNG)).read_info().ok()?; + let mut rgba = vec![0; reader.output_buffer_size()?]; + let info = reader.next_frame(&mut rgba).ok()?; + + (info.color_type == png::ColorType::Rgba && info.bit_depth == png::BitDepth::Eight).then(|| { + rgba.truncate(info.buffer_size()); + egui::IconData { + rgba, + width: info.width, + height: info.height, + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + const ICO: &[u8] = include_bytes!("../assets/watermelon.ico"); + + #[test] + fn the_embedded_picture_decodes() { + let icon = decode().expect("watermelon.png is an RGBA PNG with eight bits a channel"); + + assert_eq!(icon.rgba.len(), (icon.width * icon.height * 4) as usize); + assert_eq!(window_icon(), icon); + } + + #[test] + fn the_windows_icon_holds_the_same_picture_at_256_pixels() { + assert_eq!(ICO[..4], [0, 0, 1, 0], "an icon file starts with its type"); + let count = usize::from(u16::from_le_bytes([ICO[4], ICO[5]])); + assert!( + count > 1, + "one size is not enough for a taskbar and a window" + ); + + let mut widths = Vec::new(); + for entry in ICO[6..].as_chunks::<16>().0.iter().take(count) { + // a width of 0 means 256 + let width = entry[0]; + let pixels = if width == 0 { 256 } else { u32::from(width) }; + let length = u32::from_le_bytes([entry[8], entry[9], entry[10], entry[11]]) as usize; + let offset = u32::from_le_bytes([entry[12], entry[13], entry[14], entry[15]]) as usize; + let image = &ICO[offset..offset + length]; + + assert!( + image.starts_with(b"\x89PNG"), + "the {pixels} pixel entry is not a PNG" + ); + if width == 0 { + assert_eq!(image, PNG, "the 256 pixel entry is not watermelon.png"); + } + widths.push(width); + } + + assert!(widths.contains(&0), "no 256 pixel entry in {widths:?}"); + assert!(widths.contains(&16), "no 16 pixel entry in {widths:?}"); + } +} diff --git a/komorebi-recorder/src/lib.rs b/komorebi-recorder/src/lib.rs index c24f89b9..7ce6b28d 100644 --- a/komorebi-recorder/src/lib.rs +++ b/komorebi-recorder/src/lib.rs @@ -55,6 +55,7 @@ //! | [`annotations`] | the bug regions, notes and findings written into a session | //! | [`recovery`] | rebuilding a session whose recorder died | //! | [`cli`] | the command line | +//! | [`icon`] | the icon of the windows | //! | [`app`] | the recorder window | //! | [`av`] and [`selftest`] | decoding the recorded video and checking it against the clocks | //! | [`doctor`] | what a recording leans on, and probes that measure it | @@ -82,6 +83,7 @@ pub mod cli; pub mod clock; pub mod doctor; pub mod frames; +pub mod icon; pub mod incidents; pub mod komorebi_link; pub mod lock; diff --git a/komorebi-recorder/src/viewer/mod.rs b/komorebi-recorder/src/viewer/mod.rs index 1655eeab..fc332832 100644 --- a/komorebi-recorder/src/viewer/mod.rs +++ b/komorebi-recorder/src/viewer/mod.rs @@ -113,6 +113,7 @@ pub fn run(session: Option) -> eyre::Result<()> { // no title: komorebi leaves untitled windows alone, so opening the viewer does not // move the layout of the machine whose behavior is being investigated .with_title("") + .with_icon(crate::icon::window_icon()) .with_title_shown(false) .with_titlebar_shown(false) .with_fullsize_content_view(true) diff --git a/komorebi-recorder/tests/packaging.rs b/komorebi-recorder/tests/packaging.rs index 92c62c0c..d7bee2cf 100644 --- a/komorebi-recorder/tests/packaging.rs +++ b/komorebi-recorder/tests/packaging.rs @@ -138,3 +138,29 @@ fn the_license_the_installer_shows_is_there_and_is_the_projects() { "main.wxs does not refer to wix\\License.rtf" ); } + +#[test] +fn the_installer_shows_the_icon_the_executables_carry() { + let text = installer(); + + let id = attribute(&text, "Icon", "Id"); + let source = attribute(&text, "Icon", "SourceFile"); + assert!( + text.contains(&format!(" + + + +