// 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 at or below which a content image is // treated as an icon rather than a preview. A themed icon or an application // logo is 256 or less; a screenshot is far larger. const IconMaxPixels = 256 // 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 }