# 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] 261 wallpapers │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌──┐ ┌─────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └────┘ └────┘ └────┘ └────┘ └┬─┘ └──┬──┘ │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ═╧═ ══╧══ │ │ │ │ │ │ │ │ │ │ [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. `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, })