// 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. .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); }