diff options
| -rw-r--r-- | README.md | 29 | ||||
| -rw-r--r-- | SPEC.md | 17 | ||||
| -rwxr-xr-x | bin/wb-icon | 23 | ||||
| -rwxr-xr-x | bin/wb-idle | 45 | ||||
| -rw-r--r-- | config.jsonc | 111 | ||||
| -rwxr-xr-x | install.sh | 5 | ||||
| -rw-r--r-- | modules/custom/presentation.jsonc | 28 | ||||
| -rw-r--r-- | modules/image/idle_inhibitor.jsonc | 15 | ||||
| -rw-r--r-- | styles/modules.css | 24 | ||||
| -rw-r--r-- | styles/pills.css | 26 |
10 files changed, 178 insertions, 145 deletions
@@ -1,15 +1,22 @@ # waybar-theme-udt -A waybar bar for DP-3: pill clusters, icons from the desktop's own icon theme -instead of font glyphs, and colours driven by +Two waybar bars for DP-3: pill clusters, icons from the desktop's own icon +theme instead of font glyphs, and colours driven by [unified-desktop-theme](../unified-desktop-theme). - [◆] [📅 Wed 15/09] [🕑 14:32] [1..8] [●●] [ open apps ] [🔊] [tray] [⏻] [🇮🇹] + top [📅 Wed 15/09] [🕑 14:32] [●●] [S] [●] [🔊] [tray] [⏻] [🇮🇹] + bottom [1..8] [ open windows ] Each bracket is a pill. Pills group into clusters with gaps between them, so -the bar reads as a few objects rather than one strip. Hover cross-fades +the bars read as a few objects rather than one strip. Hover cross-fades background and text over 0.3s; taskbar buttons underline in the accent. +DP-3 is rotated, so a bar there is 1080px wide rather than 1920, and one bar +holding everything was too crowded. Status sits on top, the desktop itself +(workspaces and open windows) along the bottom. Both bars are one waybar +process reading one stylesheet: waybar takes a top-level array of bars, so +they cannot drift apart. + ## Install ./install.sh @@ -65,11 +72,13 @@ their national colours. ## Layout -| Cluster | Modules | -| --- | --- | -| left | launcher, date, time, workspaces, VM dots | -| center | open applications | -| right | privacy dots, volume drawer, tray, presentation mode, language | +| Bar | Cluster | Modules | +| --- | --- | --- | +| top | left | date, time, VM dots | +| top | centre | launcher | +| top | right | privacy dots, volume drawer, tray, presentation mode, language | +| bottom | left | workspaces | +| bottom | right | open windows | No battery, temperature, CPU, memory or backlight: this is a desktop. @@ -105,7 +114,7 @@ Macchiato values when it is missing, so they still run standalone. modules/ one file per module, by source styles/ style.css imports the rest in order icons/ the two icons the icon theme lacks - bin/ wb-icon, wb-idle, and the two dot scripts + bin/ wb-icon, wb-lang, and the two dot scripts Wildcard `include` has been broken since waybar v0.14.0, so `config.jsonc` lists every module file explicitly. @@ -23,9 +23,16 @@ pointed at DP-3. ## Layout - ┌ left ──────────────────────────────┐ ┌ center ┐ ┌ right ─────────────────────┐ - [◆] [📅 Wed 15/09] [🕑 14:32] [1..8] [●●] [ open apps ] [🔊 62%] [tray] [⏻] [🇮🇹] - launcher date time workspaces vm taskbar volume tray present lang + top [📅 Wed 15/09] [🕑 14:32] [●●] [S] [●] [🔊] [tray] [⏻] [🇮🇹] + date time vm launcher priv vol tray present lang + + bottom [1..8] [ open windows ] + workspaces taskbar + +DP-3 is rotated, so a bar there is 1080px wide rather than 1920. One bar +holding every module was too crowded at that width, so status sits on top and +the desktop itself along the bottom. Both are one waybar process reading one +stylesheet: waybar takes a top-level array of bars. Each bracket is one pill. Neighbouring pills in a cluster share a background and weld together with asymmetric corner radii; clusters are separated by @@ -43,7 +50,7 @@ transparent gaps, so the bar reads as groups rather than a continuous strip. | center | `wlr/taskbar` | `icon-theme` config key | Per-window application icons. | | right | `group/wireplumber` | `image` module | Drawer group kept as it is today. | | right | `tray` | application-provided | | -| right | `idle_inhibitor` | `image` module | Presentation mode. | +| right | `custom/presentation` | CSS, keyed on the module's class | Presentation mode, the same `statusctl`-backed module the DP-1 bar runs. It already emits an empty text and a state class, so the icon is chosen in CSS and the glyphs are simply dropped. | | right | `image#lang` | `flag-it.svg` / `flag-gb.svg`, shipped here | An `image` module: `hyprland/language` styles as `#language` only, with no per-language class, so a flag cannot be selected in CSS. | Dropped from the current bar: `custom/breaktimer`, `custom/mail`, @@ -128,7 +135,7 @@ Modular, one file per module, mirroring the current setup: tray.jsonc wireplumber.jsonc image/ - idle_inhibitor.jsonc presentation mode, via `image` + language.jsonc the keyboard flag, via `image` styles/ style.css entry point, imports the rest in order theme.css GENERATED by udt-palette, gitignored diff --git a/bin/wb-icon b/bin/wb-icon index 9c29c60..b8f91cc 100755 --- a/bin/wb-icon +++ b/bin/wb-icon @@ -10,7 +10,6 @@ # # wb-icon volume # reads wpctl, prints the matching icon # wb-icon mic -# wb-icon idle <activated|deactivated> # wb-icon --selftest # every name this bar asks for must resolve # # Icons keep their own colours: this desktop's icon theme draws them with @@ -44,10 +43,6 @@ ICONS = { "muted": "microphone-sensitivity-muted", "on": "audio-input-microphone", }, - "idle": { - "activated": "x-office-presentation", - "deactivated": "preferences-desktop-screensaver", - }, } @@ -116,9 +111,18 @@ def selftest(): ] clock_icons = ["x-office-calendar", "clock"] + # Presentation mode is a custom module: statusctl emits the state as a + # class and modules.css picks the icon, so these names appear nowhere in + # this file. They are still the bar's icons, so they are checked here. + presentation_icons = [ + "preferences-desktop-screensaver", # off + "x-office-presentation", # on + "dialog-warning", # no state file + ] + names = sorted( {n for group in ICONS.values() for n in group.values()} - | set(workspace_icons) | set(clock_icons) + | set(workspace_icons) | set(clock_icons) | set(presentation_icons) ) missing = [n for n in names if not lookup(n)] for n in names: @@ -156,13 +160,6 @@ def main(argv): label = "muted" if state == "muted" else f"{pct}%" return emit(ICONS["mic"][state], f"Microphone: {label}") - if what == "idle": - state = argv[2] if len(argv) > 2 else "deactivated" - state = state if state in ICONS["idle"] else "deactivated" - on = state == "activated" - return emit(ICONS["idle"][state], - f"Presentation mode: {'on' if on else 'off'}") - print(f"wb-icon: unknown subject {what!r}", file=sys.stderr) return 2 diff --git a/bin/wb-idle b/bin/wb-idle deleted file mode 100755 index 05c4a8c..0000000 --- a/bin/wb-idle +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Presentation mode: keep the screen awake, and say so with a themed icon. -# Copyright (C) 2026 Danilo M. <danix@danix.xyz> -# Licensed under the GNU General Public License v2 only. -# -# waybar's own idle_inhibitor module draws its state with format-icons, which -# is text. This drives the same idea through hypridle and reports the state as -# an icon path for an `image` module instead. -# -# wb-idle status # print "$path\n$tooltip" for the image module -# wb-idle toggle # flip the inhibitor, then refresh the module -# -# The inhibitor is hypridle's own pause, so nothing else has to be running. -set -euo pipefail - -state_file="${XDG_RUNTIME_DIR:-/tmp}/wb-idle.state" - -active() { [[ -f "$state_file" ]]; } - -case "${1:-status}" in -toggle) - if active; then - rm -f "$state_file" - # Resume idling. Guarded: hypridle may not be running, and that is - # not an error worth failing a click over. - pkill -USR2 -x hypridle 2>/dev/null || true - else - touch "$state_file" - pkill -USR1 -x hypridle 2>/dev/null || true - fi - # Refresh the module now rather than waiting for its interval. - pkill -RTMIN+9 -x waybar 2>/dev/null || true - ;; -status) - if active; then - wb-icon idle activated - else - wb-icon idle deactivated - fi - ;; -*) - echo "usage: wb-idle <status|toggle>" >&2 - exit 2 - ;; -esac diff --git a/config.jsonc b/config.jsonc index 0c9dbc6..a7c5ff9 100644 --- a/config.jsonc +++ b/config.jsonc @@ -1,49 +1,76 @@ -// waybar-theme-udt: the bar on DP-3. +// waybar-theme-udt: two bars on DP-3. // Copyright (C) 2026 Danilo M. <danix@danix.xyz> // Licensed under the GNU General Public License v2 only. // +// A top-level array is how waybar takes more than one bar. Both bars are one +// process reading one stylesheet, so the pills stay consistent between them. +// +// DP-3 is rotated, so the bar is 1080px rather than 1920: one bar holding +// everything was too crowded at that width. Status lives on top, the desktop +// (workspaces and open windows) along the bottom. +// // One file per module, included explicitly: wildcard `include` has been broken // since waybar v0.14.0, so every module file has to be listed by name. -{ - "include": [ - "$HOME/.config/waybar-udt/modules/image/launcher.jsonc", - "$HOME/.config/waybar-udt/modules/clock.jsonc", - "$HOME/.config/waybar-udt/modules/hyprland/workspaces.jsonc", - "$HOME/.config/waybar-udt/modules/custom/vmdot.jsonc", - "$HOME/.config/waybar-udt/modules/extras/taskbar.jsonc", - "$HOME/.config/waybar-udt/modules/custom/privacy_dots.jsonc", - "$HOME/.config/waybar-udt/modules/image/volume.jsonc", - "$HOME/.config/waybar-udt/modules/extras/tray.jsonc", - "$HOME/.config/waybar-udt/modules/image/idle_inhibitor.jsonc", - "$HOME/.config/waybar-udt/modules/image/language.jsonc", - ], +[ + { + "include": [ + "$HOME/.config/waybar-udt/modules/clock.jsonc", + "$HOME/.config/waybar-udt/modules/custom/vmdot.jsonc", + "$HOME/.config/waybar-udt/modules/image/launcher.jsonc", + "$HOME/.config/waybar-udt/modules/custom/privacy_dots.jsonc", + "$HOME/.config/waybar-udt/modules/image/volume.jsonc", + "$HOME/.config/waybar-udt/modules/extras/tray.jsonc", + "$HOME/.config/waybar-udt/modules/custom/presentation.jsonc", + "$HOME/.config/waybar-udt/modules/image/language.jsonc", + ], + + "modules-left": [ + "clock#date", + "clock#time", + "custom/vmdot" + ], + "modules-center": [ + "image#launcher" + ], + "modules-right": [ + "custom/privacy_dots", + "group/volume", + "tray", + "custom/presentation", + "image#lang" + ], + + "layer": "top", + "output": "DP-3", + "position": "top", + "height": 0, + "width": 0, + "margin": 0, + "spacing": 0, + "mode": "dock", + "reload_style_on_change": true + }, + { + "include": [ + "$HOME/.config/waybar-udt/modules/hyprland/workspaces.jsonc", + "$HOME/.config/waybar-udt/modules/extras/taskbar.jsonc", + ], - // Clusters, not a continuous strip: the stylesheet welds neighbours - // together and leaves gaps between groups. - "modules-left": [ - "image#launcher", - "clock#date", - "clock#time", - "hyprland/workspaces", - "custom/vmdot" - ], - "modules-center": [ - "wlr/taskbar" - ], - "modules-right": [ - "custom/privacy_dots", - "group/volume", - "tray", - "image#idle", - "image#lang" - ], + "modules-left": [ + "hyprland/workspaces" + ], + "modules-right": [ + "wlr/taskbar" + ], - "layer": "top", - "output": "DP-3", - "height": 0, - "width": 0, - "margin": 0, - "spacing": 0, - "mode": "dock", - "reload_style_on_change": true -} + "layer": "top", + "output": "DP-3", + "position": "bottom", + "height": 0, + "width": 0, + "margin": 0, + "spacing": 0, + "mode": "dock", + "reload_style_on_change": true + } +] @@ -86,9 +86,9 @@ ln -sfn "$repo/config.jsonc" "$target/config.jsonc" for f in modules/clock.jsonc \ modules/hyprland/workspaces.jsonc \ modules/custom/vmdot.jsonc modules/custom/privacy_dots.jsonc \ + modules/custom/presentation.jsonc \ modules/extras/taskbar.jsonc modules/extras/tray.jsonc \ - modules/image/volume.jsonc modules/image/idle_inhibitor.jsonc \ - modules/image/language.jsonc; do + modules/image/volume.jsonc modules/image/language.jsonc; do ln -sfn "$repo/$f" "$target/$f" done @@ -136,7 +136,6 @@ for s in vms_dots.sh privacy_dots.sh; do ln -sfn "$repo/bin/$s" "$HOME/bin/$s" done ln -sfn "$repo/bin/wb-icon" "$HOME/bin/wb-icon" -ln -sfn "$repo/bin/wb-idle" "$HOME/bin/wb-idle" ln -sfn "$repo/bin/wb-lang" "$HOME/bin/wb-lang" # ----------------------------------------------------------------- checks -- diff --git a/modules/custom/presentation.jsonc b/modules/custom/presentation.jsonc new file mode 100644 index 0000000..3843cb5 --- /dev/null +++ b/modules/custom/presentation.jsonc @@ -0,0 +1,28 @@ +// Presentation mode, the same module the DP-1 bar runs. +// +// `statusctl presentation watch` streams waybar JSON on every change: it +// already emits an empty text and a class of activated, deactivated or down, +// so the icon is chosen in CSS from that class and no format is needed here. +// Dropping format-icons is the whole difference from the DP-1 copy, which +// draws the same two states as Nerd Font glyphs. +// +// `down` means the state file is missing, which is not the same as off: the +// mode cannot be read at all. It gets its own icon rather than borrowing the +// off one, so a broken watcher does not read as a working bar. +// +// statusctl talks to files under XDG_RUNTIME_DIR rather than to quickshell, +// so this keeps working while the shell is down. +// The format is a single space, and it has to be something. It defaults to +// "{text}", statusctl emits an empty text, and waybar draws no widget at all +// for a custom module whose formatted output is empty: not an empty pill, no +// pill. A space gives the label real content to be allocated for, shows +// nothing itself, and leaves the icon to the CSS background. +{ + "custom/presentation": { + "exec": "$HOME/bin/statusctl presentation watch", + "return-type": "json", + "on-click": "$HOME/bin/statusctl presentation toggle", + "format": " ", + "tooltip": true + } +} diff --git a/modules/image/idle_inhibitor.jsonc b/modules/image/idle_inhibitor.jsonc deleted file mode 100644 index e026be8..0000000 --- a/modules/image/idle_inhibitor.jsonc +++ /dev/null @@ -1,15 +0,0 @@ -// Presentation mode. -// -// waybar's own idle_inhibitor module draws its state with format-icons, which -// is text, so this is an `image` module driving the same inhibitor through -// hyprland. The state lives in a file because the icon has to be resolved by -// a separate process from the one that toggles it. -{ - "image#idle": { - "exec": "$HOME/bin/wb-idle status", - "size": 22, - "interval": 5, - "signal": 9, - "on-click": "$HOME/bin/wb-idle toggle" - } -} diff --git a/styles/modules.css b/styles/modules.css index 9cc3f5e..4d3d38e 100644 --- a/styles/modules.css +++ b/styles/modules.css @@ -174,7 +174,29 @@ border-radius: 8px; } -#image.idle { +/* Presentation mode. statusctl emits the state as a class and an empty text, + * so the icon is chosen here rather than by a format string. + * + * `down` is not `off`: the state file is missing, so the mode cannot be read + * at all. It draws a warning rather than the off icon, because a watcher that + * has died should not look like a mode that is simply not on. */ +#custom-presentation { + background-repeat: no-repeat; + background-position: center; + background-size: 18px 18px; padding: 2px 10px; min-width: 22px; + min-height: 18px; +} + +#custom-presentation.deactivated { + background-image: url("@ICONS@/categories/22/preferences-desktop-screensaver.svg"); +} + +#custom-presentation.activated { + background-image: url("@ICONS@/mimetypes/22/x-office-presentation.svg"); +} + +#custom-presentation.down { + background-image: url("@ICONS@/status/22/dialog-warning.svg"); } diff --git a/styles/pills.css b/styles/pills.css index 515f053..40c6629 100644 --- a/styles/pills.css +++ b/styles/pills.css @@ -21,7 +21,7 @@ #custom-privacy_dots, #volume, #tray, -#image.idle, +#custom-presentation, #image.lang { background: @main-bg; color: @main-fg; @@ -40,7 +40,7 @@ #custom-privacy_dots:hover, #volume:hover, #tray:hover, -#image.idle:hover, +#custom-presentation:hover, #image.lang:hover { background: @hover-bg; color: @hover-fg; @@ -53,15 +53,11 @@ * pill of each, so adding a module to a cluster means editing one rule. */ -/* Left: launcher | date time | workspaces | vm dots */ -#image.launcher { - margin-left: 6px; - margin-right: 6px; -} - +/* Top bar, left: date time | vm dots */ #clock.date { border-radius: 14px 0 0 14px; padding-right: 8px; + margin-left: 6px; } #clock.time { @@ -70,9 +66,16 @@ margin-right: 6px; } +/* Top bar, centre: the launcher stands alone. */ +#image.launcher { + margin-left: 6px; + margin-right: 6px; +} + +/* Bottom bar, left: the workspace strip. */ #workspaces { padding: 2px 6px; - margin-right: 6px; + margin-left: 6px; } /* Right: privacy | volume | tray | presentation | language */ @@ -88,7 +91,7 @@ margin-right: 6px; } -#image.idle { +#custom-presentation { margin-right: 6px; } @@ -96,7 +99,8 @@ margin-right: 6px; } -/* Center: the taskbar is one pill holding a row of buttons. */ +/* Bottom bar, right: the taskbar is one pill holding a row of buttons. */ #taskbar { padding: 2px 6px; + margin-right: 6px; } |
