// 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 ( "log" "path/filepath" "strings" "github.com/godbus/dbus/v5" ) // XDGSoundDirs is the sound theme search path: the user's dir then each data // dir, the same roots as XDGIconDirs. func XDGSoundDirs() []string { dirs := XDGIconDirs() for i, d := range dirs { dirs[i] = filepath.Join(filepath.Dir(d), "sounds") } return dirs } var soundExts = []string{".oga", ".ogg", ".wav"} // LookupSound resolves a sound naming spec name: the theme, its Inherits // chain, then freedesktop, each searched across every root; within that, the // full name first and then with trailing -components dropped, as the spec's // fallback (message-new-email, message-new, message). "" when nothing matches. func LookupSound(name, theme string) string { var chain []string seen := map[string]bool{} for t := theme; t != "" && !seen[t]; { seen[t] = true chain = append(chain, t) next := "" for _, root := range XDGSoundDirs() { if v := inheritsOf(filepath.Join(root, t)); v != "" { next = v break } } t = next } if !seen["freedesktop"] { chain = append(chain, "freedesktop") } for n := name; n != ""; { for _, t := range chain { for _, root := range XDGSoundDirs() { for _, ext := range soundExts { p := filepath.Join(root, t, "stereo", n+ext) if fileExists(p) { return p } } } } i := strings.LastIndexByte(n, '-') if i < 0 { break } n = n[:i] } return "" } // SoundFromHints reads the three freedesktop sound hints. sound-file wins over // sound-name, but only as an absolute path to an existing file: anything else // is logged and treated as absent, so the name still gets its turn. silent is // suppress-sound, published as is; the renderer decides what it means. func SoundFromHints(hints map[string]dbus.Variant, theme string) (sound string, silent bool) { if v, ok := hints["suppress-sound"]; ok { silent, _ = v.Value().(bool) } if v, ok := hints["sound-file"]; ok { if p, _ := v.Value().(string); p != "" { if filepath.IsAbs(p) && fileExists(p) { return p, silent } log.Printf("notifyd: sound-file %q is not an absolute path to a file", p) } } if v, ok := hints["sound-name"]; ok { if n, _ := v.Value().(string); n != "" { return LookupSound(n, theme), silent } } return "", silent }