Finish threading the accent colour through the whole desktop
The recent accent-colour setting only reached part of the desktop. looks.lua still hardcoded the focused-window border and glow to blue, so any hyprctl reload -- or every fresh session, for about a second -- reverted a chosen accent; it now reads accentName the same way it already read colorScheme. The lock screen and terminal stayed blue regardless of the chosen accent despite the setting's own description claiming otherwise; panama-lock and panama-theme-apps now resolve and apply the real accent. AccentPicker built its swatch model from Theme.accents directly instead of the schema's own options list, so the two could drift silently; switched it to read the schema. Its hit target only covered the swatch, not the name label added specifically for colour-vision accessibility -- extended to the whole row. Settings search had no route for the "appearance" group, so searching for the accent or colour scheme landed on Home. ColorScheme's hex-to-Hyprland helper assumed 6-digit colours and would silently corrupt a future translucent one; fixed it to read from the end of the string instead of the start. An accent-only change no longer reruns the full colour-scheme pipeline. The gradient it builds for the focused border now goes through SystemSettings' existing serialiser instead of a second, under-escaped copy of the same logic. The settings-ownership contract test enforced the old rule that ColorScheme must never touch the focused border; updated it to verify the real, intended rule instead of contradicting the code. Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
This commit is contained in:
@@ -34,13 +34,23 @@ Singleton {
|
||||
// for the Prism gradient; Theme owns which colours, this owns the form the
|
||||
// compositor wants them in.
|
||||
function hyprColor(value: var): string {
|
||||
// Qt gives "#rrggbb"; Hyprland wants rgba(rrggbbaa).
|
||||
return "rgba(" + String(value).replace("#", "").slice(0, 6) + "ee)";
|
||||
// Qt gives "#rrggbb" -- or "#aarrggbb" if the colour ever carries an
|
||||
// alpha channel. slice(-6) keeps the trailing rrggbb either way;
|
||||
// slice(0, 6) would instead grab "aarrgg" out of an 8-digit string and
|
||||
// call it RGB. Hyprland wants rgba(rrggbbaa).
|
||||
return "rgba(" + String(value).replace("#", "").slice(-6) + "ee)";
|
||||
}
|
||||
readonly property string accentBorderStart: root.hyprColor(Theme.accent)
|
||||
readonly property string accentBorderEnd: root.hyprColor(Theme.accentSecondary)
|
||||
property string lastError: ""
|
||||
|
||||
// What the last push actually sent, so apply() can tell an accent-only
|
||||
// change apart from a scheme change and skip the steps that do not depend
|
||||
// on whichever did not move. Their starting values do not matter: the
|
||||
// first apply() always runs with force set, which ignores both.
|
||||
property bool appliedDark: false
|
||||
property string appliedAccentName: ""
|
||||
|
||||
// Applied one command at a time: Process runs a single command, and several
|
||||
// of these are separate programs.
|
||||
property var pending: []
|
||||
@@ -75,7 +85,7 @@ Singleton {
|
||||
Timer {
|
||||
id: settle
|
||||
interval: 1200
|
||||
onTriggered: root.apply()
|
||||
onTriggered: root.apply(true)
|
||||
}
|
||||
|
||||
Connections {
|
||||
@@ -86,57 +96,92 @@ Singleton {
|
||||
Timer {
|
||||
id: coalesce
|
||||
interval: 250
|
||||
onTriggered: root.apply()
|
||||
onTriggered: root.apply(false)
|
||||
}
|
||||
|
||||
function apply(): void {
|
||||
// `force` pushes every step regardless of what moved -- startup needs
|
||||
// that, because gsettings and the compositor keep their own state and
|
||||
// have no way to know a previous Quickshell session already told them the
|
||||
// answer. Everywhere else this reacts to ANY preference changing (see
|
||||
// onRevisionChanged above), so most calls have nothing to do with either
|
||||
// scheme or accent; only the steps whose inputs actually moved since the
|
||||
// last push run, which keeps sampling accent swatches from also rewriting
|
||||
// gsettings and re-running the whole app-theming script on every sample.
|
||||
function apply(force: bool): void {
|
||||
root.lastError = "";
|
||||
|
||||
const scheme = root.dark ? "prefer-dark" : "prefer-light";
|
||||
const accentName = DesktopPreferences.get("accentName") || "blue";
|
||||
const schemeChanged = force || root.dark !== root.appliedDark;
|
||||
const accentChanged = force || accentName !== root.appliedAccentName;
|
||||
|
||||
// adw-gtk3, not Adwaita. This is the bug that made dark mode look
|
||||
// broken while light mode looked fine:
|
||||
//
|
||||
// Neither "Adwaita" nor "Adwaita-dark" is an installed theme on Fedora
|
||||
// 44 -- only adw-gtk3 and adw-gtk3-dark are. Naming a theme that does
|
||||
// not exist makes GTK fall back to its built-in default, which is
|
||||
// LIGHT. So asking for light accidentally worked, asking for dark
|
||||
// silently produced light, and applications that take their cue from
|
||||
// the GTK theme rather than the portal -- Chromium and Electron, when
|
||||
// built against GTK -- stayed light no matter what the portal said.
|
||||
//
|
||||
// gtk-theme-contract asserts these names are actually installed,
|
||||
// because the failure mode is silent in exactly this way.
|
||||
const gtkTheme = root.dark ? "adw-gtk3-dark" : "adw-gtk3";
|
||||
if (!schemeChanged && !accentChanged)
|
||||
return;
|
||||
|
||||
const commands = [
|
||||
["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", scheme],
|
||||
["gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", gtkTheme]
|
||||
];
|
||||
const commands = [];
|
||||
|
||||
// Unfocused window borders need scheme-relative contrast, and stay this
|
||||
// service's to own. The FOCUSED border is the accent role and belongs to
|
||||
// the theme -- see modules/settings/README.md -- so it is written from
|
||||
// the chosen accent rather than from the scheme.
|
||||
//
|
||||
// Both are pushed together because both change when the scheme flips:
|
||||
// each accent carries a separate pair for light and dark, so switching
|
||||
// schemes must restate the focused border too, not only the neutral one.
|
||||
commands.push(["hyprctl", "eval",
|
||||
`hl.config({ general = { col = { inactive_border = "${root.inactiveBorder}" } } })`]);
|
||||
if (schemeChanged) {
|
||||
const scheme = root.dark ? "prefer-dark" : "prefer-light";
|
||||
|
||||
// A two-stop gradient at the shipped angle. Written as a Lua TABLE: the
|
||||
// string form of a gradient carries only one stop, and passing
|
||||
// "rgba(a) rgba(b) 115deg" as a string is accepted and silently keeps
|
||||
// the previous value.
|
||||
commands.push(["hyprctl", "eval",
|
||||
`hl.config({ general = { col = { active_border = { colors = { "${root.accentBorderStart}", "${root.accentBorderEnd}" }, angle = 115 } } } })`]);
|
||||
// adw-gtk3, not Adwaita. This is the bug that made dark mode look
|
||||
// broken while light mode looked fine:
|
||||
//
|
||||
// Neither "Adwaita" nor "Adwaita-dark" is an installed theme on Fedora
|
||||
// 44 -- only adw-gtk3 and adw-gtk3-dark are. Naming a theme that does
|
||||
// not exist makes GTK fall back to its built-in default, which is
|
||||
// LIGHT. So asking for light accidentally worked, asking for dark
|
||||
// silently produced light, and applications that take their cue from
|
||||
// the GTK theme rather than the portal -- Chromium and Electron, when
|
||||
// built against GTK -- stayed light no matter what the portal said.
|
||||
//
|
||||
// gtk-theme-contract asserts these names are actually installed,
|
||||
// because the failure mode is silent in exactly this way.
|
||||
const gtkTheme = root.dark ? "adw-gtk3-dark" : "adw-gtk3";
|
||||
|
||||
// Applications that predate org.freedesktop.appearance and carry their
|
||||
// own palettes -- terminals, chiefly. Everything that reads the portal
|
||||
// (GTK4, Qt6, Chromium, Electron) is already handled by the gsettings
|
||||
// write above and needs nothing here.
|
||||
commands.push([root.appThemePath, root.dark ? "dark" : "light"]);
|
||||
commands.push(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", scheme]);
|
||||
commands.push(["gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", gtkTheme]);
|
||||
|
||||
// Unfocused window borders need scheme-relative contrast, and stay
|
||||
// this service's to own. The FOCUSED border below is the accent
|
||||
// role and belongs to the theme -- see modules/settings/README.md.
|
||||
// Unlike that role, this one does not depend on the accent, so an
|
||||
// accent-only change never needs to restate it.
|
||||
commands.push(["hyprctl", "eval",
|
||||
`hl.config({ general = { col = { inactive_border = "${root.inactiveBorder}" } } })`]);
|
||||
}
|
||||
|
||||
if (schemeChanged || accentChanged) {
|
||||
// Each accent carries a separate pair for light and dark, so this
|
||||
// runs on either kind of change: a scheme flip restates the same
|
||||
// accent's other pair, and an accent change restates the same
|
||||
// scheme's other colours.
|
||||
//
|
||||
// A two-stop gradient at the shipped angle. Written as a Lua TABLE:
|
||||
// the string form of a gradient carries only one stop, and passing
|
||||
// "rgba(a) rgba(b) 115deg" as a string is accepted and silently
|
||||
// keeps the previous value. Multi-stop must be the table form --
|
||||
// built by the same serialiseValue() SystemSettings uses for every
|
||||
// other gradient, rather than a second hand-rolled copy of that
|
||||
// escaping here.
|
||||
const activeBorder = SystemSettings.serialiseValue({
|
||||
colors: [root.accentBorderStart, root.accentBorderEnd],
|
||||
angle: 115
|
||||
});
|
||||
commands.push(["hyprctl", "eval",
|
||||
`hl.config({ general = { col = { active_border = ${activeBorder} } } })`]);
|
||||
|
||||
// Applications that predate org.freedesktop.appearance and carry
|
||||
// their own palettes -- terminals, chiefly. Everything that reads
|
||||
// the portal (GTK4, Qt6, Chromium, Electron) is already handled by
|
||||
// the gsettings write above and needs nothing here. The accent is
|
||||
// passed through too: kitty's border and the hyprlock fallback
|
||||
// template are also the accent role, not just the scheme -- so
|
||||
// this still has to run on an accent-only change, even though the
|
||||
// scheme-relative work it also does is then redundant.
|
||||
commands.push([root.appThemePath, root.dark ? "dark" : "light", accentName]);
|
||||
}
|
||||
|
||||
root.appliedDark = root.dark;
|
||||
root.appliedAccentName = accentName;
|
||||
|
||||
root.enqueue(commands);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ Singleton {
|
||||
// entry here still appears in results and routes to Home rather than being
|
||||
// dropped, so adding a group can never make a setting unreachable.
|
||||
readonly property var groupPages: ({
|
||||
"appearance": "appearance",
|
||||
"clock": "appearance",
|
||||
"vitals": "appearance",
|
||||
"typography": "appearance",
|
||||
|
||||
Reference in New Issue
Block a user