aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/udt-accent78
1 files changed, 59 insertions, 19 deletions
diff --git a/bin/udt-accent b/bin/udt-accent
index 53839b1..a862ae9 100755
--- a/bin/udt-accent
+++ b/bin/udt-accent
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0-only
# udt-accent: pick a Catppuccin Macchiato accent matching a wallpaper.
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
# Licensed under the GNU General Public License v2 only.
@@ -338,31 +339,42 @@ def icon_theme():
def find_icon(theme, names, dirs=None):
- """Absolute path to the first of `names` found in `theme`, else "".
-
- A theme may ship an icon flat or as a symbolic variant at several sizes.
- Prefer the symbolic variant, then the largest size directory, so the panel
- gets a tintable silhouette. `dirs` is injectable for the selftest.
+ """Absolute path to the first of `names` present in `theme`, else "".
+
+ The names are tried in order: the first that has any match wins. For that
+ name a theme may ship the icon flat or as a symbolic variant at several
+ sizes. Prefer the symbolic variant, then the largest size directory, where
+ `16x16`, `24x24`, `48x48`, a plain `16`/`48` and `scalable` are all parsed
+ and `scalable` counts as larger than any pixel size. `dirs` is injectable
+ for the selftest.
"""
if not theme:
return ""
dirs = ICON_DIRS if dirs is None else dirs
- wanted = set(names) | {f"{name}-symbolic" for name in names}
- matches = []
- for base in dirs:
- root = base / theme
- if not root.is_dir():
- continue
- matches = [p for p in root.rglob("*.svg") if p.stem in wanted]
- if matches:
- break
def rank(path):
symbolic = path.stem.endswith("-symbolic")
- sizes = [int(part) for part in path.parts if part.isdigit()]
- return (symbolic, max(sizes, default=0))
-
- return str(max(matches, key=rank)) if matches else ""
+ scalable = False
+ size = 0
+ for part in path.parts:
+ if part == "scalable":
+ scalable = True
+ continue
+ match = re.fullmatch(r"(\d+)(?:x\d+)?", part)
+ if match:
+ size = max(size, int(match.group(1)))
+ return (symbolic, scalable, size)
+
+ for name in names:
+ stems = {name, f"{name}-symbolic"}
+ for base in dirs:
+ root = base / theme
+ if not root.is_dir():
+ continue
+ matches = [p for p in root.rglob("*.svg") if p.stem in stems]
+ if matches:
+ return str(max(matches, key=rank))
+ return ""
def power_icons():
@@ -516,7 +528,7 @@ def selftest():
# Power icons: an unknown or empty theme resolves to empty, so the greeter
# falls back to its bundled SVGs; a real theme prefers the symbolic variant
- # and a name list is honoured in order.
+ # at the largest size, and a name list is tried in order.
assert find_icon("", ["system-shutdown"]) == ""
assert find_icon("no-such-theme-udt", ["system-shutdown"]) == ""
with tempfile.TemporaryDirectory() as tmpdir:
@@ -534,6 +546,34 @@ def selftest():
assert find_icon("probe", ["system-hibernate", "system-suspend-hibernate"],
dirs=[root]) == ""
+ # Among the sizes one name ships at, the largest directory wins. Both the
+ # WxH and plain-number spellings parse, and scalable is treated as largest.
+ with tempfile.TemporaryDirectory() as tmpdir:
+ root = Path(tmpdir) / "icons"
+ actions = root / "probe" / "actions"
+ for sub in ("16x16", "48x48"):
+ (actions / sub).mkdir(parents=True)
+ (actions / sub / "system-shutdown.svg").write_text("<svg/>")
+ got = find_icon("probe", ["system-shutdown"], dirs=[root])
+ assert got.endswith("actions/48x48/system-shutdown.svg"), got
+ (actions / "scalable").mkdir()
+ (actions / "scalable" / "system-shutdown.svg").write_text("<svg/>")
+ got = find_icon("probe", ["system-shutdown"], dirs=[root])
+ assert got.endswith("actions/scalable/system-shutdown.svg"), got
+
+ # The first name in the list that exists wins, even when a later name also
+ # has a match.
+ with tempfile.TemporaryDirectory() as tmpdir:
+ root = Path(tmpdir) / "icons"
+ base = root / "probe" / "actions" / "48"
+ base.mkdir(parents=True)
+ (base / "system-shutdown.svg").write_text("<svg/>")
+ (base / "system-reboot.svg").write_text("<svg/>")
+ got = find_icon("probe", ["system-shutdown", "system-reboot"], dirs=[root])
+ assert got.endswith("actions/48/system-shutdown.svg"), got
+ got = find_icon("probe", ["system-reboot", "system-shutdown"], dirs=[root])
+ assert got.endswith("actions/48/system-reboot.svg"), got
+
with tempfile.TemporaryDirectory() as tmpdir:
probe_image = Path(tmpdir) / "wall.png"
probe_image.write_bytes(b"")