aboutsummaryrefslogtreecommitdiffstats
path: root/internal/notify/classify.go
diff options
context:
space:
mode:
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
+}