aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--internal/notify/config.go9
-rw-r--r--internal/notify/config_test.go12
-rw-r--r--internal/notify/service.go2
-rw-r--r--internal/notify/store.go2
5 files changed, 31 insertions, 3 deletions
diff --git a/README.md b/README.md
index 7a4c026..17e2e88 100644
--- a/README.md
+++ b/README.md
@@ -39,13 +39,20 @@ is optional and the defaults are the behaviour without the file:
{
"timeouts": { "low": 10, "normal": 10, "critical": 0 },
"liveCap": 20,
- "historyCap": 20
+ "historyCap": 20,
+ "soundTheme": "freedesktop"
}
Timeouts are seconds up to a day (86400), 0 is never, and apply only when a client sends
`expire_timeout -1`. Caps clamp to 1..100; a lowered cap applies at the next
notification. A broken file is logged and read as the defaults.
+`soundTheme` is the freedesktop sound theme an app's `sound-name` hint is
+looked up in (then its `Inherits`, then `freedesktop`). The daemon publishes
+each notification's resolved `sound` (a path, or `""`) and `silent` (the
+app's `suppress-sound`); a `sound-file` hint wins over `sound-name` when it
+is an absolute path to an existing file. Playing is the renderer's job.
+
The daemon reads it at start and on `notifyctl reload`. The quickshell
settings page writes it; the same file carries the balloon shell's keys
(`balloons`, `dndPassCritical`, `muted`), which the daemon ignores.
diff --git a/internal/notify/config.go b/internal/notify/config.go
index fcba5a8..98a6f51 100644
--- a/internal/notify/config.go
+++ b/internal/notify/config.go
@@ -29,16 +29,18 @@ type Timeouts struct {
// Config is the daemon's share of ~/.config/notifyd/config.json. The file
// also carries the balloon shell's keys (placement, DND, mute); they are not
-// declared here and the decoder ignores them.
+// declared here and the decoder ignores them. The daemon reads timeouts, caps
+// and the sound theme for sound-name hints.
type Config struct {
Timeouts Timeouts `json:"timeouts"`
LiveCap int `json:"liveCap"`
HistoryCap int `json:"historyCap"`
+ SoundTheme string `json:"soundTheme"`
}
// DefaultConfig is the behaviour before the file existed.
func DefaultConfig() Config {
- return Config{Timeouts: Timeouts{Low: 10, Normal: 10, Critical: 0}, LiveCap: 20, HistoryCap: 20}
+ return Config{Timeouts: Timeouts{Low: 10, Normal: 10, Critical: 0}, LiveCap: 20, HistoryCap: 20, SoundTheme: "freedesktop"}
}
// ConfigPath is $XDG_CONFIG_HOME/notifyd/config.json, ~/.config when unset.
@@ -74,6 +76,9 @@ func LoadConfig(path string) Config {
}
c.LiveCap = clamp(c.LiveCap, 1, 100)
c.HistoryCap = clamp(c.HistoryCap, 1, 100)
+ if c.SoundTheme == "" {
+ c.SoundTheme = def.SoundTheme
+ }
c.Timeouts.Low = seconds(c.Timeouts.Low, def.Timeouts.Low)
c.Timeouts.Normal = seconds(c.Timeouts.Normal, def.Timeouts.Normal)
c.Timeouts.Critical = seconds(c.Timeouts.Critical, def.Timeouts.Critical)
diff --git a/internal/notify/config_test.go b/internal/notify/config_test.go
index 818e88f..52781ac 100644
--- a/internal/notify/config_test.go
+++ b/internal/notify/config_test.go
@@ -63,3 +63,15 @@ func TestLoadConfigClamps(t *testing.T) {
t.Errorf("got %+v", got)
}
}
+
+func TestLoadConfigSoundTheme(t *testing.T) {
+ if got := LoadConfig(writeConfig(t, `{"soundTheme": "mine"}`)).SoundTheme; got != "mine" {
+ t.Errorf("soundTheme = %q, want mine", got)
+ }
+ if got := LoadConfig(writeConfig(t, `{"soundTheme": ""}`)).SoundTheme; got != "freedesktop" {
+ t.Errorf("empty soundTheme = %q, want freedesktop", got)
+ }
+ if got := LoadConfig(writeConfig(t, `{"soundTheme": 4, "liveCap": 3}`)); got.SoundTheme != "freedesktop" || got.LiveCap != 3 {
+ t.Errorf("got %+v", got)
+ }
+}
diff --git a/internal/notify/service.go b/internal/notify/service.go
index 1b1c89b..3ad3c75 100644
--- a/internal/notify/service.go
+++ b/internal/notify/service.go
@@ -116,6 +116,7 @@ func (s *Service) Notify(appName string, replacesID uint32, appIcon, summary, bo
now := time.Now().UnixMilli()
s.mu.Lock()
timeouts := s.cfg.Timeouts
+ theme := s.cfg.SoundTheme
s.mu.Unlock()
ms := EffectiveTimeoutMS(expireTimeout, u, timeouts)
// Spec priority is image-data > image-path > the deprecated icon_data. A
@@ -131,6 +132,7 @@ func (s *Service) Notify(appName string, replacesID uint32, appIcon, summary, bo
Actions: ParseActions(actions),
Created: now,
}
+ n.Sound, n.Silent = SoundFromHints(hints, theme)
var raw *RawImage
if r, ok := ImageDataFromHints(hints); ok {
raw = r
diff --git a/internal/notify/store.go b/internal/notify/store.go
index 3648983..51a6f39 100644
--- a/internal/notify/store.go
+++ b/internal/notify/store.go
@@ -23,6 +23,8 @@ type Popup struct {
Urgency Urgency `json:"urgency"`
Icon string `json:"icon"`
Image string `json:"image"`
+ Sound string `json:"sound"`
+ Silent bool `json:"silent"`
Actions [][2]string `json:"actions"`
Created int64 `json:"created"`
Expires int64 `json:"expires"`