The review of 2026-10-09 (H4): a link's code shown on the desk stayed in dunst's history, which node-notifier.history serves to every caller of the mesh's verbs, agents among them; and a code sent to a paused notifier is held and shown later, to whoever is then at the screen. send now takes secret (transient, category mesh.secret) and transient; the dunstrc's rules keep both out of the history, and history leaves out any that got there. The pause, read from dunst itself, is said as the event paused at start, on change and every two minutes, for the desk to read.
111 lines
5.1 KiB
Go
111 lines
5.1 KiB
Go
// dunst's Go tools bundle (novox/hq ADR 0188, ADR 0193, ADR 0208): its implementation of
|
|
// node-notifier's verbs `send` and `history`, and its own tools, served by the node's runtime as the
|
|
// operator account. Everything here goes over the account's session bus; none of it needs the screen.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
func main() {
|
|
go sayStarted()
|
|
go watchPaused()
|
|
if err := stdio.Serve("", tools()); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func tools() []stdio.Tool {
|
|
return []stdio.Tool{
|
|
{
|
|
Name: "node-notifier.send",
|
|
Description: "Show a notification on the operator's desktop: a title, a body, an urgency (low, " +
|
|
"normal, critical), and optionally the sending application's name, an icon, how long it stays and " +
|
|
"the answers it offers (actions: token and label each). Answers the notification's id; the answer " +
|
|
"the operator chooses is said later as the event action-chosen {id, token, machine}. A secret " +
|
|
"or transient one is kept out of the history.",
|
|
Input: map[string]any{
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"summary": map[string]any{"type": "string", "description": "the title"},
|
|
"body": map[string]any{"type": "string", "description": "the text; simple markup (<b>, <i>, <u>, <a href>) is shown"},
|
|
"urgency": map[string]any{"type": "string", "enum": urgencies, "description": "default normal"},
|
|
"app_name": map[string]any{"type": "string", "description": "who it is from (default: mesh); a notifier rule can match it"},
|
|
"icon": map[string]any{"type": "string", "description": "an icon name from the icon theme, or a file"},
|
|
"expire_ms": map[string]any{"type": "integer", "description": "how long it stays; 0 until dismissed (default: the urgency's own)"},
|
|
"category": map[string]any{"type": "string", "description": "a notification category, e.g. email.arrived"},
|
|
"replace_id": map[string]any{"type": "integer", "description": "replace the notification with this id instead of adding one"},
|
|
"transient": map[string]any{"type": "boolean", "description": "keep it out of the history (default false)"},
|
|
"secret": map[string]any{"type": "boolean", "description": "it carries something shown once, such as a " +
|
|
"link's code: transient, in the category " + SecretCategory + ", and never in the history (default false)"},
|
|
"actions": map[string]any{"type": "array", "description": "at most 4 answers the notification offers, " +
|
|
"each {token, label}; the chosen token is said as the event action-chosen",
|
|
"items": map[string]any{"type": "object", "properties": map[string]any{
|
|
"token": map[string]any{"type": "string"}, "label": map[string]any{"type": "string"}}}},
|
|
},
|
|
"required": []string{"summary"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
n, err := notificationOf(args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Send(n)
|
|
},
|
|
},
|
|
{
|
|
Name: "node-notifier.history",
|
|
Description: "The notifications the operator was shown, newest first: id, application, title, " +
|
|
"body, urgency and how long ago. A secret one (a link's code) is never shown; withheld counts them.",
|
|
Input: map[string]any{
|
|
"limit": map[string]any{"type": "integer", "description": "at most this many (default 20, at most 200)"},
|
|
},
|
|
Run: func(args map[string]any) (any, error) {
|
|
limit, err := whole(args, "limit", 20, 1, 200)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return History(limit)
|
|
},
|
|
},
|
|
{
|
|
Name: "dunst_pause",
|
|
Description: "Do not disturb: hold every new notification back until resumed. They are shown then, not lost.",
|
|
Run: func(map[string]any) (any, error) { return SetPaused(true) },
|
|
},
|
|
{
|
|
Name: "dunst_resume",
|
|
Description: "End do-not-disturb: notifications held back are shown.",
|
|
Run: func(map[string]any) (any, error) { return SetPaused(false) },
|
|
},
|
|
{
|
|
Name: "dunst_close_all",
|
|
Description: "Close every notification on screen. They stay in the history.",
|
|
Run: func(map[string]any) (any, error) { return CloseAll() },
|
|
},
|
|
{
|
|
Name: "dunst_rules",
|
|
Description: "The notifier's rules in force — each rule's name, whether it is enabled, what it " +
|
|
"matches and what it sets — and the files they come from (the mesh's dunstrc, then dunstrc.d).",
|
|
Run: func(map[string]any) (any, error) { return Rules() },
|
|
},
|
|
{
|
|
Name: "dunst_reload",
|
|
Description: "Make the running notifier read its configuration again now (dunstrc, then dunstrc.d), " +
|
|
"keeping what is on screen and in the history. The mesh does this by itself when it rewrites " +
|
|
"dunstrc, for instance after a font-size or width setting changes. Answers the files read and the " +
|
|
"font, font size and width they set, with the file each came from.",
|
|
Run: func(map[string]any) (any, error) { return Reload() },
|
|
},
|
|
{
|
|
Name: "dunst_count",
|
|
Description: "How many notifications are shown, waiting and in the history, and whether do-not-disturb is on.",
|
|
Run: func(map[string]any) (any, error) { return Count() },
|
|
},
|
|
}
|
|
}
|