aboutsummaryrefslogtreecommitdiffstats
path: root/shared/NotifyConfig.js
blob: ff522c493664a8b5acd763b70d1611d30812daa5 (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
// 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.

.pragma library

// ~/.config/notifyd/config.json, resolved over its defaults. notifyd reads
// the same file for timeouts and caps (its config.go clamps the same way);
// the balloon keys are this side's alone. Tested by test_notify_config.js.

const corners = ["top-left", "top-right", "bottom-left", "bottom-right"];

function defaults() {
    return {
        timeouts: { low: 10, normal: 10, critical: 0 },
        liveCap: 20,
        historyCap: 20,
        balloons: { screen: "DP-1", corner: "bottom-right" },
        dndPassCritical: true,
        muted: [],
    };
}

// Out of range (negative, or above a day) is the default, as in the daemon.
function seconds(v, d) { return Number.isInteger(v) && v >= 0 && v <= 86400 ? v : d; }
function cap(v, d) { return Number.isInteger(v) ? Math.min(Math.max(v, 1), 100) : d; }

// Never throws: anything that is not the right shape reads as its default.
function resolve(raw) {
    const d = defaults();
    if (!raw || typeof raw !== "object") return d;
    const t = raw.timeouts || {};
    const b = raw.balloons || {};
    return {
        timeouts: {
            low: seconds(t.low, d.timeouts.low),
            normal: seconds(t.normal, d.timeouts.normal),
            critical: seconds(t.critical, d.timeouts.critical),
        },
        liveCap: cap(raw.liveCap, d.liveCap),
        historyCap: cap(raw.historyCap, d.historyCap),
        balloons: {
            screen: typeof b.screen === "string" && b.screen !== "" ? b.screen : d.balloons.screen,
            corner: corners.indexOf(b.corner) >= 0 ? b.corner : d.balloons.corner,
        },
        dndPassCritical: typeof raw.dndPassCritical === "boolean" ? raw.dndPassCritical : d.dndPassCritical,
        muted: Array.isArray(raw.muted) ? raw.muted.filter(a => typeof a === "string") : d.muted,
    };
}

// Whether a balloon is held back by mute or DND. Drawer and snooze are the
// balloon's own checks; this is only what the config decides.
function withheld(cfg, n, dnd) {
    if (cfg.muted.indexOf(n.app) >= 0) return true;
    return dnd && !(n.urgency === "critical" && cfg.dndPassCritical);
}