aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/appearance/README.md
blob: 50da8fbfdaf19f8860622e5c8bc6d8d1cbc7ef41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# appearance

Six pages of the settings window: Wallpaper, Theme, Theme status, Icons &
cursor, HyprSunset and HyprIdle. Until October 2026 they were the tabs of a separate `appearance/`
shell with an overlay of its own; they moved into the desktop process when
the settings window gave them the room that shell existed to provide.

Each page is a `*Module.qml` here wrapping a `*Tab.qml`, and the work is done
by singletons (`Wallpapers`, `Udt`, `Icons`, `Hyprsunset`, `Hypridle`,
`Steps`). The window's open calls each module's `refresh()`, which rescans:
schemes, wallpapers and themes can all change during a session, and a scan
done only at startup would go stale.

The old shell's IPC target is kept in `desktop/shell.qml` with the same verbs,
so its callers only changed the path:

    qs -p ~/Programming/GIT/quickshell/desktop ipc call appearance wallpaper

Verbs: `wallpaper` (also `toggle`), `theme`, `sunset`, `idle`, `icons` open
that page; `steps` pokes the steps window; `selftest` runs the parsers' own
checks. Write the path out in full in the Hyprland config: `exec_cmd` has no
shell to expand `~`.

Search finds a scheme, a theme consumer, and an icon or cursor theme by
name, and scrolls to it.

## Theme status

The live scheme, accent and wallpaper, and one row per unified-desktop-theme
consumer, with what it themes, and a Re-apply button running `install.sh
<name>`, split into software and hardware lists; Re-apply all in the header
runs `install.sh` with no argument. The list is `install.sh --list`, which
reads the script's own `c_<name>` functions and their line comments
(`hardware:` marks a device), so a consumer added there appears here with no
edit. One run at a time, and not
during a scheme switch.

## 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 Icons page 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. It is chained after the `gsettings` write in one shell,
since it reads it. Waybar is gone, so the icon switch no longer reruns
waybar-theme-udt's `install.sh` or signals waybar.

## Wallpapers

Clicking a thumbnail **stages** it rather than setting it, so both screens can
be composed before anything changes, and Apply, in the page header like
every settings page's commit, then makes a single
`wallp --set H=… V=…` call. The mock screens above the list 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 settings window, as a successful Apply does, so the desk
shows. 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 page
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 pane **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/<name>.conf` holds the colours
under the scheme's own names, `palette/roles-<name>.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
settings window opens: the desktop process 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,
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

`Theme` is the desktop's shared one, through `import "../.."`: the palette
comes from `~/.cache/udt/udt-palette.qml` and is watched. The settings window
is a normal toplevel, so it takes Hyprland's ordinary window blur; the old
`blur-appearance` layer rule went with the overlay.