From d350b314add837a655370cacce98d3748e83b3a0 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 31 Aug 2026 21:14:36 +0200 Subject: [PATCH] Give the app an icon and a place in the application menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit icon.svg is the source; make_icons.py rasterises it into assets/icon-.png at the six sizes a window manager asks for. Tk 8.6 reads PNG but not SVG, so the icon has to ship pre-rendered, and the glyph is laid on a rounded plate in the app's own colours — light on dark stays legible against both a light and a dark dock, which slate grey on nothing does not. ui.py loads whichever of those files exist and passes them all to iconphoto(), letting the WM pick per context (title bar, alt-tab, dock). The images are kept on the instance because Tk holds only a weak reference to a PhotoImage and the icon blanks out when it is collected. A missing or unreadable file is ignored rather than allowed to fail a launch. It also passes className="lektor-ai", which lands in WM_CLASS. Without it Tk calls itself "Tk" and that is what the dock shows on hover; the readable name comes from the desktop entry, which matches the window through StartupWMClass. install_desktop.sh writes ~/.local/share/applications/lektor-ai.desktop and copies the icons into the hicolor theme, so the app appears in the menu and can be pinned. Everything it installs lives under ~/.local and comes back out with --uninstall. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NkWDx9dVB7xBpqntvjvm6A --- assets/icon-128.png | Bin 0 -> 7354 bytes assets/icon-16.png | Bin 0 -> 676 bytes assets/icon-256.png | Bin 0 -> 15175 bytes assets/icon-32.png | Bin 0 -> 1559 bytes assets/icon-48.png | Bin 0 -> 2446 bytes assets/icon-64.png | Bin 0 -> 3500 bytes icon.svg | 1 + install_desktop.sh | 55 +++++++++++++++++++++++++ make_icons.py | 96 ++++++++++++++++++++++++++++++++++++++++++++ ui.py | 33 ++++++++++++++- 10 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 assets/icon-128.png create mode 100644 assets/icon-16.png create mode 100644 assets/icon-256.png create mode 100644 assets/icon-32.png create mode 100644 assets/icon-48.png create mode 100644 assets/icon-64.png create mode 100644 icon.svg create mode 100755 install_desktop.sh create mode 100755 make_icons.py diff --git a/assets/icon-128.png b/assets/icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..57d93c7c09b33d1bd9f055c7b85269cb5a4e5c7d GIT binary patch literal 7354 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 M07*qoM6N<$g0#1&2><{9 literal 0 HcmV?d00001 diff --git a/assets/icon-64.png b/assets/icon-64.png new file mode 100644 index 0000000000000000000000000000000000000000..f1cd8291ea1987859798d1d2271ba314c9d4a6cd GIT binary patch literal 3500 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 CV%Y=$ literal 0 HcmV?d00001 diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..ff34bdc --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/install_desktop.sh b/install_desktop.sh new file mode 100755 index 0000000..29c7965 --- /dev/null +++ b/install_desktop.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# +# install_desktop.sh — put "Lektor AI" in the application menu and the dock. +# +# Two things live outside this folder afterwards, both under ~/.local and both +# removable with --uninstall: +# +# ~/.local/share/applications/lektor-ai.desktop +# ~/.local/share/icons/hicolor//apps/lektor-ai.png +# +# The desktop entry matches the running window through StartupWMClass, which is +# the WM_CLASS that ui.py sets. Without the match the dock falls back to the raw +# WM_CLASS for its hover label. +# +set -euo pipefail + +HERE="$(dirname "$(readlink -f "$0")")" +APPS="$HOME/.local/share/applications" +ICONS="$HOME/.local/share/icons/hicolor" +ENTRY="$APPS/lektor-ai.desktop" +SIZES=(16 32 48 64 128 256) + +if [[ "${1:-}" == "--uninstall" ]]; then + rm -f "$ENTRY" + for s in "${SIZES[@]}"; do + rm -f "$ICONS/${s}x${s}/apps/lektor-ai.png" + done + echo "Removed the desktop entry and its icons." +else + for s in "${SIZES[@]}"; do + src="$HERE/assets/icon-$s.png" + [[ -f "$src" ]] || continue + mkdir -p "$ICONS/${s}x${s}/apps" + cp "$src" "$ICONS/${s}x${s}/apps/lektor-ai.png" + done + + mkdir -p "$APPS" + cat > "$ENTRY" </dev/null || true +gtk-update-icon-cache -f -t "$ICONS" 2>/dev/null || true diff --git a/make_icons.py b/make_icons.py new file mode 100755 index 0000000..45502b5 --- /dev/null +++ b/make_icons.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Rasterise icon.svg into the PNG sizes ui.py loads for the window icon. + +Tk 8.6 reads PNG but not SVG, so the icon ships pre-rendered in assets/. The +glyph is laid on a rounded plate in the app's own colours: light on dark keeps +it legible against both a light and a dark dock, which the bare SVG (slate grey +on nothing) is not. + +Needs inkscape for the render and Pillow for the downscales. Run it after +editing icon.svg, then ./install_desktop.sh to refresh the dock's copies. +""" +import re +import shutil +import subprocess +import sys +from pathlib import Path + +HERE = Path(__file__).resolve().parent +SRC = HERE / "icon.svg" +OUT = HERE / "assets" +SIZES = (256, 128, 64, 48, 32, 16) + +PLATE = 64 # plate side, in the units the composite is drawn in +GLYPH = 44 # how much of that side the glyph's longest edge covers +RADIUS = 14 # corner rounding of the plate +BG = "#2b2b2b" # ui.BAR +FG = "#ededed" # ui.FG + + +def glyph_box(svg): + """Bounding box of the drawing, in viewBox units. + + inkscape answers in the file's own pixel units, which are viewBox units + only when width and viewBox agree — so convert. + """ + out = subprocess.run(["inkscape", "--query-all", str(svg)], + capture_output=True, text=True, check=True).stdout + line = next(l for l in out.splitlines() if l.startswith("svg")) + x, y, w, h = (float(v) for v in line.split(",")[1:5]) + + text = svg.read_text() + width = float(re.search(r'\swidth="([\d.]+)"', text).group(1)) + vb_w = float(re.search(r'viewBox="[\d.\-]+ [\d.\-]+ ([\d.]+)', text).group(1)) + u = vb_w / width + return x * u, y * u, w * u, h * u + + +def main(): + if not SRC.is_file(): + sys.exit(f"{SRC.name} not found") + if not shutil.which("inkscape"): + sys.exit("inkscape not found — needed to rasterise the SVG") + try: + from PIL import Image + except ImportError: + sys.exit("Pillow not found — needed to downscale the render") + + d = re.search(r'\sd="([^"]+)"', SRC.read_text()).group(1) + x, y, w, h = glyph_box(SRC) + + # Scale the glyph to GLYPH along its longest edge, then centre what that + # leaves. The offsets cancel the bbox origin, which is not at 0,0. + s = GLYPH / max(w, h) + tx = (PLATE - w * s) / 2 - x * s + ty = (PLATE - h * s) / 2 - y * s + + OUT.mkdir(exist_ok=True) + composite = OUT / "_composite.svg" + composite.write_text( + f'\n' + f' \n' + f' \n' + f' \n' + f' \n\n') + + master = OUT / "_master.png" + subprocess.run(["inkscape", "--export-type=png", + f"--export-filename={master}", "-w", "512", "-h", "512", + str(composite)], check=True, capture_output=True) + + img = Image.open(master).convert("RGBA") + for size in SIZES: + img.resize((size, size), Image.LANCZOS).save(OUT / f"icon-{size}.png") + + composite.unlink() + master.unlink() + print(f"glyph bbox {w:.2f}x{h:.2f} at ({x:.2f}, {y:.2f}) " + f"-> scale {s:.3f}, offset ({tx:.2f}, {ty:.2f})") + print("wrote " + ", ".join(f"icon-{s}.png" for s in SIZES) + f" in {OUT}") + + +if __name__ == "__main__": + main() diff --git a/ui.py b/ui.py index 6be8896..910eb2a 100644 --- a/ui.py +++ b/ui.py @@ -45,6 +45,11 @@ import lektor_ai_comfy as core # noqa: E402 (path set above) CLI = HERE / "lektor_ai_comfy.py" +# Window icon. Tk 8.6 reads PNG but not SVG, so assets/ holds sizes rendered +# from icon.svg; the WM picks whichever fits its title bar, alt-tab and dock. +ICON_DIR = HERE / "assets" +ICON_SIZES = (256, 128, 64, 48, 32, 16) + # ── Palette, sampled from the mock-up ─────────────────────────────────────── BG = "#3c3c3c" # body BAR = "#2b2b2b" # top strip @@ -1061,7 +1066,11 @@ class SimplePrompt(tk.Toplevel): class LektorUI(tk.Tk): def __init__(self): - super().__init__() + # className lands in WM_CLASS; without it Tk reports itself as "Tk" and + # that is what the dock shows on hover. Tk lowercases everything past + # the first letter, so this is kept as a plain identifier — the pretty + # name comes from lektor-ai.desktop, which matches on StartupWMClass. + super().__init__(className="lektor-ai") self.title("lektor_ai — voiceover queue") self.geometry("1180x900") self.minsize(1000, 600) @@ -1080,6 +1089,7 @@ class LektorUI(tk.Tk): self.out_q = queue.Queue() self._in_redraw = False + self._set_icon() self._style() self._build() self.after(80, self._drain_output) @@ -1093,6 +1103,27 @@ class LektorUI(tk.Tk): self.log(" 'sudo apt install python3-tk', then run with plain python3.") self.log("Add videos with 'Add videos...' or the + button, then press GENERATE.") + # ── window icon ───────────────────────────────────────────────────────── + def _set_icon(self): + """Icon for the title bar, alt-tab and the dock. + + Kept on self because Tk holds only a weak reference to PhotoImages — + drop it and the icon blanks out. Missing or unreadable files are not + worth failing a launch over, so any trouble here is simply ignored. + """ + self._icons = [] + for size in ICON_SIZES: + path = ICON_DIR / f"icon-{size}.png" + if not path.is_file(): + continue + try: + self._icons.append(tk.PhotoImage(file=str(path))) + except tk.TclError: + pass + if self._icons: + # True: also the default for dialogs opened later. + self.iconphoto(True, *self._icons) + # ── theming ───────────────────────────────────────────────────────────── def _style(self): st = ttk.Style(self)