From 8087086bc0a996e7dc020ba8bcf1332dd843f034 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Tue, 15 Sep 2026 18:31:30 +0200 Subject: fix(notify): route icon-like content images to the app-icon slot Clients like kitty, mail and opencode send their icon through the content-image hint, so it drew as a large balloon preview. A theme-name source, an SVG, or a raster small on both sides now fills the app-icon slot instead; a larger raster (a screenshot) stays the preview. An app_icon already present keeps the slot. --- internal/notify/classify.go | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 internal/notify/classify.go (limited to 'internal/notify/classify.go') diff --git a/internal/notify/classify.go b/internal/notify/classify.go new file mode 100644 index 0000000..c7e0dc8 --- /dev/null +++ b/internal/notify/classify.go @@ -0,0 +1,58 @@ +// Copyright (C) 2026 Danilo M. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +package notify + +import ( + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "os" + "path/filepath" + "strings" +) + +// IconMaxPixels is the long-side bound below which a content image is treated +// as an icon rather than a preview. A screenshot is far larger; a themed icon +// or an application logo is not. +const IconMaxPixels = 128 + +// IsIconImage reports whether a content image should fill the app-icon slot +// instead of the large preview. Clients disagree about where the icon goes: +// kitty, mail and opencode send it through the content-image hint, while a +// screenshot uses the same hint. The hint is one of the icon forms when the +// client sent a theme name, when the resolved file is an SVG, or when a raster +// is small on both sides. raw is the value before ResolveIcon; resolved is the +// path it resolved to. +func IsIconImage(raw, resolved string) bool { + if raw != "" && !strings.Contains(raw, "/") && !strings.HasPrefix(raw, "file:") { + return true + } + if resolved == "" { + return false + } + if strings.EqualFold(filepath.Ext(resolved), ".svg") { + return true + } + f, err := os.Open(resolved) + if err != nil { + return false + } + defer f.Close() + cfg, _, err := image.DecodeConfig(f) + if err != nil { + // A format without a registered decoder is not something we can size, + // so it stays a preview rather than being silently dropped. + return false + } + return cfg.Width <= IconMaxPixels && cfg.Height <= IconMaxPixels +} -- cgit v1.2.3