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.
This commit is contained in:
LGUG2Z committed 2026-10-27 22:22:56 -07:00
1 parent df4ab2d2a5
commit 8a203f0da7
19 files changed
+335 -17

No files matched your search

+5 -2
View File
@@ -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
Generated
+89 -14
View File
@@ -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"
+1
View File
@@ -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
+2
View File
@@ -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.
+5 -1
View File
@@ -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
];
};
+4
View File
@@ -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
+1
View File
@@ -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"] }
+44
View File
@@ -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(
"<BBBBHHII", size % 256, size % 256, 0, 0, 1, 32, len(png), offset + len(data)
)
data += png
return struct.pack("<HHH", 0, 1, len(images)) + entries + data
with tempfile.TemporaryDirectory() as directory:
images = [(size, render(size, directory)) for size in SIZES]
(HERE / "watermelon.png").write_bytes(images[-1][1])
(HERE / "watermelon.ico").write_bytes(ico(images))
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+1
View File
@@ -0,0 +1 @@
1 ICON "watermelon.ico"
+25
View File
@@ -0,0 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<title>A slice of watermelon</title>
<defs>
<linearGradient id="flesh" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ff6f7d"/>
<stop offset="1" stop-color="#ea3a52"/>
</linearGradient>
</defs>
<g transform="translate(239.1 193.8) rotate(-30) scale(1.054)">
<path d="M-236 0 A236 236 0 0 0 236 0 Z" fill="#2a7a3d" stroke="#2a7a3d" stroke-width="18" stroke-linejoin="round"/>
<path d="M-212 0 A212 212 0 0 0 212 0 Z" fill="#9bd36b"/>
<path d="M-194 0 A194 194 0 0 0 194 0 Z" fill="#f3f8dc"/>
<path d="M-182 0 A182 182 0 0 0 182 0 Z" fill="url(#flesh)"/>
<g transform="translate(56.7 44.3) rotate(-52.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(22.2 68.5) rotate(-18.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(-22.2 68.5) rotate(18.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(-56.7 44.3) rotate(52.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(124.0 45.1) rotate(-70.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(91.7 95.0) rotate(-44.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(40.8 125.5) rotate(-18.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(-18.4 130.7) rotate(8.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(-73.8 109.4) rotate(34.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
<g transform="translate(-114.3 66.0) rotate(60.0)"><path d="M0 -21 C13 -7 13 15 0 21 C-13 15 -13 -7 0 -21 Z" fill="#2b1d1d"/><ellipse cx="-4.5" cy="-1" rx="2.6" ry="7" fill="#ffffff" opacity=".28"/></g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+25
View File
@@ -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");
+1
View File
@@ -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])
+99
View File
@@ -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<egui::IconData> {
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:?}");
}
}
+2
View File
@@ -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;
+1
View File
@@ -113,6 +113,7 @@ pub fn run(session: Option<PathBuf>) -> 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)
+26
View File
@@ -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!("<Property Id='ARPPRODUCTICON' Value='{id}'")),
"Apps & features is not told to show the icon {id}"
);
assert!(
repository().join(source.replace('\\', "/")).is_file(),
"wix/main.wxs takes its icon from {source}, which is not there"
);
// the resource build.rs compiles into the executables names the same file
let resource =
std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/watermelon.rc"))
.expect("assets/watermelon.rc");
let icon = source.rsplit('\\').next().expect("a file name");
assert!(
resource.contains(&format!("\"{icon}\"")),
"the executables carry an icon other than the installer's {icon}: {resource}"
);
}
+4
View File
@@ -112,6 +112,10 @@
<Property Id='ARPHELPLINK' Value='https://github.com/LGUG2Z/komorebi-recorder' />
<!-- the icon of the entry in Apps & features; the executables carry the same one -->
<Icon Id='watermelon.ico' SourceFile='komorebi-recorder\assets\watermelon.ico' />
<Property Id='ARPPRODUCTICON' Value='watermelon.ico' />
<UI>
<UIRef Id='WixUI_FeatureTree' />
</UI>