From 4c34836037848ca65589734a6da5ab40fdbf94fe Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 8 Oct 2026 16:23:03 +0200 Subject: [PATCH] dunst: make the font size and width settings, defaulting to today's values (hq ADR 0262) The laptop's 242 DPI panel shows Inter 10 at less than half the workstation's size. The defaults keep every machine's file byte for byte as it is; a machine sets its own. --- modules/dunst/README.md | 18 +++++++++++ .../cmd/dunst-tools/manifest_helpers_test.go | 30 +++++++++++------- .../dunst/cmd/dunst-tools/manifest_test.go | 31 ++++++++++++++++++- modules/dunst/files/dunstrc | 4 +-- modules/dunst/module.json | 14 ++++++++- 5 files changed, 82 insertions(+), 15 deletions(-) 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..373ab64e 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": "Inter at 10 points reads well on a screen of about 100 DPI at 96 DPI for X, which is the workstations; a denser screen sets more for its machine" + }, + "width": { + "kind": "preference", + "default": 250, + "why": "250 pixels holds about forty characters of a title at the default size; 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": {