aboutsummaryrefslogtreecommitdiffstats
path: root/internal/notify/classify.go
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-15 18:31:30 +0200
committerDanilo M. <danix@danix.xyz>2026-09-15 18:31:30 +0200
commit8087086bc0a996e7dc020ba8bcf1332dd843f034 (patch)
treef0fade4044cd29681670e54f7b9e80af8fce2f23 /internal/notify/classify.go
parentd5812ffa717cad99e12752b719a024f06b72b6af (diff)
downloadnotifyd-8087086bc0a996e7dc020ba8bcf1332dd843f034.tar.gz
notifyd-8087086bc0a996e7dc020ba8bcf1332dd843f034.zip
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.
Diffstat (limited to 'internal/notify/classify.go')
-rw-r--r--internal/notify/classify.go58
1 files changed, 58 insertions, 0 deletions
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. <danix@danix.xyz>
+//
+// 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
+}