# appearance Wallpapers and colour scheme in one drawer. `SUPER+Return` opens it on the Wallpaper tab; Tab switches tabs, Escape closes. ┌─[ Wallpaper ]─[ Theme ]─[ Sunset ]─[ Idle ]─[ Icons ]──┐ │ Set on [Horizontal] [Vertical] 1161 wallpapers │ │ ▸ wallpapers 16 ┌──┐ ┌─────┐ │ │ ▾ SFW 30 │ │ │ │ │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ └┬─┘ └──┬──┘ │ │ │ │ │ │ │ │ │ │ ═╧═ ══╧══ │ │ └────┘ └────┘ └────┘ └────┘ enlarged 1.6×: … │ │ ▸ system 727 [Apply] [Reset] │ └────────────────────────────────────────────────────────┘ ## Running it qs -p . It is started from `autostart.lua` and reached over IPC, so the shell has to be running for the keybind to work: hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd( "qs -p ~/Programming/GIT/quickshell/appearance ipc call appearance wallpaper")) Write that path out in full in the real config: `exec_cmd` has no shell to expand `~`. `ipc call appearance theme` opens the Theme tab. IPC verbs: `wallpaper`, `theme`, `sunset`, `idle`, `icons`, `steps`. ## Sunset A port of `hyprsunset-qt` (`~/Programming/GIT/sunset-qt`): profiles in `~/.config/hypr/hyprsunset.conf`, location in `~/.config/hyprsunset-qt/config`, sunrise/sunset from the same API and cache. Both apps read and write the identical file format, so either can edit it. ## Idle Timeout and enabled only; the commands in `~/.config/hypr/hypridle.conf` are fixed. Everything before the first `listener` (the `general` block and the comments explaining the design) is preserved verbatim. A disabled listener is written commented out. Save restarts `hypridle`, which resets its timers. ## Icons Switches the icon and cursor theme, and the pointer size. Icon previews come from a GTK lookup per theme. Cursor previews come from one python pass per open: for every theme it extracts four shapes (arrow, hand, text, horizontal resize) out of a hyprcursor `.hlc`, a shape directory's SVG, or a legacy Xcursor binary via `xcur2png`, so a card shows the cases that tell themes apart. Hovering a card changes the real cursor and reverts it on leave. Applying writes `gsettings` (which GTK3 and GTK4 both read), the Qt configs (`qt6ct`/`qt5ct`), and, for cursors, `~/.config/udt/cursor.lua`, which hypr-theme's `environment.lua` `dofile()`s at login. A local file rather than an edit to `environment.lua`, which is tracked and was left dirty by every cursor switch. The GTK theme names do not come from `gsettings` alone here, so the drawer also rewrites `gtk-3.0/settings.ini` and `gtk-4.0/settings.ini` to match. Apps and a relogin are needed to see the rest. An icon switch also regenerates the two consumers that resolve icons by name when they are generated rather than when they run: `udt-accent` rewrites the SDDM power icons, and waybar-theme-udt's `install.sh` rewrites the taskbar theme and the module icons, then waybar is sent `SIGUSR2`. Both are chained after the `gsettings` write in one shell, since both read it. ## Wallpapers Clicking a thumbnail **stages** it rather than setting it, so both screens can be composed before anything changes, and Apply then makes a single `wallp --set H=… V=…` call. The mock screens on the right show what the desk would look like: the staged pick where there is one, what is currently set where there is not, and the hovered thumbnail on the targeted screen while the pointer is over it. The thumbnails are grouped by folder, each a header that opens and closes it, closed to begin with and remembered while the shell runs. The local groups are the top-level folders of `~/Pictures/wallpapers`, with the images loose in it as a group of its own; after them come the distribution's, `/usr/share/wallpapers`, as `system` for its loose images and `Plasma` for its packages. A Plasma package holds one picture at several resolutions, some in a dark version too, so it shows once: dark where there is one, then the largest, named after the package rather than its `5120x2880.png`. The list is flat, one row of thumbnails per item, so only the rows in view exist and decode, and a folder of hundreds loads as it scrolls. Opening or closing a folder inserts or removes just its rows, which keeps the scroll position and the thumbnails around it. A first version made each folder one item, which loaded every thumbnail of a folder the moment it opened. Each thumbnail's border says how it will look on the targeted screen, from its real size, which `file` reads from the header after each scan (half a second for a thousand). `swaybg -m fill` scales to cover, so the measure is how far it enlarges: not at all is green, up to 1.33x yellow, past that red. Under the mock screens a note in the same colour says why, for the hovered image or else the staged one. The staged image keeps the accent border. "Open wallhaven-dl" under the mock screens launches the wallhaven-dl app, which searches wallhaven.cc and finds larger copies of low-res wallpapers, and closes the panel. It goes through `gtk-launch` and the app's desktop entry, so `~/bin` need not be on quickshell's PATH. `wallp` does the actual work, including running `udt-accent`, so the accent follows the new wallpaper exactly as it does from a terminal. This panel replaces its qarma file dialog, not the script. The two monitors are drawn at their real proportions, read from `hyprctl monitors`: DP-1 is 2560x1080 and DP-3 is 1920x1080 with `transform=1`, which makes it 1080x1920 on the desk. They are centred against each other because that is how Hyprland has them, both spanning y=0. The bezel and stand are Rectangles rather than an SVG: no asset to ship, sharp at any size, and the case stays a fixed near-black while the accent marks which screen is targeted. Their proportions come from the panel **width**, not its height. Scaling the stand off height gave the wide monitor a 20px stand that was invisible and would have given the vertical one an absurd long neck. ## Themes Each scheme shows its real colours, parsed from the pair of files unified-desktop-theme keeps for it: `palette/.conf` holds the colours under the scheme's own names, `palette/roles-.conf` says what each colour is for, so a role is resolved by looking its value up as a key in the first file. Beside the swatches is a small mock of a panel painted in that scheme, which says how the colours sit together rather than only what they are. The list comes from the palette directory rather than a hardcoded set, so adding a scheme to unified-desktop-theme is enough to make it appear here. Both the scheme list and the wallpaper scan are redone every time the panel opens: this shell is autostarted and runs all session, so a scan done only at startup would go stale the first time anything was added. Applying writes the `scheme =` line in `~/.config/udt/roles.conf` and runs `install.sh`, which regenerates every themed config and reloads Hyprland, waybar, kitty and conky. Its whole output becomes the `install` tab of the steps window below. The notice names what is left: Qt and GTK apps only reread a theme when they restart, and neovim needs `:colorscheme udt`. ## Steps A separate window, `StepsWindow.qml`, for the manual steps unified-desktop-theme cannot run itself: the root copies into `/boot` and `/usr/share/sddm`, the finish on the homepage host, and the last `install.sh` log, shown first as "General recap". Its `udt-steps` writes one file per consumer under `~/.cache/udt/steps/` and calls the `steps` verb, which reloads the tabs and opens the window if any remain; an empty file set closes it. Nothing is watched: the poke is the signal. The same verb reopens it after Close. Centred on DP-3, on the `top` layer with on-demand keyboard focus, so it can stay open while commands are pasted into a terminal elsewhere. A line indented two spaces is a command block with a Copy button (`wl-copy`), and the text is selectable too. Done deletes the tab's file; the producer also clears it once its consumer is up to date. The window's height comes from its content, so the scroll area is capped against the screen and never against the window: binding it to the window's own height collapsed the window to its margins. ## Two QML traps met here **There is no `String.matchAll`.** QML's JS engine does not have it. It threw inside a `try` and left every swatch empty with nothing in the log. The palette parser uses an `exec` loop instead. **Assigning `running = true` to a Process that is already running does nothing.** The scheme loader reuses one `Process` for each scheme in turn and stopped after the first until it set `running = false` immediately before. ## Theme and blur `Theme.qml` is the shared one: the palette comes from `~/.cache/udt/udt-palette.qml` and is watched. Frosting is Hyprland's, matched on this window's namespace: hl.layer_rule({ name = "blur-appearance", match = { namespace = "^(quickshell-appearance)$" }, blur = true, xray = false, ignore_alpha = 0.1, })