diff --git a/modules/dunst/README.md b/modules/dunst/README.md index b30a1691..c7137ee3 100644 --- a/modules/dunst/README.md +++ b/modules/dunst/README.md @@ -39,6 +39,24 @@ the account is not logged in. | `dunst_rules` | the rules the running notifier holds, and the files they come from | | `dunst_count` | shown, waiting, in history, and whether paused | +## Settings + +Two preferences have defaults in the definition (novox/hq ADR 0262). The mesh's layer, then a machine's, +overrides them, and the file is written with the value, never with a variable dunst reads. + +| setting | default | what it is | +|---|---|---| +| `font-size` | 10 | the points of `font = Inter `; 10 reads well on a screen of about 100 DPI, which is the workstations | +| `width` | 250 | the notification's width in pixels; a larger font needs a wider notification | + +A machine with a denser screen sets both for itself, and the next push writes them: + + mesh-controller.settings {module: "dunst", node: "", values: '{"font-size": 16, "width": 400}'} + +`mesh-controller.settings {module: "dunst", node: ""}` shows each value and where it came from: +the default, the mesh, or the machine. dunst reads its file when it starts, and nothing in this module +tells a running dunst that the file changed: a new size shows at the next login, or after `dunstctl reload`. + ## What it chose, and what it improves The workstations' files differed: one had the notifications bottom-right, 15 % transparent and with diff --git a/modules/dunst/cmd/dunst-tools/manifest_helpers_test.go b/modules/dunst/cmd/dunst-tools/manifest_helpers_test.go index 84fe6b64..94cb1520 100644 --- a/modules/dunst/cmd/dunst-tools/manifest_helpers_test.go +++ b/modules/dunst/cmd/dunst-tools/manifest_helpers_test.go @@ -12,22 +12,30 @@ import ( // every desktop module that carries it. type manifest struct { - Module string `json:"module"` - Version string `json:"version"` - Capabilities []string `json:"capabilities"` - Requires []string `json:"requires"` - Claims []claim `json:"claims"` - Seats []any `json:"seats"` - Tools []string `json:"tools"` - Emits []string `json:"emits"` - Environment *environment `json:"environment"` - Shell []shellCode `json:"shell"` - Resources []map[string]any `json:"resources"` + Module string `json:"module"` + Version string `json:"version"` + Capabilities []string `json:"capabilities"` + Requires []string `json:"requires"` + Claims []claim `json:"claims"` + Seats []any `json:"seats"` + Tools []string `json:"tools"` + Settings map[string]setting `json:"settings"` + Emits []string `json:"emits"` + Environment *environment `json:"environment"` + Shell []shellCode `json:"shell"` + Resources []map[string]any `json:"resources"` Build struct { Artifacts []map[string]any `json:"artifacts"` } `json:"build"` } +// setting is one key's default, as the definition gives it (novox/hq ADR 0262). +type setting struct { + Kind string `json:"kind"` + Default any `json:"default"` + Why string `json:"why"` +} + type claim struct { Name string `json:"name"` Scope string `json:"scope"` diff --git a/modules/dunst/cmd/dunst-tools/manifest_test.go b/modules/dunst/cmd/dunst-tools/manifest_test.go index 6c7fb3e5..56313353 100644 --- a/modules/dunst/cmd/dunst-tools/manifest_test.go +++ b/modules/dunst/cmd/dunst-tools/manifest_test.go @@ -43,7 +43,8 @@ func TestItOwnsItsFileAndTheDropInDirectory(t *testing.T) { func TestTheFileIsTheDecidedOneAndCallsTheSeatsMenu(t *testing.T) { m := readManifest(t) c := m.resource(t, "configuration")["content"].(string) - for _, want := range []string{"origin = top-right", "transparency = 0", "corner_radius = 0", "font = Inter 10", "dmenu = dmenu -p dunst"} { + for _, want := range []string{"origin = top-right", "transparency = 0", "corner_radius = 0", "font = Inter ${setting:font-size}", + "width = ${setting:width}", "dmenu = dmenu -p dunst"} { if !strings.Contains(c, " "+want+"\n") { t.Errorf("lacks %q", want) } @@ -55,6 +56,34 @@ func TestTheFileIsTheDecidedOneAndCallsTheSeatsMenu(t *testing.T) { } } +// The font size and the width are preferences with today's values as their defaults (novox/hq ADR +// 0262): a machine with a denser screen sets its own, and every other machine's file is unchanged. +func TestTheFontSizeAndWidthAreSettingsWithTodaysDefaults(t *testing.T) { + m := readManifest(t) + want := map[string]float64{"font-size": 10, "width": 250} + if len(m.Settings) != len(want) { + t.Fatalf("settings: %+v", m.Settings) + } + for key, value := range want { + s, ok := m.Settings[key] + if !ok || s.Kind != "preference" || s.Default != value || strings.TrimSpace(s.Why) == "" { + t.Errorf("%s: %+v, want a preference defaulting to %v with why", key, s, value) + } + } + // Filled with the defaults, the file is the one every machine had before it took settings. + c := m.resource(t, "configuration")["content"].(string) + c = strings.ReplaceAll(c, "${setting:font-size}", "10") + c = strings.ReplaceAll(c, "${setting:width}", "250") + for _, want := range []string{"font = Inter 10", "width = 250"} { + if !strings.Contains(c, " "+want+"\n") { + t.Errorf("filled with the defaults, lacks %q", want) + } + } + if strings.Contains(c, "${setting:") { + t.Errorf("asks for a setting it gives no default: %s", c) + } +} + func TestNothingStartsItButTheBus(t *testing.T) { m := readManifest(t) if m.Shell != nil { diff --git a/modules/dunst/files/dunstrc b/modules/dunst/files/dunstrc index f5044858..d9bc6ad9 100644 --- a/modules/dunst/files/dunstrc +++ b/modules/dunst/files/dunstrc @@ -11,7 +11,7 @@ follow = none # Geometry - width = 250 + width = ${setting:width} height = (0, 300) origin = top-right offset = (10, 50) @@ -37,7 +37,7 @@ corner_radius = 0 # Text: the interface face (research 026/04) - font = Inter 10 + font = Inter ${setting:font-size} line_height = 0 markup = full format = "%s\n%b" diff --git a/modules/dunst/module.json b/modules/dunst/module.json index 3f34298b..df8130eb 100644 --- a/modules/dunst/module.json +++ b/modules/dunst/module.json @@ -21,6 +21,18 @@ "dunst_rules", "dunst_count" ], + "settings": { + "font-size": { + "kind": "preference", + "default": 10, + "why": "10 points of Inter reads well on a screen of about 100 DPI; a denser screen needs a larger size" + }, + "width": { + "kind": "preference", + "default": 250, + "why": "250 pixels fits a title of about forty characters at 10 points; a larger font needs a wider notification" + } + }, "resources": [ { "id": "package", @@ -52,7 +64,7 @@ "path": "${machine:account-home}/.config/dunst/dunstrc", "owner": "${machine:account}", "mode": "0644", - "content": "# dunst, the notifier (module dunst, novox/hq ADR 0208). Owned by the mesh: this file is replaced\n# at every push. Adopted from the laptop's file of 2026-10-04 (the two workstations differed in\n# position, transparency and corner radius; the laptop's square, opaque, top-right one matches the\n# rest of the desktop). Only what differs from dunst's defaults, and what the desktop relies on.\n#\n# Other modules' rules go in ~/.config/dunst/dunstrc.d/*.conf, which dunst reads after this file,\n# so a drop-in outranks it. dunst is started by D-Bus on the first notification: nothing starts it.\n\n[global]\n monitor = 0\n follow = none\n\n # Geometry\n width = 250\n height = (0, 300)\n origin = top-right\n offset = (10, 50)\n notification_limit = 20\n\n progress_bar = true\n progress_bar_height = 10\n progress_bar_frame_width = 1\n progress_bar_min_width = 150\n progress_bar_max_width = 300\n\n indicate_hidden = yes\n transparency = 0\n separator_height = 2\n padding = 8\n horizontal_padding = 8\n text_icon_padding = 0\n frame_width = 3\n frame_color = \"#de5200\"\n gap_size = 0\n separator_color = frame\n sort = yes\n corner_radius = 0\n\n # Text: the interface face (research 026/04)\n font = Inter 10\n line_height = 0\n markup = full\n format = \"%s\\n%b\"\n alignment = left\n vertical_alignment = center\n show_age_threshold = 60\n ellipsize = middle\n ignore_newline = no\n stack_duplicates = true\n hide_duplicate_count = false\n show_indicators = yes\n\n # Icons, from the desktop's icon theme\n enable_recursive_icon_lookup = true\n icon_theme = Adwaita\n icon_position = left\n min_icon_size = 32\n max_icon_size = 128\n\n # History\n sticky_history = yes\n history_length = 20\n\n # The context menu is the node's dmenu-compatible command, which the holder of node-launcher\n # answers (rofi on the workstations). Links open in the desktop's default browser.\n dmenu = dmenu -p dunst\n browser = /usr/bin/xdg-open\n always_run_script = true\n\n title = Dunst\n class = Dunst\n ignore_dbusclose = false\n\n mouse_left_click = close_current\n mouse_middle_click = do_action, close_current\n mouse_right_click = close_all\n\n[urgency_low]\n background = \"#000000\"\n foreground = \"#ffffff\"\n timeout = 10\n\n[urgency_normal]\n background = \"#000000\"\n foreground = \"#ffffff\"\n timeout = 10\n\n[urgency_critical]\n background = \"#000000\"\n foreground = \"#ffffff\"\n frame_color = \"#ff0000\"\n timeout = 0\n" + "content": "# dunst, the notifier (module dunst, novox/hq ADR 0208). Owned by the mesh: this file is replaced\n# at every push. Adopted from the laptop's file of 2026-10-04 (the two workstations differed in\n# position, transparency and corner radius; the laptop's square, opaque, top-right one matches the\n# rest of the desktop). Only what differs from dunst's defaults, and what the desktop relies on.\n#\n# Other modules' rules go in ~/.config/dunst/dunstrc.d/*.conf, which dunst reads after this file,\n# so a drop-in outranks it. dunst is started by D-Bus on the first notification: nothing starts it.\n\n[global]\n monitor = 0\n follow = none\n\n # Geometry\n width = ${setting:width}\n height = (0, 300)\n origin = top-right\n offset = (10, 50)\n notification_limit = 20\n\n progress_bar = true\n progress_bar_height = 10\n progress_bar_frame_width = 1\n progress_bar_min_width = 150\n progress_bar_max_width = 300\n\n indicate_hidden = yes\n transparency = 0\n separator_height = 2\n padding = 8\n horizontal_padding = 8\n text_icon_padding = 0\n frame_width = 3\n frame_color = \"#de5200\"\n gap_size = 0\n separator_color = frame\n sort = yes\n corner_radius = 0\n\n # Text: the interface face (research 026/04)\n font = Inter ${setting:font-size}\n line_height = 0\n markup = full\n format = \"%s\\n%b\"\n alignment = left\n vertical_alignment = center\n show_age_threshold = 60\n ellipsize = middle\n ignore_newline = no\n stack_duplicates = true\n hide_duplicate_count = false\n show_indicators = yes\n\n # Icons, from the desktop's icon theme\n enable_recursive_icon_lookup = true\n icon_theme = Adwaita\n icon_position = left\n min_icon_size = 32\n max_icon_size = 128\n\n # History\n sticky_history = yes\n history_length = 20\n\n # The context menu is the node's dmenu-compatible command, which the holder of node-launcher\n # answers (rofi on the workstations). Links open in the desktop's default browser.\n dmenu = dmenu -p dunst\n browser = /usr/bin/xdg-open\n always_run_script = true\n\n title = Dunst\n class = Dunst\n ignore_dbusclose = false\n\n mouse_left_click = close_current\n mouse_middle_click = do_action, close_current\n mouse_right_click = close_all\n\n[urgency_low]\n background = \"#000000\"\n foreground = \"#ffffff\"\n timeout = 10\n\n[urgency_normal]\n background = \"#000000\"\n foreground = \"#ffffff\"\n timeout = 10\n\n[urgency_critical]\n background = \"#000000\"\n foreground = \"#ffffff\"\n frame_color = \"#ff0000\"\n timeout = 0\n" } ], "build": {