aboutsummaryrefslogtreecommitdiffstats
path: root/internal/notify/config.go
blob: 2b6ba5ec56b765fa4a0138e36150da9989b0b5bd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 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 (
	"encoding/json"
	"errors"
	"log"
	"os"
	"path/filepath"
)

// Timeouts are the balloon lifetimes in seconds per urgency, used only when a
// client leaves the choice to the server. 0 means never expires.
type Timeouts struct {
	Low      int `json:"low"`
	Normal   int `json:"normal"`
	Critical int `json:"critical"`
}

// 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.
type Config struct {
	Timeouts   Timeouts `json:"timeouts"`
	LiveCap    int      `json:"liveCap"`
	HistoryCap int      `json:"historyCap"`
}

// 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}
}

// ConfigPath is $XDG_CONFIG_HOME/notifyd/config.json, ~/.config when unset.
func ConfigPath() string {
	dir, err := os.UserConfigDir()
	if err != nil {
		dir = filepath.Join(homeDir(), ".config")
	}
	return filepath.Join(dir, "notifyd", "config.json")
}

// LoadConfig never fails: a hand typo must not stop the daemon. A missing
// file or broken JSON is the defaults; a key of the wrong type keeps its
// default while the rest of the file applies, which is what encoding/json
// does on an UnmarshalTypeError. Out-of-range values clamp.
func LoadConfig(path string) Config {
	def := DefaultConfig()
	data, err := os.ReadFile(path)
	if err != nil {
		if !errors.Is(err, os.ErrNotExist) {
			log.Printf("notifyd: config: %v", err)
		}
		return def
	}
	c := def
	if err := json.Unmarshal(data, &c); err != nil {
		var typeErr *json.UnmarshalTypeError
		if !errors.As(err, &typeErr) {
			log.Printf("notifyd: config %s: %v, using defaults", path, err)
			return def
		}
		log.Printf("notifyd: config %s: %v", path, err)
	}
	c.LiveCap = clamp(c.LiveCap, 1, 100)
	c.HistoryCap = clamp(c.HistoryCap, 1, 100)
	if c.Timeouts.Low < 0 {
		c.Timeouts.Low = def.Timeouts.Low
	}
	if c.Timeouts.Normal < 0 {
		c.Timeouts.Normal = def.Timeouts.Normal
	}
	if c.Timeouts.Critical < 0 {
		c.Timeouts.Critical = def.Timeouts.Critical
	}
	return c
}

func clamp(v, lo, hi int) int {
	return min(max(v, lo), hi)
}