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_test.go | 95 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 internal/notify/classify_test.go (limited to 'internal/notify/classify_test.go') diff --git a/internal/notify/classify_test.go b/internal/notify/classify_test.go new file mode 100644 index 0000000..6234008 --- /dev/null +++ b/internal/notify/classify_test.go @@ -0,0 +1,95 @@ +// 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/png" + "os" + "path/filepath" + "testing" + "time" + + "github.com/godbus/dbus/v5" +) + +func writeTestPNG(t *testing.T, path string, w, h int) { + t.Helper() + f, err := os.Create(path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + if err := png.Encode(f, image.NewRGBA(image.Rect(0, 0, w, h))); err != nil { + t.Fatal(err) + } +} + +func TestIsIconImage(t *testing.T) { + dir := t.TempDir() + small := filepath.Join(dir, "logo.png") + large := filepath.Join(dir, "shot.png") + svg := filepath.Join(dir, "icon.svg") + writeTestPNG(t, small, 32, 32) + writeTestPNG(t, large, 800, 600) + if err := os.WriteFile(svg, []byte(""), 0o644); err != nil { + t.Fatal(err) + } + cases := []struct { + name string + raw, resolved string + want bool + }{ + {"theme name", "utilities-terminal", "/x/apps/16/utilities-terminal.svg", true}, + {"svg", svg, svg, true}, + {"small raster", small, small, true}, + {"large raster", large, large, false}, + {"missing file", "/no/such.png", "/no/such.png", false}, + {"empty", "", "", false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if got := IsIconImage(c.raw, c.resolved); got != c.want { + t.Fatalf("IsIconImage(%q, %q) = %v, want %v", c.raw, c.resolved, got, c.want) + } + }) + } +} + +func TestNotifyRoutesContentImageByKind(t *testing.T) { + dir := t.TempDir() + small := filepath.Join(dir, "logo.png") + large := filepath.Join(dir, "shot.png") + writeTestPNG(t, small, 32, 32) + writeTestPNG(t, large, 800, 600) + + run := func(path string) Popup { + var live []Popup + s := &Service{dir: t.TempDir(), timers: map[uint32]*time.Timer{}} + s.store = NewStore(s.emitClosed, func(l, _ []Popup) { live = l }, s.removeImage) + hints := map[string]dbus.Variant{"image-path": dbus.MakeVariant(path)} + if _, err := s.Notify("app", 0, "", "s", "b", nil, hints, -1); err != nil { + t.Fatal(err) + } + if len(live) != 1 { + t.Fatalf("published %d popups", len(live)) + } + return live[0] + } + + if p := run(small); p.Icon != small || p.Image != "" { + t.Fatalf("small image: icon=%q image=%q, want icon only", p.Icon, p.Image) + } + if p := run(large); p.Icon != "" || p.Image != large { + t.Fatalf("large image: icon=%q image=%q, want image only", p.Icon, p.Image) + } +} -- cgit v1.2.3