From be6a2d6a8b76262d896399c20122728c86bfcaaf Mon Sep 17 00:00:00 2001 From: landon Date: Tue, 25 Aug 2026 18:15:21 -0500 Subject: [PATCH] Give the app an icon, and a script that installs it The launcher pointed at `Icon=story-editor`, a name that only the Breeze theme happened to ship, and only at 16 and 22 px -- so anywhere the desktop wanted a larger icon it fell back to something generic. assets/md-manuscript.svg is the source: a sheaf of manuscript pages, an amber chapter-title line over three of prose. Drawn in plain rounded rectangles because a finer design silts up into a grey smear at 16 px. It is rasterised to the eight sizes a desktop asks for, and the 256px PNG is compiled into the binary as the window icon, so the title bar and the task switcher carry it whether or not anything is installed. That costs no new dependency: eframe already pulls `image` with png decoding. A test decodes the bundled PNG, since a rename would otherwise show up only as a blank icon at runtime. Installation is now `./assets/install.sh` -- binary, icons, launcher and both cache refreshes, safe to re-run after a rebuild. The launcher is a file in the repository rather than a here-document in the instructions: pasting one into a terminal leaves the shell sat at a `>` prompt waiting for a terminator that never arrives, which is exactly what happened, and this way the launcher is versioned along with everything else. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01GYSGPDwkSzhm4qLqjCbqxU --- INSTALL-debian12-surface.md | 76 ++++++++++++++---- .../hicolor/128x128/apps/md-manuscript.png | Bin 0 -> 5536 bytes .../hicolor/16x16/apps/md-manuscript.png | Bin 0 -> 710 bytes .../hicolor/22x22/apps/md-manuscript.png | Bin 0 -> 1024 bytes .../hicolor/24x24/apps/md-manuscript.png | Bin 0 -> 1100 bytes .../hicolor/256x256/apps/md-manuscript.png | Bin 0 -> 12428 bytes .../hicolor/32x32/apps/md-manuscript.png | Bin 0 -> 1509 bytes .../hicolor/48x48/apps/md-manuscript.png | Bin 0 -> 2280 bytes .../hicolor/64x64/apps/md-manuscript.png | Bin 0 -> 2700 bytes .../hicolor/scalable/apps/md-manuscript.svg | 14 ++++ assets/install.sh | 43 ++++++++++ assets/md-manuscript.desktop | 9 +++ assets/md-manuscript.svg | 14 ++++ src/main.rs | 44 +++++++++- 14 files changed, 182 insertions(+), 18 deletions(-) create mode 100644 assets/icons/hicolor/128x128/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/16x16/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/22x22/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/24x24/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/256x256/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/32x32/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/48x48/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/64x64/apps/md-manuscript.png create mode 100644 assets/icons/hicolor/scalable/apps/md-manuscript.svg create mode 100755 assets/install.sh create mode 100644 assets/md-manuscript.desktop create mode 100644 assets/md-manuscript.svg diff --git a/INSTALL-debian12-surface.md b/INSTALL-debian12-surface.md index 6ef7115..7a1a259 100644 --- a/INSTALL-debian12-surface.md +++ b/INSTALL-debian12-surface.md @@ -92,26 +92,74 @@ applications menu (GNOME app grid / XFCE Whisker Menu) and can be pinned for touch: ```sh -# 4a. Binary +./assets/install.sh +``` + +That puts the binary on your `PATH`, installs the icon at every size the desktop +might ask for, and adds the launcher — then refreshes both caches. It is safe to +re-run after every rebuild, which is how you pick up a new version. Installing +somewhere else is `PREFIX=/usr/local sudo ./assets/install.sh`. + +
+What it does, if you would rather do it by hand + +```sh install -Dm755 target/release/md-manuscript ~/.local/bin/md-manuscript -# 4b. Desktop launcher -mkdir -p ~/.local/share/applications -cat > ~/.local/share/applications/md-manuscript.desktop <<'EOF' -[Desktop Entry] -Type=Application -Name=md-manuscript -Comment=Draggable markdown manuscript editor with git sync and ODT export -Exec=md-manuscript -Icon=text-editor -Categories=Office;TextEditor; -Terminal=false -EOF +for size in 16 22 24 32 48 64 128 256; do + install -Dm644 "assets/icons/hicolor/${size}x${size}/apps/md-manuscript.png" \ + ~/.local/share/icons/hicolor/${size}x${size}/apps/md-manuscript.png +done +install -Dm644 assets/icons/hicolor/scalable/apps/md-manuscript.svg \ + ~/.local/share/icons/hicolor/scalable/apps/md-manuscript.svg -# 4c. Refresh the app list +install -Dm644 assets/md-manuscript.desktop \ + ~/.local/share/applications/md-manuscript.desktop + +gtk-update-icon-cache -f -t ~/.local/share/icons/hicolor 2>/dev/null || true update-desktop-database ~/.local/share/applications 2>/dev/null || true ``` +The launcher is `assets/md-manuscript.desktop`, kept in the repository and +copied into place rather than typed out at the prompt — a here-document is an +awkward thing to paste into a terminal, and this way the launcher is versioned +along with everything else. + +
+ +`Icon=md-manuscript` in the launcher is a *name*, not a path: the desktop looks +it up in the icon theme and picks whichever size it wants, which is why the whole +set gets installed rather than one file. `StartupWMClass` matches the running +window to the launcher, so a pinned icon highlights rather than spawning a second +entry. + +The window carries the icon itself as well — the 256px PNG is compiled into the +binary — so the title bar and the task switcher show it even before any of this +is installed. + +### Regenerating the icon + +`assets/md-manuscript.svg` is the source. After editing it, rebuild the PNG set +(ImageMagick rasterises the SVG; Pillow does the downscaling, which is markedly +cleaner at 16 and 24 px): + +```sh +python3 - <<'REGEN' +import os, subprocess +from PIL import Image +subprocess.run(["convert", "-background", "none", "-density", "1200", + "assets/md-manuscript.svg", "-resize", "512x512", + "/tmp/md-manuscript-master.png"], check=True) +im = Image.open("/tmp/md-manuscript-master.png").convert("RGBA") +for size in (16, 22, 24, 32, 48, 64, 128, 256): + d = f"assets/icons/hicolor/{size}x{size}/apps" + os.makedirs(d, exist_ok=True) + im.resize((size, size), Image.LANCZOS).save(f"{d}/md-manuscript.png") +REGEN +cp assets/md-manuscript.svg assets/icons/hicolor/scalable/apps/md-manuscript.svg +cargo build --release # the 256px PNG is baked into the binary +``` + Make sure `~/.local/bin` is on your `PATH` (it is by default on Debian 12 if the directory exists at login — otherwise add this to `~/.bashrc` and re-login): diff --git a/assets/icons/hicolor/128x128/apps/md-manuscript.png b/assets/icons/hicolor/128x128/apps/md-manuscript.png new file mode 100644 index 0000000000000000000000000000000000000000..3be4b6dab917552ea3202a3d9fb0d7abea39a84c GIT binary patch literal 5536 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 iKCr#=*->5fqxpYzrtcIV*X14n0000Y+g5X+8 zqayA^(Uol})esd51`5HCMny9*>11Z!nfD$SV}kMPtj@jX{vLkk-g5;glzWEGB<0@k zjrCSgW3A;wk!2xdSsH4yPk*aEnLO;uJwspWvUne`Bo!+DpB;#Y{sWUnp|>_p6K@KSH_GMQAmVm4p0r z{51K<;l1PSSlThh&}`J%yk(e!M~>p00abkR2}#mL(08=U0U;<+ z8{`TxgKJjs%ZWXhD96P39nxlN(ia{vGU literal 0 HcmV?d00001 diff --git a/assets/icons/hicolor/22x22/apps/md-manuscript.png b/assets/icons/hicolor/22x22/apps/md-manuscript.png new file mode 100644 index 0000000000000000000000000000000000000000..1bbdb04bb02a4e55500269e8d6b209f58df5e1e3 GIT binary patch literal 1024 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 + md-manuscript + + + + + + + + + + + diff --git a/assets/install.sh b/assets/install.sh new file mode 100755 index 0000000..d72b9d4 --- /dev/null +++ b/assets/install.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# Install md-manuscript for the current user: the binary, the icon set and the +# desktop launcher. Safe to re-run; it overwrites what it installed last time. +# +# Everything lands under ~/.local, so no root is needed. Set PREFIX to install +# somewhere else (PREFIX=/usr/local sudo ./assets/install.sh). +set -eu + +# Resolve paths against the repo, not the working directory, so this runs from +# anywhere. +here=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +prefix=${PREFIX:-$HOME/.local} +binary=$here/target/release/md-manuscript + +if [ ! -x "$binary" ]; then + echo "No release binary at $binary" >&2 + echo "Build it first: cargo build --release" >&2 + exit 1 +fi + +install -Dm755 "$binary" "$prefix/bin/md-manuscript" + +for size in 16 22 24 32 48 64 128 256; do + install -Dm644 \ + "$here/assets/icons/hicolor/${size}x${size}/apps/md-manuscript.png" \ + "$prefix/share/icons/hicolor/${size}x${size}/apps/md-manuscript.png" +done +install -Dm644 "$here/assets/icons/hicolor/scalable/apps/md-manuscript.svg" \ + "$prefix/share/icons/hicolor/scalable/apps/md-manuscript.svg" + +install -Dm644 "$here/assets/md-manuscript.desktop" \ + "$prefix/share/applications/md-manuscript.desktop" + +# Best-effort cache refreshes: the launcher works without them, it may just take +# a re-login to show up. +gtk-update-icon-cache -f -t "$prefix/share/icons/hicolor" 2>/dev/null || true +update-desktop-database "$prefix/share/applications" 2>/dev/null || true + +echo "Installed to $prefix" +case ":$PATH:" in + *":$prefix/bin:"*) ;; + *) echo "Note: $prefix/bin is not on your PATH." >&2 ;; +esac diff --git a/assets/md-manuscript.desktop b/assets/md-manuscript.desktop new file mode 100644 index 0000000..ca09718 --- /dev/null +++ b/assets/md-manuscript.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Type=Application +Name=md-manuscript +Comment=Draggable markdown manuscript editor with git sync and ODT export +Exec=md-manuscript +Icon=md-manuscript +StartupWMClass=md-manuscript +Categories=Office;TextEditor; +Terminal=false diff --git a/assets/md-manuscript.svg b/assets/md-manuscript.svg new file mode 100644 index 0000000..4d2adcf --- /dev/null +++ b/assets/md-manuscript.svg @@ -0,0 +1,14 @@ + + md-manuscript + + + + + + + + + + + diff --git a/src/main.rs b/src/main.rs index d29426e..1dd984e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,12 +18,33 @@ mod spell; use eframe::egui; +/// The window icon, for the title bar, the task switcher and the dock. +/// +/// A desktop usually finds an app's icon by matching the window class against a +/// `.desktop` file, but that only works once one is installed; setting it here +/// means the window carries its own icon whatever it was launched from. +/// +/// The PNG is generated from `assets/md-manuscript.svg` — see the icon section +/// of INSTALL-debian12-surface.md for the command that regenerates the set. +fn window_icon() -> Option { + const PNG: &[u8] = include_bytes!("../assets/icons/hicolor/256x256/apps/md-manuscript.png"); + // A broken icon is not worth refusing to start over. + eframe::icon_data::from_png_bytes(PNG).ok() +} + fn main() -> eframe::Result<()> { + let mut viewport = egui::ViewportBuilder::default() + .with_inner_size([1100.0, 720.0]) + .with_min_inner_size([700.0, 400.0]) + .with_title("md-manuscript") + // Matched against the .desktop file's name by the desktop shell, which + // is what lets a launcher pin and group the window. + .with_app_id("md-manuscript"); + if let Some(icon) = window_icon() { + viewport = viewport.with_icon(icon); + } let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default() - .with_inner_size([1100.0, 720.0]) - .with_min_inner_size([700.0, 400.0]) - .with_title("md-manuscript"), + viewport, ..Default::default() }; @@ -33,3 +54,18 @@ fn main() -> eframe::Result<()> { Box::new(|cc| Ok(Box::new(app::App::new(cc)))), ) } + +#[cfg(test)] +mod tests { + /// The window icon is an asset loaded by path, so a rename or a bad export + /// would only show up as a missing icon at runtime. Decode it in a test. + #[test] + fn the_window_icon_decodes() { + let icon = super::window_icon().expect("the bundled icon must decode"); + assert_eq!((icon.width, icon.height), (256, 256)); + assert_eq!(icon.rgba.len(), 256 * 256 * 4); + // Not a blank square: the tile has both ink and paper in it. + let opaque = icon.rgba.chunks(4).filter(|p| p[3] > 0).count(); + assert!(opaque > 256 * 256 / 2, "icon looks empty"); + } +}