aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 08:07:00 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 08:07:00 +0200
commit121235db5ca23e53e0dad3a402c521dc7a940fbc (patch)
treea899aff77c6cd08ee763d418d4904e51eaf3bb5c
parent2c23c9cc2fa5c26e408e7276f7da7d0cb37922d6 (diff)
downloadwaybar-theme-udt-121235db5ca23e53e0dad3a402c521dc7a940fbc.tar.gz
waybar-theme-udt-121235db5ca23e53e0dad3a402c521dc7a940fbc.zip
feat(icons): read the icon theme from gsettings
The icon theme name was hardcoded in three places (bin/wb-icon, modules/extras/taskbar.jsonc and install.sh), so switching themes in the appearance drawer was silently reverted as far as this bar was concerned. wb-icon now reads org.gnome.desktop.interface icon-theme and gains two modes for install.sh: --path NAME prints one icon's absolute path, --theme prints the resolved theme name. Icons are resolved per name rather than by substituting a directory prefix, because themes disagree about both subdirectory and file extension: the Suru themes keep web-browser at categories/22/*.svg, elementary-xfce at apps/22/*.png. Only a real GTK lookup knows the path. styles/modules.css therefore carries @ICON:name@ placeholders and modules/extras/taskbar.jsonc carries @ICON_THEME@, both filled at install time. taskbar.jsonc is generated rather than symlinked for that reason, and install.sh removes the old symlink first so the redirect cannot overwrite the committed source. A theme missing one of the twenty names fails the install at the --selftest step rather than drawing empty pills. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--README.md13
-rw-r--r--SPEC.md21
-rw-r--r--TODO-icons.md50
-rwxr-xr-xbin/wb-icon57
-rwxr-xr-xinstall.sh39
-rw-r--r--modules/extras/taskbar.jsonc6
-rw-r--r--styles/modules.css34
7 files changed, 177 insertions, 43 deletions
diff --git a/README.md b/README.md
index e7d8b58..2c85675 100644
--- a/README.md
+++ b/README.md
@@ -34,9 +34,10 @@ from there, and `install.sh` fails clearly if it is missing.
## No glyphs
-Every symbol on this bar is a real icon from `Material-Black-Plum-Suru`, the
-theme GTK already uses. Icons keep their own colours, matching every other
-application on the desktop.
+Every symbol on this bar is a real icon from the desktop's icon theme, read
+from gsettings, so the bar follows a theme switch instead of naming one theme
+forever. Icons keep their own colours, matching every other application on the
+desktop.
waybar offers three ways to draw one, and this bar uses all three because no
single one covers everything:
@@ -63,9 +64,9 @@ with nothing to say why, so it fails while someone is watching instead.
### Why the icons do not follow the accent
-2466 of the 5169 panel icons in `Material-Black-Plum-Suru` carry a hardcoded
-gradient and are not recolourable. That is intended here: icons match the rest
-of the desktop rather than the bar.
+2466 of the 5169 panel icons in `Material-Black-Plum-Suru`, the theme this
+desktop runs, carry a hardcoded gradient and are not recolourable. That is
+intended here: icons match the rest of the desktop rather than the bar.
The icons this repo ships are the exception, because the icon theme has none
of them. `icons/slackware.svg` (Simple Icons, CC0) is single-path monochrome,
diff --git a/SPEC.md b/SPEC.md
index 8042637..60cec00 100644
--- a/SPEC.md
+++ b/SPEC.md
@@ -10,8 +10,9 @@ icons from the desktop's own icon theme rather than font glyphs.
Three things at once:
- **No font glyphs.** Every module that shows a symbol shows a real icon from
- `Material-Black-Plum-Suru`, the theme GTK already uses. Icons keep their own
- colours, because that is what every other application on this desktop shows.
+ the desktop's icon theme, which `bin/wb-icon` reads from gsettings so a theme
+ switch reaches the bar. Icons keep their own colours, because that is what
+ every other application on this desktop shows.
- **Colours from udt.** Pill backgrounds, text and states resolve from
`palette/roles-<scheme>.conf`, so switching scheme recolours this bar with
everything else.
@@ -80,7 +81,9 @@ sits before the text, which is what makes it match the rest.
| 7 | `internet-chat` | chat glyph |
| 8 | `input-gaming` | joypad: games and media players |
-All eight resolve in `Material-Black-Plum-Suru` today.
+All eight resolve in `Material-Black-Plum-Suru`, the theme this desktop runs.
+`wb-icon --selftest` checks them against whatever theme is active, so a switch
+to a theme missing one fails the install rather than drawing an empty pill.
## How icons reach the bar
@@ -88,9 +91,11 @@ Three mechanisms, because waybar offers three and no single one covers
everything. This is the part worth reading before changing anything.
**1. Native `icon-theme` (taskbar, tray).** `wlr/taskbar` takes `icon-theme`
-and `icon-size` outright. Today it asks for `Papirus`, **which is not
-installed**, so GTK has been silently falling back. Pointing it at
-`Material-Black-Plum-Suru` is a bug fix, not a preference.
+and `icon-size` outright. It used to ask for `Papirus`, **which is not
+installed**, so GTK silently fell back. It now carries an `@ICON_THEME@`
+placeholder that `install.sh` fills from gsettings, which is why that one
+module file is generated rather than symlinked. Being a config key and not
+CSS, a change needs waybar restarted, not signalled.
**2. The `image` module (volume, presentation mode).** `image` runs `exec` and
expects `$path\n$tooltip` on stdout, so a small script maps state to an icon
@@ -110,8 +115,8 @@ stylesheet, not in the config.** Changing a workspace icon means editing CSS.
### Why the icons cannot follow the accent
-2466 of the 5169 panel icons in `Material-Black-Plum-Suru` carry a hardcoded
-`oomox` gradient (`#a08fbf` to `#5a33a3`) and seven further named gradients
+2466 of the 5169 panel icons in `Material-Black-Plum-Suru`, the theme this
+desktop runs, carry a hardcoded `oomox` gradient (`#a08fbf` to `#5a33a3`) and seven further named gradients
each. They are not monochrome and not recolourable by CSS `color`. So icons
show their own colours and do not track the scheme, which is the intended
behaviour here: they match every other application on the desktop.
diff --git a/TODO-icons.md b/TODO-icons.md
new file mode 100644
index 0000000..ac904f7
--- /dev/null
+++ b/TODO-icons.md
@@ -0,0 +1,50 @@
+# Unhardcode the icon theme
+
+Done. The active icon theme is now read from
+`gsettings get org.gnome.desktop.interface icon-theme`, which is what the
+appearance drawer's Icons tab sets, so a switch there reaches this bar instead
+of being silently reverted.
+
+## How it works
+
+`bin/wb-icon` reads the gsettings key once and resolves names through GTK's
+icon lookup. Nothing else may assume a path shape: themes disagree about both
+the subdirectory and the file extension (the Suru themes keep `web-browser` at
+`categories/22/*.svg`, elementary-xfce at `apps/22/*.png`), so substituting one
+directory prefix would have broken on any non-Suru theme.
+
+Two new modes serve `install.sh`:
+
+- `wb-icon --path NAME` prints one absolute path
+- `wb-icon --theme` prints the resolved theme name
+
+`styles/modules.css` names icons as `@ICON:name@` placeholders and
+`modules/extras/taskbar.jsonc` carries `@ICON_THEME@`; `install.sh` resolves
+both at install time. taskbar.jsonc is generated for this reason and is no
+longer symlinked.
+
+## After a theme switch
+
+Re-run `install.sh`, then **restart waybar**. CSS reloads itself through
+`reload_style_on_change`, but `taskbar.jsonc` is config: a signal is not enough.
+
+## Fixed elsewhere
+
+- `~/.config/rofi/config.rasi` — `icon-theme` commented out, so rofi follows
+ the desktop theme.
+- `~/.local/share/applications/firefox-clean.desktop` — absolute `Icon=` path
+ replaced with the bare name `firefox-aurora`, which the desktop-entry spec
+ resolves through the active theme (and which picks a 48px icon rather than
+ the 16px one that was pinned).
+
+## Not a conflict
+
+`~/.config/hypr/hyprqt6engine.conf`'s `icon_theme` is commented out.
+unified-desktop-theme's `templates/qt-gtk/*` are reference copies that nothing
+installs; see that repo's TODO-icons.md.
+
+## Caveat
+
+A theme lacking one of the 20 names this bar asks for fails the install at the
+`--selftest` step rather than rendering empty pills. elementary-xfce, for
+example, has no `text-editor` or `internet-chat`.
diff --git a/bin/wb-icon b/bin/wb-icon
index 9e011b9..1d76369 100755
--- a/bin/wb-icon
+++ b/bin/wb-icon
@@ -10,6 +10,7 @@
#
# wb-icon volume # reads wpctl, prints the matching icon
# wb-icon mic
+# wb-icon --path NAME # absolute path of one icon, for install.sh
# wb-icon --selftest # every name this bar asks for must resolve
#
# Icons keep their own colours: this desktop's icon theme draws them with
@@ -24,9 +25,29 @@ import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk # noqa: E402 (must follow require_version)
-# The icon theme to draw from. Kept in one place because changing it is a
-# single edit, and because --selftest needs the same value the modules use.
-THEME = "Material-Black-Plum-Suru"
+# The icon theme to draw from: whatever the desktop is set to. The appearance
+# drawer switches it through gsettings, so reading that key is what keeps this
+# bar following a switch instead of silently pointing at the old theme.
+#
+# The fallback only matters when gsettings is unreadable; GTK falls back to
+# hicolor on its own if the named theme is missing.
+FALLBACK_THEME = "Adwaita"
+
+
+def icon_theme():
+ """The desktop's icon theme name, per gsettings."""
+ try:
+ out = subprocess.run(
+ ["gsettings", "get", "org.gnome.desktop.interface", "icon-theme"],
+ capture_output=True, text=True, timeout=2,
+ ).stdout.strip()
+ except (OSError, subprocess.SubprocessError):
+ return FALLBACK_THEME
+ # gsettings quotes its answer: 'Material-Black-Plum-Suru'
+ return out.strip("'\"") or FALLBACK_THEME
+
+
+THEME = icon_theme()
SIZE = 22
@@ -47,7 +68,13 @@ ICONS = {
def lookup(name):
- """Absolute path of an icon in THEME, or None."""
+ """Absolute path of an icon in THEME, or None.
+
+ GTK resolves the theme's own layout, so this works across icon themes
+ that disagree about subdirectory and file extension: the Suru themes keep
+ web-browser under categories/22/*.svg, elementary-xfce under apps/22/*.png.
+ Nothing outside this function may assume a path shape.
+ """
theme = Gtk.IconTheme.new()
theme.set_custom_theme(THEME)
info = theme.lookup_icon(name, SIZE, 0)
@@ -138,7 +165,8 @@ def selftest():
def main(argv):
if len(argv) < 2 or argv[1] in ("-h", "--help"):
- print(__doc__ or "usage: wb-icon <volume|mic|idle|--selftest> [state]")
+ print(__doc__ or
+ "usage: wb-icon <volume|mic|--path NAME|--theme|--selftest>")
return 0
what = argv[1]
@@ -146,6 +174,25 @@ def main(argv):
if what == "--selftest":
return selftest()
+ # install.sh bakes icon paths into the generated stylesheet, because CSS
+ # cannot look an icon up by name. It asks here so that the theme is
+ # resolved in exactly one place.
+ if what == "--path":
+ if len(argv) < 3:
+ print("wb-icon: --path needs an icon name", file=sys.stderr)
+ return 2
+ path = lookup(argv[2])
+ if not path:
+ print(f"wb-icon: no icon named {argv[2]!r} in {THEME}",
+ file=sys.stderr)
+ return 1
+ print(path)
+ return 0
+
+ if what == "--theme":
+ print(THEME)
+ return 0
+
if what == "volume":
pct, muted = wpctl("@DEFAULT_AUDIO_SINK@")
if pct is None:
diff --git a/install.sh b/install.sh
index 42bc50a..775e318 100755
--- a/install.sh
+++ b/install.sh
@@ -15,7 +15,6 @@ set -euo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
target="$HOME/.config/waybar-udt"
udt="$(cd "$repo/../unified-desktop-theme" 2>/dev/null && pwd || true)"
-icons="$HOME/.local/share/icons/Material-Black-Plum-Suru"
mkdir -p "$target/modules/hyprland" "$target/modules/custom" \
"$target/modules/extras" "$target/modules/image" \
@@ -96,7 +95,7 @@ for f in modules/clock.jsonc \
modules/custom/vmdot.jsonc modules/custom/privacy_dots.jsonc \
modules/custom/presentation.jsonc modules/custom/mail.jsonc \
modules/custom/launcher.jsonc \
- modules/extras/taskbar.jsonc modules/extras/tray.jsonc \
+ modules/extras/tray.jsonc \
modules/image/volume.jsonc modules/image/language.jsonc; do
ln -sfn "$repo/$f" "$target/$f"
done
@@ -105,11 +104,37 @@ for f in style.css fonts.css global.css pills.css; do
ln -sfn "$repo/styles/$f" "$target/styles/$f"
done
-# modules.css is generated rather than linked: it carries the icon theme's
-# absolute path, which cannot be committed, and the accent-tinted launcher.
-sed -e "s|@ICONS@|$icons|g" -e "s|@CONFIG@|$target|g" \
+# modules.css is generated rather than linked: it carries absolute icon paths,
+# which cannot be committed, and the accent-tinted launcher.
+#
+# Each @ICON:name@ is resolved by wb-icon, which reads the desktop's icon theme
+# from gsettings. Resolving per name rather than substituting one directory is
+# what makes a theme switch work at all: themes disagree about both the
+# subdirectory and the file extension (Suru keeps web-browser at
+# categories/22/*.svg, elementary-xfce at apps/22/*.png), so only a real icon
+# lookup knows the path. An unresolved name is left as-is and reported; the
+# selftest below is what turns that into a failed install.
+sed -e "s|@CONFIG@|$target|g" \
"$repo/styles/modules.css" > "$target/styles/modules.css"
+for name in $(grep -o '@ICON:[a-z0-9-]*@' "$target/styles/modules.css" \
+ | sed 's/@ICON:\(.*\)@/\1/' | sort -u); do
+ if path="$("$repo/bin/wb-icon" --path "$name")"; then
+ sed -i "s|@ICON:$name@|$path|g" "$target/styles/modules.css"
+ else
+ echo "warning: no icon named '$name' in the current icon theme" >&2
+ fi
+done
+
+# taskbar.jsonc names the icon theme, which is gsettings' to say, so it is
+# generated for the same reason modules.css is. Every other module is a symlink.
+# An earlier install symlinked this file into the repo. Writing through that
+# link would overwrite the committed source with a generated copy, so the link
+# is removed first; the redirect below then creates a real file.
+rm -f "$target/modules/extras/taskbar.jsonc"
+sed "s|@ICON_THEME@|$("$repo/bin/wb-icon" --theme)|g" \
+ "$repo/modules/extras/taskbar.jsonc" > "$target/modules/extras/taskbar.jsonc"
+
# The launcher module carries no generated path any more: its mark is a CSS
# background, so modules.css owns the paths and the module file is a plain
# symlink like every other.
@@ -149,7 +174,9 @@ ln -sfn "$repo/bin/wb-lang" "$HOME/bin/wb-lang"
# ----------------------------------------------------------------- checks --
# A missing icon renders as an empty pill and nothing says why, so the names
-# are checked here, while someone is watching.
+# are checked here, while someone is watching. The theme is whatever gsettings
+# says, so this also catches a switch to a theme that lacks one of these names.
+echo "icon theme: $("$repo/bin/wb-icon" --theme)"
"$repo/bin/wb-icon" --selftest
echo
diff --git a/modules/extras/taskbar.jsonc b/modules/extras/taskbar.jsonc
index c7f88ea..b47448a 100644
--- a/modules/extras/taskbar.jsonc
+++ b/modules/extras/taskbar.jsonc
@@ -1,7 +1,9 @@
// Open applications, drawn with each application's own icon.
//
// icon-theme used to name Papirus, which is not installed on this machine, so
-// GTK fell back silently. It now names the theme the rest of the desktop uses.
+// GTK fell back silently. It is now @ICON_THEME@, substituted by install.sh
+// with whatever gsettings reports, so a theme switch reaches this bar too.
+// That is also why this file is generated rather than symlinked.
//
// all-outputs makes this a global window switcher rather than a list of what
// is on this screen: with it false, the DP-3 bar showed only the two windows
@@ -11,7 +13,7 @@
"wlr/taskbar": {
"all-outputs": true,
"format": "{icon}",
- "icon-theme": "Material-Black-Plum-Suru",
+ "icon-theme": "@ICON_THEME@",
"icon-size": 20,
"tooltip": true,
"tooltip-format": "{title}",
diff --git a/styles/modules.css b/styles/modules.css
index ee5c4b2..b6fd38d 100644
--- a/styles/modules.css
+++ b/styles/modules.css
@@ -2,8 +2,10 @@
*
* The icons for the workspaces, the clock pair, the launcher and the language
* live here rather than in the config, because waybar's format-icons takes
- * text and not paths. @ICONS@ is substituted by install.sh with the icon
- * theme's absolute path, so the theme can move without editing this file.
+ * text and not paths. Each icon placeholder below is substituted by install.sh
+ * with the absolute path `wb-icon --path <name>` resolves, so this file names
+ * icons and never a directory: themes disagree about both subdirectory and
+ * file extension, and only a real icon lookup knows which.
*/
/* ------------------------------------------------------------------ *
@@ -59,11 +61,11 @@
}
#clock.date {
- background-image: url("@ICONS@/mimetypes/22/x-office-calendar.svg");
+ background-image: url("@ICON:x-office-calendar@");
}
#clock.time {
- background-image: url("@ICONS@/categories/22/clock.svg");
+ background-image: url("@ICON:clock@");
}
/* ------------------------------------------------------------- *
@@ -104,14 +106,14 @@
/* The eight. Generic names, so the bar does not lie when the application on a
* workspace changes. */
-#workspaces button:nth-child(1) { background-image: url("@ICONS@/categories/22/web-browser.svg"); }
-#workspaces button:nth-child(2) { background-image: url("@ICONS@/categories/22/text-editor.svg"); }
-#workspaces button:nth-child(3) { background-image: url("@ICONS@/categories/22/utilities-terminal.svg"); }
-#workspaces button:nth-child(4) { background-image: url("@ICONS@/devices/22/network-server.svg"); }
-#workspaces button:nth-child(5) { background-image: url("@ICONS@/actions/22/document-edit.svg"); }
-#workspaces button:nth-child(6) { background-image: url("@ICONS@/categories/22/applications-graphics.svg"); }
-#workspaces button:nth-child(7) { background-image: url("@ICONS@/actions/22/dialog-messages.svg"); }
-#workspaces button:nth-child(8) { background-image: url("@ICONS@/devices/22/input-gaming.svg"); }
+#workspaces button:nth-child(1) { background-image: url("@ICON:web-browser@"); }
+#workspaces button:nth-child(2) { background-image: url("@ICON:text-editor@"); }
+#workspaces button:nth-child(3) { background-image: url("@ICON:utilities-terminal@"); }
+#workspaces button:nth-child(4) { background-image: url("@ICON:network-server@"); }
+#workspaces button:nth-child(5) { background-image: url("@ICON:document-edit@"); }
+#workspaces button:nth-child(6) { background-image: url("@ICON:applications-graphics@"); }
+#workspaces button:nth-child(7) { background-image: url("@ICON:dialog-messages@"); }
+#workspaces button:nth-child(8) { background-image: url("@ICON:input-gaming@"); }
/* ------------------------------------------------ *
* Language: a flag, shipped by this repo. *
@@ -135,7 +137,7 @@
* ------------------------------------------------------ */
#custom-mail {
- background-image: url("@ICONS@/actions/22/mail-unread.svg");
+ background-image: url("@ICON:mail-unread@");
background-repeat: no-repeat;
background-position: 10px center;
background-size: 16px 16px;
@@ -240,13 +242,13 @@
}
#custom-presentation.deactivated {
- background-image: url("@ICONS@/categories/22/preferences-desktop-screensaver.svg");
+ background-image: url("@ICON:preferences-desktop-screensaver@");
}
#custom-presentation.activated {
- background-image: url("@ICONS@/mimetypes/22/x-office-presentation.svg");
+ background-image: url("@ICON:x-office-presentation@");
}
#custom-presentation.down {
- background-image: url("@ICONS@/status/22/dialog-warning.svg");
+ background-image: url("@ICON:dialog-warning@");
}