Convert British spellings to American across the repo

colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.

Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).

The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.

Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
This commit is contained in:
Gabriel Brown
2026-08-19 08:07:55 -04:00
parent 8156fc47ca
commit d96863b687
113 changed files with 322 additions and 322 deletions
+2 -2
View File
@@ -14,10 +14,10 @@
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
# Generated from the colour scheme; machine state, not configuration. # Generated from the color scheme; machine state, not configuration.
/config/dot/kitty/current-theme.conf /config/dot/kitty/current-theme.conf
# Generated from the colour scheme; machine state, not configuration. # Generated from the color scheme; machine state, not configuration.
/config/dot/gtk-3.0/settings.ini /config/dot/gtk-3.0/settings.ini
/config/dot/gtk-4.0/settings.ini /config/dot/gtk-4.0/settings.ini
/config/dot/tmux/current-theme.conf /config/dot/tmux/current-theme.conf
+1 -1
View File
@@ -2,7 +2,7 @@
# #
# btop ships no Tokyo Night light variant at all, and the nearest stock light # btop ships no Tokyo Night light variant at all, and the nearest stock light
# theme (flat-remix-light) is a different palette that happens to have a similar # theme (flat-remix-light) is a different palette that happens to have a similar
# background. Same colours as kitty/themes/tokyonight-day.conf so the terminal # background. Same colors as kitty/themes/tokyonight-day.conf so the terminal
# and what runs inside it cannot disagree. # and what runs inside it cannot disagree.
# #
# Gradients keep the same low -> middle -> high meaning as the dark theme, using # Gradients keep the same low -> middle -> high meaning as the dark theme, using
+1 -1
View File
@@ -1,6 +1,6 @@
# GENERATED FILE -- edit settings.ini.template instead. # GENERATED FILE -- edit settings.ini.template instead.
# #
# The theme name and dark preference below follow Panama's colour # The theme name and dark preference below follow Panama's color
# scheme, so this file is regenerated on every switch and is not # scheme, so this file is regenerated on every switch and is not
# committed. Under GNOME, gnome-settings-daemon publishes these over # committed. Under GNOME, gnome-settings-daemon publishes these over
# XSETTINGS and this file is ignored; under Hyprland there is no # XSETTINGS and this file is ignored; under Hyprland there is no
+1 -1
View File
@@ -1,6 +1,6 @@
# GENERATED FILE -- edit settings.ini.template instead. # GENERATED FILE -- edit settings.ini.template instead.
# #
# The theme name and dark preference below follow Panama's colour # The theme name and dark preference below follow Panama's color
# scheme, so this file is regenerated on every switch and is not # scheme, so this file is regenerated on every switch and is not
# committed. Under GNOME, gnome-settings-daemon publishes these over # committed. Under GNOME, gnome-settings-daemon publishes these over
# XSETTINGS and this file is ignored; under Hyprland there is no # XSETTINGS and this file is ignored; under Hyprland there is no
+7 -7
View File
@@ -15,15 +15,15 @@ Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
| Hidden applications | GNOME App Hider list in Vicinae provider config | Live | | Hidden applications | GNOME App Hider list in Vicinae provider config | Live |
| Tiling and dynamic workspaces | Hyprland Lua config, Forge keymap preserved | Live | | Tiling and dynamic workspaces | Hyprland Lua config, Forge keymap preserved | Live |
| Overview | Continuum filmstrip, large workspace preview, cross-workspace search | Live | | Overview | Continuum filmstrip, large workspace preview, cross-workspace search | Live |
| Minimise | `SUPER+SHIFT+X` sends to scratchpad; `SUPER+X` restores | Live | | Minimize | `SUPER+SHIFT+X` sends to scratchpad; `SUPER+X` restores | Live |
| Dock | Quickshell dock with GNOME favourites and intellihide | Live | | Dock | Quickshell dock with GNOME favorites and intellihide | Live |
| Panel | Workspaces, CPU/RAM/GPU, clock, weather, quiet event capsule, media, clipboard, tray, status | Live | | Panel | Workspaces, CPU/RAM/GPU, clock, weather, quiet event capsule, media, clipboard, tray, status | Live |
| Clipboard history | Vicinae owns capture/storage; Quickshell provides the panel | Live | | Clipboard history | Vicinae owns capture/storage; Quickshell provides the panel | Live |
| Notifications | Quickshell server, toasts, grouped history, DND, clear actions | Live | | Notifications | Quickshell server, toasts, grouped history, DND, clear actions | Live |
| Date menu | Two-column Daybook with Agenda, Ongoing activity, Notifications, month calendar, weather, and media | Live | | Date menu | Two-column Daybook with Agenda, Ongoing activity, Notifications, month calendar, weather, and media | Live |
| Ongoing activity | Focus, Caffeine, recording, screen sharing, camera, and microphone state with direct controls | Live | | Ongoing activity | Focus, Caffeine, recording, screen sharing, camera, and microphone state with direct controls | Live |
| Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live | | Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live |
| Control Center | Wi-Fi, Bluetooth, audio input/output, Caffeine, Night Light, Focus, Home Assistant favourites, and KDE Connect phone continuity | Live | | Control Center | Wi-Fi, Bluetooth, audio input/output, Caffeine, Night Light, Focus, Home Assistant favorites, and KDE Connect phone continuity | Live |
| Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live | | Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live |
| Brightness | Appears only when a backlight device exists | Live, not applicable on this monitor | | Brightness | Appears only when a backlight device exists | Live, not applicable on this monitor |
| AppIndicator / tray | Native StatusNotifierItem host and themed menus | Live | | AppIndicator / tray | Native StatusNotifierItem host and themed menus | Live |
@@ -36,7 +36,7 @@ Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
| Removable media | udiskie plus udisks notifications | Live | | Removable media | udiskie plus udisks notifications | Live |
| Autostart apps | Nextcloud, Bitwarden, and RustDesk system service/tray | Live | | Autostart apps | Nextcloud, Bitwarden, and RustDesk system service/tray | Live |
| Printer administration | CUPS with the `system-config-printer` graphical interface | Live | | Printer administration | CUPS with the `system-config-printer` graphical interface | Live |
| System settings | The Settings app for display policy, appearance, desktop, sound, focus, shortcuts, and services; labelled GNOME hardware/account handoffs | Live | | System settings | The Settings app for display policy, appearance, desktop, sound, focus, shortcuts, and services; labeled GNOME hardware/account handoffs | Live |
| System health and recovery | Settings → System Health, `Panama: Check System Health` in Vicinae, a degraded-only bar indicator, redacted reports, and bounded Panama-owned repairs | Live | | System health and recovery | Settings → System Health, `Panama: Check System Health` in Vicinae, a degraded-only bar indicator, redacted reports, and bounded Panama-owned repairs | Live |
## System health and recovery ## System health and recovery
@@ -59,7 +59,7 @@ They never install packages, invoke `sudo`, delete user data, or rewrite
arbitrary configuration. arbitrary configuration.
Generic Fedora configuration remains with the system tools that own it. The Generic Fedora configuration remains with the system tools that own it. The
final System Health card hands network settings, users, sharing, colour final System Health card hands network settings, users, sharing, color
profiles, and digital wellbeing to their exact GNOME Settings panels rather profiles, and digital wellbeing to their exact GNOME Settings panels rather
than presenting inert Hyprland controls. than presenting inert Hyprland controls.
@@ -79,7 +79,7 @@ than presenting inert Hyprland controls.
| Bluetooth Quick Connect | Replaced by the full Bluetooth picker | | Bluetooth Quick Connect | Replaced by the full Bluetooth picker |
| Wi-Fi QR | Replaced by an on-demand QR-code sharing flow in Control Center's Wi-Fi panel; the code is generated only while shown and written to tmpfs, never persisted | | Wi-Fi QR | Replaced by an on-demand QR-code sharing flow in Control Center's Wi-Fi panel; the code is generated only while shown and written to tmpfs, never persisted |
| GSConnect | Replaced by capability-aware KDE Connect phone continuity in Control Center; the paired iPhone exposes file, clipboard, and Ring actions when reachable | | GSConnect | Replaced by capability-aware KDE Connect phone continuity in Control Center; the paired iPhone exposes file, clipboard, and Ring actions when reachable |
| Home Assistant | Replaced by secure favourites in Control Center; explicit private environment values take precedence over the existing GNOME extension and Secret Service setup | | Home Assistant | Replaced by secure favorites in Control Center; explicit private environment values take precedence over the existing GNOME extension and Secret Service setup |
| Custom Hot Corners Extended | No action was configured, so there is no behavior to port | | Custom Hot Corners Extended | No action was configured, so there is no behavior to port |
| App Hider | Hidden app list is now applied in Vicinae | | App Hider | Hidden app list is now applied in Vicinae |
| Allow Locked Remote Desktop | Mutter-specific patch; RustDesk is independent, but lock-screen access needs a real remote test | | Allow Locked Remote Desktop | Mutter-specific patch; RustDesk is independent, but lock-screen access needs a real remote test |
@@ -111,4 +111,4 @@ menu for a minority, which is worse than omitting it. Relevant references:
- <https://github.com/hyprwm/Hyprland/discussions/1358> - <https://github.com/hyprwm/Hyprland/discussions/1358>
A future focused-window menu could expose compositor actions such as float, A future focused-window menu could expose compositor actions such as float,
move, fullscreen, and close. It should not be labelled as an application menu. move, fullscreen, and close. It should not be labeled as an application menu.
+13 -13
View File
@@ -30,7 +30,7 @@ Don't "fix" them.
| `prefs.lua` | Reads the settings file the Settings app writes. See below | | `prefs.lua` | Reads the settings file the Settings app writes. See below |
| `env.lua` | Environment. Note the uwsm caveat below | | `env.lua` | Environment. Note the uwsm caveat below |
| `monitors.lua` | DP-2 geometry, scaling, and the HDR decision | | `monitors.lua` | DP-2 geometry, scaling, and the HDR decision |
| `looks.lua` | Colours, blur, glow, shadows, animations, VRR, scanout | | `looks.lua` | Colors, blur, glow, shadows, animations, VRR, scanout |
| `input.lua` | Keyboard/mouse. Click-to-focus, like GNOME | | `input.lua` | Keyboard/mouse. Click-to-focus, like GNOME |
| `rules.lua` | Window rules, gaming rules, layer rules for the shell | | `rules.lua` | Window rules, gaming rules, layer rules for the shell |
| `keybinds.lua` | The full keymap | | `keybinds.lua` | The full keymap |
@@ -87,7 +87,7 @@ persistence, validation, reset, and the live write are all derived from that
entry. entry.
`prefs.lua` never raises. A missing, empty, truncated, malformed, or `prefs.lua` never raises. A missing, empty, truncated, malformed, or
wrong-typed settings file costs you your customisations and nothing else; wrong-typed settings file costs you your customizations and nothing else;
`tests/hypr/prefs-fallback-contract.sh` pins that, including that Hyprland still `tests/hypr/prefs-fallback-contract.sh` pins that, including that Hyprland still
accepts the config in each of those states. accepts the config in each of those states.
@@ -163,7 +163,7 @@ four places, and nowhere else —
dock, popovers), running blue on the left to orchid on the right and fading dock, popovers), running blue on the left to orchid on the right and fading
out before the corners. See `quickshell/widgets/PrismEdge.qml`. out before the corners. See `quickshell/widgets/PrismEdge.qml`.
2. The focused window's border — `general.col.active_border`, blue→orchid at 2. The focused window's border — `general.col.active_border`, blue→orchid at
115°. Unfocused windows get no colour at all, because the gradient only 115°. Unfocused windows get no color at all, because the gradient only
means something if one window on screen is wearing it. means something if one window on screen is wearing it.
3. The active workspace pill in the bar. 3. The active workspace pill in the bar.
4. Slider fills. 4. Slider fills.
@@ -179,9 +179,9 @@ which is the opposite of the intent.
The Nerd Font is still used, but only to draw **icon glyphs** — it is the The Nerd Font is still used, but only to draw **icon glyphs** — it is the
pragmatic alternative to freedesktop symbolic icons, which ship with a pragmatic alternative to freedesktop symbolic icons, which ship with a
hardcoded `#2e3436` fill that Qt (unlike GTK) will not recolour. Where a hardcoded `#2e3436` fill that Qt (unlike GTK) will not recolor. Where a
themed freedesktop icon is wanted instead, `quickshell/widgets/ThemedIcon.qml` themed freedesktop icon is wanted instead, `quickshell/widgets/ThemedIcon.qml`
paints a palette colour through the icon's alpha. paints a palette color through the icon's alpha.
Anything whose digits change in place — the clock, the vitals percentages, the Anything whose digits change in place — the clock, the vitals percentages, the
recording timer, the selection readout — sets `font.features: recording timer, the selection readout — sets `font.features:
@@ -256,7 +256,7 @@ The mental model is unchanged from Forge:
| `SUPER + CTRL + G` / `Z` / `V` | Toggle split / preselect right / preselect down | | `SUPER + CTRL + G` / `Z` / `V` | Toggle split / preselect right / preselect down |
| `SUPER + Tab` / `SHIFT + Tab` | Cycle windows | | `SUPER + Tab` / `SHIFT + Tab` | Cycle windows |
| `SUPER + SHIFT + grave` | Last window | | `SUPER + SHIFT + grave` | Last window |
| `SUPER + X` / `SUPER + SHIFT + X` | Restore scratchpad / minimise to scratchpad | | `SUPER + X` / `SUPER + SHIFT + X` | Restore scratchpad / minimize to scratchpad |
### Workspaces (dynamic, like GNOME) ### Workspaces (dynamic, like GNOME)
| Key | Action | | Key | Action |
@@ -270,19 +270,19 @@ The mental model is unchanged from Forge:
### Launcher and shell ### Launcher and shell
| Key | Action | | Key | Action |
|---|---| |---|---|
| `SUPER + A` / `R` / `Space` | Launcher (vicinae) — all three, pick your favourite | | `SUPER + A` / `R` / `Space` | Launcher (vicinae) — all three, pick your favorite |
| `SUPER + SHIFT + R` | Fallback launcher (wofi) if the shell is broken | | `SUPER + SHIFT + R` | Fallback launcher (wofi) if the shell is broken |
| `SUPER + V` | Clipboard history | | `SUPER + V` | Clipboard history |
| `SUPER + .` | Emoji picker | | `SUPER + .` | Emoji picker |
| `SUPER + S` | Quick settings | | `SUPER + S` | Quick settings |
| `SUPER + I` | Settings | | `SUPER + I` | Settings |
| `SUPER + SHIFT + F` | Start or reveal focus session | | `SUPER + SHIFT + F` | Start or reveal focus session |
| `SUPER + B` | Notification centre | | `SUPER + B` | Notification center |
| `SUPER + grave` | Workspace overview | | `SUPER + grave` | Workspace overview |
| `Print` | Screenshot / record picker | | `Print` | Screenshot / record picker |
| `SHIFT` / `ALT + Print` | Screenshot screen / window immediately | | `SHIFT` / `ALT + Print` | Screenshot screen / window immediately |
| `SUPER + SHIFT + S` | Screen Intelligence — read text and codes from a selection | | `SUPER + SHIFT + S` | Screen Intelligence — read text and codes from a selection |
| `SUPER + SHIFT + P` | Colour picker | | `SUPER + SHIFT + P` | Color picker |
| `CTRL + ALT + L` | Lock (SUPER+L is "focus right") | | `CTRL + ALT + L` | Lock (SUPER+L is "focus right") |
| `CTRL + ALT + Delete` | Power menu | | `CTRL + ALT + Delete` | Power menu |
@@ -325,8 +325,8 @@ Caffeine, Night Light, Focus, audio input/output, user, settings, and power in
one place. Home and Phone continue the same surface rather than opening extra one place. Home and Phone continue the same surface rather than opening extra
dashboard windows. dashboard windows.
Home shows the first four selected favourites at rest and every selected light Home shows the first four selected favorites at rest and every selected light
when expanded. Use **Settings → Home & Phone** to choose favourites, when expanded. Use **Settings → Home & Phone** to choose favorites,
set Panama-only aliases, and arrange their order. Dragging a brightness control set Panama-only aliases, and arrange their order. Dragging a brightness control
only previews the value; releasing it sends one brightness request. A normal only previews the value; releasing it sends one brightness request. A normal
power toggle leaves Home Assistant responsible for restoring its previous power toggle leaves Home Assistant responsible for restoring its previous
@@ -334,7 +334,7 @@ level.
Credentials stay private in the gitignored `config/bash/env` file, with the Credentials stay private in the gitignored `config/bash/env` file, with the
existing GNOME extension and Secret Service setup retained as a compatibility existing GNOME extension and Secret Service setup retained as a compatibility
fallback. Favourites, aliases, and order live in Quickshell state. No shell fallback. Favorites, aliases, and order live in Quickshell state. No shell
restart is required after changing credentials; close and reopen Control Center restart is required after changing credentials; close and reopen Control Center
to refresh. If Home Assistant is offline, the last known values stay visible to refresh. If Home Assistant is offline, the last known values stay visible
with a stale-state label and Retry action. with a stale-state label and Retry action.
@@ -361,7 +361,7 @@ These are the places a 1:1 port was impossible, and what was done instead:
- **Per-edge resize.** Forge resized one named edge; Hyprland resizes along an - **Per-edge resize.** Forge resized one named edge; Hyprland resizes along an
axis and lets the layout pick the edge. The eight Forge keys collapse to four axis and lets the layout pick the edge. The eight Forge keys collapse to four
behaviours, keeping the horizontal/vertical and grow/shrink pairing. behaviors, keeping the horizontal/vertical and grow/shrink pairing.
- **Overview on `SUPER + grave`, not a bare SUPER tap.** Tap-detection on a - **Overview on `SUPER + grave`, not a bare SUPER tap.** Tap-detection on a
modifier misfires when you're quick with SUPER combos. modifier misfires when you're quick with SUPER combos.
- **Lock on `CTRL + ALT + L`.** `SUPER + L` is "focus right" in this keymap. - **Lock on `CTRL + ALT + L`.** `SUPER + L` is "focus right" in this keymap.
+2 -2
View File
@@ -1,7 +1,7 @@
-- ───────────────────────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────────────────────
-- Environment -- Environment
-- --
-- hl.env() sets variables before the display server initialises, which is what -- hl.env() sets variables before the display server initializes, which is what
-- makes it valid for AQ_* and cursor variables. -- makes it valid for AQ_* and cursor variables.
-- --
-- NOTE for uwsm sessions: if you log in via "Hyprland (uwsm-managed)", uwsm -- NOTE for uwsm sessions: if you log in via "Hyprland (uwsm-managed)", uwsm
@@ -58,7 +58,7 @@ hl.env("QT_QPA_PLATFORM", "wayland;xcb")
-- "gtk3" makes Qt derive its palette from the live GTK3 theme via the qgtk3 -- "gtk3" makes Qt derive its palette from the live GTK3 theme via the qgtk3
-- platform plugin, so Qt apps match adw-gtk3-dark exactly rather than -- platform plugin, so Qt apps match adw-gtk3-dark exactly rather than
-- approximating it -- and it gives them GTK file/font/colour dialogs. It also -- approximating it -- and it gives them GTK file/font/color dialogs. It also
-- covers Qt5 and Qt6 with one value. -- covers Qt5 and Qt6 with one value.
-- --
-- Note this is a single value, NOT a fallback list: Qt splits on ":" and uses -- Note this is a single value, NOT a fallback list: Qt splits on ":" and uses
+1 -1
View File
@@ -1,5 +1,5 @@
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# hypridle — idle behaviour # hypridle — idle behavior
# #
# Mirrors the GNOME power profile this replaces, exactly: # Mirrors the GNOME power profile this replaces, exactly:
# org.gnome.desktop.session idle-delay = 300 (screen off at 5min) # org.gnome.desktop.session idle-delay = 300 (screen off at 5min)
+1 -1
View File
@@ -15,7 +15,7 @@
-- ───────────────────────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────────────────────
-- Shared preferences first: looks/input/monitors read their defaults through it. -- Shared preferences first: looks/input/monitors read their defaults through it.
-- It never raises, so a missing or malformed settings file costs customisations -- It never raises, so a missing or malformed settings file costs customizations
-- and nothing else. -- and nothing else.
require("prefs") require("prefs")
+2 -2
View File
@@ -3,9 +3,9 @@
# #
# GENERATED FILE. Edit hyprlock.conf.template and re-run # GENERATED FILE. Edit hyprlock.conf.template and re-run
# quickshell/scripts/panama-theme-apps; editing this copy is overwritten on the # quickshell/scripts/panama-theme-apps; editing this copy is overwritten on the
# next colour scheme change. # next color scheme change.
# #
# The colours here follow the desktop's light/dark setting. They used to be # The colors here follow the desktop's light/dark setting. They used to be
# hardcoded Tokyo Night Moon, which meant the one screen you see most often # hardcoded Tokyo Night Moon, which meant the one screen you see most often
# stayed dark when everything else went light. # stayed dark when everything else went light.
# #
+1 -1
View File
@@ -9,7 +9,7 @@
# hyprland-guiutils hyprland-dialog / hyprland-run # hyprland-guiutils hyprland-dialog / hyprland-run
# hyprpolkitagent authentication prompts (via hyprland-qt-support) # hyprpolkitagent authentication prompts (via hyprland-qt-support)
# #
# Colours are Tokyo Night Moon, matching quickshell/config/Theme.qml and # Colors are Tokyo Night Moon, matching quickshell/config/Theme.qml and
# hypr/looks.lua. Format is hyprlang; path is fixed at # hypr/looks.lua. Format is hyprlang; path is fixed at
# $XDG_CONFIG_HOME/hypr/hyprtoolkit.conf (confirmed in libhyprtoolkit). # $XDG_CONFIG_HOME/hypr/hyprtoolkit.conf (confirmed in libhyprtoolkit).
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
+2 -2
View File
@@ -22,10 +22,10 @@ hl.config({
repeat_rate = prefs.get("keyRepeatRate", 33), repeat_rate = prefs.get("keyRepeatRate", 33),
-- 1 = FOLLOW. The window under the pointer takes focus. This comment -- 1 = FOLLOW. The window under the pointer takes focus. This comment
-- previously claimed 1 was "click to focus, GNOME's behaviour", which -- previously claimed 1 was "click to focus, GNOME's behavior", which
-- is the opposite of what Hyprland does -- `hyprctl descriptions` gives -- is the opposite of what Hyprland does -- `hyprctl descriptions` gives
-- map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}], -- map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}],
-- so click-to-focus is 0. Changing the shipped value is a behaviour -- so click-to-focus is 0. Changing the shipped value is a behavior
-- decision rather than a correction, so the value is left alone and -- decision rather than a correction, so the value is left alone and
-- only the description is fixed; Settings exposes all four. -- only the description is fixed; Settings exposes all four.
follow_mouse = prefs.getInt("followMouse", 1), follow_mouse = prefs.getInt("followMouse", 1),
+11 -11
View File
@@ -118,7 +118,7 @@ bind(mod .. " + SHIFT + F", hl.dsp.exec_cmd(qs("focus", "reveal")), { descriptio
-- which Hyprland has no equivalent for. -- which Hyprland has no equivalent for.
bind(mod .. " + grave", hl.dsp.exec_cmd(qs("overview", "toggle")), { description = "Overview" }) bind(mod .. " + grave", hl.dsp.exec_cmd(qs("overview", "toggle")), { description = "Overview" })
-- Notification centre. -- Notification center.
bind(mod .. " + B", hl.dsp.exec_cmd(qs("notifications", "toggle")), { description = "Notifications" }) bind(mod .. " + B", hl.dsp.exec_cmd(qs("notifications", "toggle")), { description = "Notifications" })
-- Screenshot / screen record. One key, then pick screen / window / region and -- Screenshot / screen record. One key, then pick screen / window / region and
@@ -133,8 +133,8 @@ bind("ALT + Print", hl.dsp.exec_cmd(qs("capture", "windowNow")), { description =
bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd(qs("screen-intelligence", "open")), bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd(qs("screen-intelligence", "open")),
{ description = "Screen Intelligence" }) { description = "Screen Intelligence" })
-- Colour picker: copies the hex under the cursor to the clipboard. -- Color picker: copies the hex under the cursor to the clipboard.
bind(mod .. " + SHIFT + P", hl.dsp.exec_cmd("hyprpicker -a -f hex"), { description = "Colour picker" }) bind(mod .. " + SHIFT + P", hl.dsp.exec_cmd("hyprpicker -a -f hex"), { description = "Color picker" })
-- ── Window management ─────────────────────────────────────────────────────── -- ── Window management ───────────────────────────────────────────────────────
bind(mod .. " + Q", hl.dsp.window.close(), { description = "Close window" }) bind(mod .. " + Q", hl.dsp.window.close(), { description = "Close window" })
@@ -182,7 +182,7 @@ bind(mod .. " + CTRL + L", hl.dsp.window.swap({ direction = "r" }), { descriptio
-- --
-- Forge resized one named edge at a time. Hyprland resizes the active window -- Forge resized one named edge at a time. Hyprland resizes the active window
-- along an axis and lets the layout decide which edge actually moves, so the -- along an axis and lets the layout decide which edge actually moves, so the
-- eight Forge keys collapse onto four behaviours. The pairing is kept -- eight Forge keys collapse onto four behaviors. The pairing is kept
-- consistent with the original: Y/B/O/M are horizontal, I/P/U/N are vertical, -- consistent with the original: Y/B/O/M are horizontal, I/P/U/N are vertical,
-- and "increase" always grows while "decrease" always shrinks. -- and "increase" always grows while "decrease" always shrinks.
local step = 60 local step = 60
@@ -192,7 +192,7 @@ bind(mod .. " + SHIFT + B", hl.dsp.window.resize({ x = -step, y = 0, relative =
bind(mod .. " + SHIFT + M", hl.dsp.window.resize({ x = -step, y = 0, relative = true }), { repeating = true, description = "Narrower" }) bind(mod .. " + SHIFT + M", hl.dsp.window.resize({ x = -step, y = 0, relative = true }), { repeating = true, description = "Narrower" })
bind(mod .. " + SHIFT + I", hl.dsp.window.resize({ x = 0, y = step, relative = true }), { repeating = true, description = "Taller" }) bind(mod .. " + SHIFT + I", hl.dsp.window.resize({ x = 0, y = step, relative = true }), { repeating = true, description = "Taller" })
bind(mod .. " + SHIFT + U", hl.dsp.window.resize({ x = 0, y = step, relative = true }), { repeating = true, description = "Taller" }) bind(mod .. " + SHIFT + U", hl.dsp.window.resize({ x = 0, y = step, relative = true }), { repeating = true, description = "Taller" })
-- SUPER+SHIFT+P was double-bound with the colour picker above; moved to -- SUPER+SHIFT+P was double-bound with the color picker above; moved to
-- Comma, which continues the bottom-row cluster (B/M/N) this axis already -- Comma, which continues the bottom-row cluster (B/M/N) this axis already
-- uses rather than landing on an arbitrary free key. -- uses rather than landing on an arbitrary free key.
bind(mod .. " + SHIFT + Comma", hl.dsp.window.resize({ x = 0, y = -step, relative = true }), { repeating = true, description = "Shorter" }) bind(mod .. " + SHIFT + Comma", hl.dsp.window.resize({ x = 0, y = -step, relative = true }), { repeating = true, description = "Shorter" })
@@ -227,7 +227,7 @@ bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true, description
-- moving right past the last workspace creates a new one, and moving left from -- moving right past the last workspace creates a new one, and moving left from
-- the first clamps instead of wrapping. -- the first clamps instead of wrapping.
-- Behaviour for the relative/cyclic binds below. These are Hyprland's own -- Behavior for the relative/cyclic binds below. These are Hyprland's own
-- `binds:` options -- not part of general/dwindle -- and have no other home -- `binds:` options -- not part of general/dwindle -- and have no other home
-- in the config, so they are read here rather than in looks.lua. -- in the config, so they are read here rather than in looks.lua.
hl.config({ hl.config({
@@ -259,18 +259,18 @@ end
bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "+1" }), { description = "Workspace right" }) bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "+1" }), { description = "Workspace right" })
bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "-1" }), { description = "Workspace left" }) bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "-1" }), { description = "Workspace left" })
-- Minimise, as far as Hyprland has one. -- Minimize, as far as Hyprland has one.
-- --
-- Hyprland has no minimise: it receives the request (the binary has -- Hyprland has no minimize: it receives the request (the binary has
-- setSetMinimized handlers for xdg, XWayland and foreign-toplevel) but exposes -- setSetMinimized handlers for xdg, XWayland and foreign-toplevel) but exposes
-- no dispatcher, no config option and not even an event to hook, so titlebar -- no dispatcher, no config option and not even an event to hook, so titlebar
-- minimise buttons are inert and cannot be made to work. A tiling WM has no -- minimize buttons are inert and cannot be made to work. A tiling WM has no
-- iconified state and no taskbar to restore from. -- iconified state and no taskbar to restore from.
-- --
-- The scratchpad is the honest equivalent: the window goes away, and the same -- The scratchpad is the honest equivalent: the window goes away, and the same
-- key brings it back. Bound to X to match the muscle memory it replaces. -- key brings it back. Bound to X to match the muscle memory it replaces.
bind(mod .. " + X", hl.dsp.workspace.toggle_special("scratch"), { description = "Toggle scratchpad (restore minimised)" }) bind(mod .. " + X", hl.dsp.workspace.toggle_special("scratch"), { description = "Toggle scratchpad (restore minimized)" })
bind(mod .. " + SHIFT + X", hl.dsp.window.move({ workspace = "special:scratch" }), { description = "Minimise to scratchpad" }) bind(mod .. " + SHIFT + X", hl.dsp.window.move({ workspace = "special:scratch" }), { description = "Minimize to scratchpad" })
-- ── Session ───────────────────────────────────────────────────────────────── -- ── Session ─────────────────────────────────────────────────────────────────
-- GNOME's lock was SUPER+L, which is "focus right" here, so lock moves to -- GNOME's lock was SUPER+L, which is "focus right" here, so lock moves to
+4 -4
View File
@@ -1,7 +1,7 @@
-- ───────────────────────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────────────────────
-- Look and feel -- Tokyo Night Moon -- Look and feel -- Tokyo Night Moon
-- --
-- Colours here must stay in sync with quickshell/config/Theme.qml. -- Colors here must stay in sync with quickshell/config/Theme.qml.
-- accent user-selectable, see `accents` below -- blue (#82aaff) ships -- accent user-selectable, see `accents` below -- blue (#82aaff) ships
-- bg #222436 base -- bg #222436 base
-- --
@@ -53,7 +53,7 @@ hl.config({
-- from Theme.accent/Theme.accentSecondary on every scheme change -- from Theme.accent/Theme.accentSecondary on every scheme change
-- too, since each accent carries a separate pair per scheme. -- too, since each accent carries a separate pair per scheme.
active_border = { colors = { "rgba(" .. accentStart .. "ee)", "rgba(" .. accentEnd .. "ee)" }, angle = 115 }, active_border = { colors = { "rgba(" .. accentStart .. "ee)", "rgba(" .. accentEnd .. "ee)" }, angle = 115 },
-- The neutral inactive role follows the colour scheme because a -- The neutral inactive role follows the color scheme because a
-- dark neutral disappears against a light desktop. -- dark neutral disappears against a light desktop.
-- services/ColorScheme.qml applies the same values live; this is -- services/ColorScheme.qml applies the same values live; this is
-- the value a fresh session starts from. -- the value a fresh session starts from.
@@ -193,12 +193,12 @@ hl.config({
-- Let a crashed lock screen be recovered rather than stranding you. -- Let a crashed lock screen be recovered rather than stranding you.
allow_session_lock_restore = true, allow_session_lock_restore = true,
-- Don't let apps steal focus by shouting; matches GNOME's behaviour. -- Don't let apps steal focus by shouting; matches GNOME's behavior.
focus_on_activate = false, focus_on_activate = false,
}, },
render = { render = {
-- Colour management is stable and on by default in 0.56. -- Color management is stable and on by default in 0.56.
cm_enabled = true, cm_enabled = true,
-- 1 = automatically flip the monitor into HDR for fullscreen content -- 1 = automatically flip the monitor into HDR for fullscreen content
+2 -2
View File
@@ -25,7 +25,7 @@ local prefs = require("prefs")
-- x = 0, y = 0, primary = true, -- x = 0, y = 0, primary = true,
-- } } -- } }
-- --
-- Only mode, scale, and transform are read. Colour management and bit depth -- Only mode, scale, and transform are read. Color management and bit depth
-- stay here, because those are the settings with a documented reason attached -- stay here, because those are the settings with a documented reason attached
-- (see the header) rather than preferences, and a settings page has no way to -- (see the header) rather than preferences, and a settings page has no way to
-- explain the screencopy tradeoff at the moment you would be changing it. -- explain the screencopy tradeoff at the moment you would be changing it.
@@ -147,7 +147,7 @@ hl.monitor({
}) })
-- Other connected outputs use the same validated per-output store. They keep -- Other connected outputs use the same validated per-output store. They keep
-- automatic placement and the compositor's normal colour policy; DP-2 alone -- automatic placement and the compositor's normal color policy; DP-2 alone
-- carries the panel-specific 10-bit policy documented above. -- carries the panel-specific 10-bit policy documented above.
for output, _ in pairs(displays) do for output, _ in pairs(displays) do
if output ~= "DP-2" then if output ~= "DP-2" then
+1 -1
View File
@@ -16,7 +16,7 @@
-- cannot drift apart. -- cannot drift apart.
-- --
-- Nothing here may raise. A missing, empty, truncated, or actively malformed -- Nothing here may raise. A missing, empty, truncated, or actively malformed
-- file must cost the user nothing worse than their customisations; it must -- file must cost the user nothing worse than their customizations; it must
-- never cost them a working compositor. Every failure path returns the caller's -- never cost them a working compositor. Every failure path returns the caller's
-- fallback. -- fallback.
-- ───────────────────────────────────────────────────────────────────────────── -- ─────────────────────────────────────────────────────────────────────────────
+2 -2
View File
@@ -69,7 +69,7 @@ hl.window_rule({
float = true, float = true,
}) })
-- Settings-style utility windows and pickers: float and centre, like GNOME did. -- Settings-style utility windows and pickers: float and center, like GNOME did.
hl.window_rule({ hl.window_rule({
match = { class = "^(pavucontrol|org\\.pulseaudio\\.pavucontrol|nm-connection-editor|blueman-manager|org\\.gnome\\.Settings)$" }, match = { class = "^(pavucontrol|org\\.pulseaudio\\.pavucontrol|nm-connection-editor|blueman-manager|org\\.gnome\\.Settings)$" },
float = true, float = true,
@@ -150,7 +150,7 @@ hl.layer_rule({
ignore_alpha = 0.3, ignore_alpha = 0.3,
}) })
-- Popovers: calendar, quick settings, notification centre, tray menus. -- Popovers: calendar, quick settings, notification center, tray menus.
hl.layer_rule({ hl.layer_rule({
name = "qs-popover", name = "qs-popover",
match = { namespace = "^qs-popover" }, match = { namespace = "^qs-popover" },
+1 -1
View File
@@ -1,7 +1,7 @@
# Tokyo Night Moon — the dark theme Panama ships. # Tokyo Night Moon — the dark theme Panama ships.
# #
# Extracted from kitty.conf so the two schemes can be swapped. kitty.conf # Extracted from kitty.conf so the two schemes can be swapped. kitty.conf
# includes current-theme.conf, which Panama generates from the colour scheme # includes current-theme.conf, which Panama generates from the color scheme
# setting; that generated file is gitignored because it is machine state. # setting; that generated file is gitignored because it is machine state.
# #
# Live changes go through `kitty @ set-colors`, which is why kitty.conf enables # Live changes go through `kitty @ set-colors`, which is why kitty.conf enables
+1 -1
View File
@@ -6,7 +6,7 @@
-- Hyprland config read. -- Hyprland config read.
-- --
-- Nothing here may raise. A missing or malformed settings file must cost the -- Nothing here may raise. A missing or malformed settings file must cost the
-- user their colour scheme preference and nothing else; editing text is more -- user their color scheme preference and nothing else; editing text is more
-- important than matching the desktop. -- important than matching the desktop.
local M = {} local M = {}
+3 -3
View File
@@ -1,12 +1,12 @@
-- Tokyo Night, following the desktop's colour scheme. -- Tokyo Night, following the desktop's color scheme.
-- --
-- Moon when Panama is dark, Day when it is light. Same theme family either way, -- Moon when Panama is dark, Day when it is light. Same theme family either way,
-- so the editor keeps the identity the rest of the desktop has rather than -- so the editor keeps the identity the rest of the desktop has rather than
-- becoming a different-looking application when the scheme flips. -- becoming a different-looking application when the scheme flips.
-- --
-- The readability fixes below are deliberately dark-only. They were written -- The readability fixes below are deliberately dark-only. They were written
-- against Moon's palette -- a pale comment colour, a mid-grey gutter -- and -- against Moon's palette -- a pale comment color, a mid-gray gutter -- and
-- applying them to Day would put light grey text on a light background, which -- applying them to Day would put light gray text on a light background, which
-- is exactly the legibility problem they exist to solve, inverted. -- is exactly the legibility problem they exist to solve, inverted.
local panama = require("config.panama") local panama = require("config.panama")
+1 -1
View File
@@ -22,7 +22,7 @@ console_title_template = '{{ .Shell }} in {{ .Folder }}'
[[blocks.segments]] [[blocks.segments]]
type = 'git' type = 'git'
style = 'plain' style = 'plain'
foreground = 'p:grey' foreground = 'p:gray'
background = 'transparent' background = 'transparent'
template = ' {{ .HEAD }}{{ if or (.Working.Changed) (.Staging.Changed) }}*{{ end }} <cyan>{{ if gt .Behind 0 }}⇣{{ end }}{{ if gt .Ahead 0 }}⇡{{ end }}</>' template = ' {{ .HEAD }}{{ if or (.Working.Changed) (.Staging.Changed) }}*{{ end }} <cyan>{{ if gt .Behind 0 }}⇣{{ end }}{{ if gt .Ahead 0 }}⇡{{ end }}</>'
@@ -101,7 +101,7 @@ Singleton {
const raw = (parsed && typeof parsed === "object") ? parsed : {}; const raw = (parsed && typeof parsed === "object") ? parsed : {};
// Upgrade before anything reads a value. A stored key the current // Upgrade before anything reads a value. A stored key the current
// schema no longer recognises is carried through untouched and silently // schema no longer recognizes is carried through untouched and silently
// stops taking effect, so the conversion has to happen here rather than // stops taking effect, so the conversion has to happen here rather than
// being noticed later by whoever owns that setting. // being noticed later by whoever owns that setting.
const result = Migrations.apply(raw); const result = Migrations.apply(raw);
@@ -19,7 +19,7 @@ Singleton {
printErrors: false printErrors: false
atomicWrites: true atomicWrites: true
onSaved: root.saveError = "" onSaved: root.saveError = ""
onSaveFailed: error => root.saveError = "Could not save Home favourites." onSaveFailed: error => root.saveError = "Could not save Home favorites."
JsonAdapter { JsonAdapter {
id: values id: values
+1 -1
View File
@@ -5,7 +5,7 @@ pragma Singleton
// The schema is the single source of truth for what a setting IS, but it cannot // The schema is the single source of truth for what a setting IS, but it cannot
// describe what a setting USED to be. Renaming a key, changing its units, or // describe what a setting USED to be. Renaming a key, changing its units, or
// splitting one setting into two all leave a stored value that the new schema // splitting one setting into two all leave a stored value that the new schema
// does not recognise -- and an unrecognised key is silently carried through // does not recognize -- and an unrecognized key is silently carried through
// untouched, so the user's choice simply stops taking effect with nothing to // untouched, so the user's choice simply stops taking effect with nothing to
// say why. That is the failure this exists to prevent. // say why. That is the failure this exists to prevent.
// //
@@ -171,7 +171,7 @@ Singleton {
unit: "px", unit: "px",
group: "windows", group: "windows",
label: "Inner gaps", label: "Inner gaps",
detail: "Space between neighbouring tiled windows", detail: "Space between neighboring tiled windows",
hypr: { path: ["general", "gaps_in"], option: "general:gaps_in", readAs: "css" } hypr: { path: ["general", "gaps_in"], option: "general:gaps_in", readAs: "css" }
}, },
{ {
@@ -278,7 +278,7 @@ Singleton {
// ── Master layout ─────────────────────────────────────────────────── // ── Master layout ───────────────────────────────────────────────────
// Only meaningful when the tiling layout is Master and stack. Offering // Only meaningful when the tiling layout is Master and stack. Offering
// that layout with none of its options was an omission: it is the one // that layout with none of its options was an omission: it is the one
// layout whose whole behaviour is in these settings. // layout whose whole behavior is in these settings.
{ {
key: "masterFactor", type: "real", def: 0.55, min: 0.1, max: 0.9, step: 0.05, key: "masterFactor", type: "real", def: 0.55, min: 0.1, max: 0.9, step: 0.05,
group: "master", group: "master",
@@ -295,7 +295,7 @@ Singleton {
{ value: "right", label: "Right" }, { value: "right", label: "Right" },
{ value: "top", label: "Top" }, { value: "top", label: "Top" },
{ value: "bottom", label: "Bottom" }, { value: "bottom", label: "Bottom" },
{ value: "center", label: "Centre" } { value: "center", label: "Center" }
], ],
hypr: { path: ["master", "orientation"], option: "master:orientation", readAs: "str" } hypr: { path: ["master", "orientation"], option: "master:orientation", readAs: "str" }
}, },
@@ -429,7 +429,7 @@ Singleton {
{ {
key: "keyboardVariant", type: "string", def: "", group: "input", key: "keyboardVariant", type: "string", def: "", group: "input",
// Same shape as the layout list and for the same reason: this is // Same shape as the layout list and for the same reason: this is
// serialised into an hl.config string. // serialized into an hl.config string.
pattern: "^$|^[a-z0-9_]{1,24}(,[a-z0-9_]{1,24})*$", pattern: "^$|^[a-z0-9_]{1,24}(,[a-z0-9_]{1,24})*$",
label: "Layout variant", label: "Layout variant",
detail: "XKB variant, such as dvorak or colemak. Empty for the standard layout", detail: "XKB variant, such as dvorak or colemak. Empty for the standard layout",
@@ -475,7 +475,7 @@ Singleton {
// The compositor publishes the authoritative mapping itself -- // The compositor publishes the authoritative mapping itself --
// `hyprctl descriptions` gives // `hyprctl descriptions` gives
// map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}] // map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}]
// -- so a desktop labelled "Click to focus" was in fact following // -- so a desktop labeled "Click to focus" was in fact following
// the pointer, and the way to actually get click-to-focus was to // the pointer, and the way to actually get click-to-focus was to
// choose "Never". Value 3 was missing entirely. // choose "Never". Value 3 was missing entirely.
// //
@@ -852,14 +852,14 @@ Singleton {
detail: "A time earlier than the start simply means the next morning" detail: "A time earlier than the start simply means the next morning"
}, },
// ── Colour scheme ─────────────────────────────────────────────────── // ── Color scheme ───────────────────────────────────────────────────
// Light is Tokyo Night Day, the official light variant, rather than a // Light is Tokyo Night Day, the official light variant, rather than a
// palette invented to merely not be dark. Both share the same hues at // palette invented to merely not be dark. Both share the same hues at
// different lightness, which is what keeps the Prism identity intact // different lightness, which is what keeps the Prism identity intact
// across the switch. // across the switch.
// //
// services/ColorScheme.qml pushes the choice to GTK and to the // services/ColorScheme.qml pushes the choice to GTK and to the
// compositor's border colours, because an application toolbar or a // compositor's border colors, because an application toolbar or a
// window border still wearing the other scheme is more jarring than // window border still wearing the other scheme is more jarring than
// either scheme on its own. // either scheme on its own.
{ {
@@ -873,10 +873,10 @@ Singleton {
}, },
{ {
key: "accentName", type: "enum", def: "blue", group: "appearance", key: "accentName", type: "enum", def: "blue", group: "appearance",
label: "Accent colour", label: "Accent color",
detail: "Drives the focused window border, the bar hairline, and every active state", detail: "Drives the focused window border, the bar hairline, and every active state",
// NAMED accents, not a free colour. Each name carries a curated // NAMED accents, not a free color. Each name carries a curated
// pair per scheme, because one hex cannot serve both: a colour // pair per scheme, because one hex cannot serve both: a color
// legible on the dark ground is usually illegible on the light one. // legible on the dark ground is usually illegible on the light one.
// The palette and its measured contrast live in config/Theme.qml, // The palette and its measured contrast live in config/Theme.qml,
// which is also what stops this list drifting from what is drawn. // which is also what stops this list drifting from what is drawn.
@@ -998,7 +998,7 @@ Singleton {
}, },
// ── Application titlebars ────────────────────────────────────────── // ── Application titlebars ──────────────────────────────────────────
// These affect applications that honour GNOME's window preferences. // These affect applications that honor GNOME's window preferences.
// Hyprland itself has no server-side titlebar buttons, so minimize is // Hyprland itself has no server-side titlebar buttons, so minimize is
// deliberately absent rather than presented as a switch that lies. // deliberately absent rather than presented as a switch that lies.
{ {
@@ -1061,7 +1061,7 @@ Singleton {
// ── Which GPU the vitals readout tracks ───────────────────────────── // ── Which GPU the vitals readout tracks ─────────────────────────────
// A sysfs path rather than a card number, because the number is neither // A sysfs path rather than a card number, because the number is neither
// stable across machines nor meaningful. Constrained to the one shape // stable across machines nor meaningful. Constrained to the one shape
// that can be read for utilisation; VitalsWidget hides itself when the // that can be read for utilization; VitalsWidget hides itself when the
// path is unreadable, so a stale value degrades to no readout rather // path is unreadable, so a stale value degrades to no readout rather
// than a wrong one. // than a wrong one.
{ {
@@ -1077,7 +1077,7 @@ Singleton {
// Coordinates rather than a place name, because that is what Open-Meteo // Coordinates rather than a place name, because that is what Open-Meteo
// takes and it needs no API key. weatherLocation is only the label shown // takes and it needs no API key. weatherLocation is only the label shown
// in the UI; it is never sent anywhere, so it can say whatever makes the // in the UI; it is never sent anywhere, so it can say whatever makes the
// reading recognisable. // reading recognizable.
{ {
key: "weatherLatitude", type: "real", def: 27.7375, min: -90, max: 90, step: 0.0001, key: "weatherLatitude", type: "real", def: 27.7375, min: -90, max: 90, step: 0.0001,
group: "weather", internal: true, group: "weather", internal: true,
@@ -1212,7 +1212,7 @@ Singleton {
// ── Display configuration ─────────────────────────────────────────── // ── Display configuration ───────────────────────────────────────────
// { "<output>": { mode, scale, transform, x, y, primary } }, applied by // { "<output>": { mode, scale, transform, x, y, primary } }, applied by
// hypr/monitors.lua on top of the shipped values. Colour management and // hypr/monitors.lua on top of the shipped values. Color management and
// bit depth are deliberately not here: those carry a documented // bit depth are deliberately not here: those carry a documented
// screencopy tradeoff that a settings page cannot explain at the moment // screencopy tradeoff that a settings page cannot explain at the moment
// you would be changing it. // you would be changing it.
@@ -1325,7 +1325,7 @@ Singleton {
case "string": { case "string": {
const text = typeof value === "string" ? value : String(value); const text = typeof value === "string" ? value : String(value);
// A constrained string is rejected rather than sanitised. Several // A constrained string is rejected rather than sanitised. Several
// of these are serialised into an hl.config payload, and quietly // of these are serialized into an hl.config payload, and quietly
// stripping characters would turn a typo into a different setting // stripping characters would turn a typo into a different setting
// instead of an error the user can see. // instead of an error the user can see.
if (entry.pattern && !new RegExp(entry.pattern).test(text)) if (entry.pattern && !new RegExp(entry.pattern).test(text))
+1 -1
View File
@@ -62,7 +62,7 @@ Singleton {
readonly property int focusDurationMinutes: DesktopPreferences.get("focusDurationMinutes") readonly property int focusDurationMinutes: DesktopPreferences.get("focusDurationMinutes")
// ── Dock ──────────────────────────────────────────────────────────────── // ── Dock ────────────────────────────────────────────────────────────────
// Pinned apps, in order, taken from the GNOME dash favourites. // Pinned apps, in order, taken from the GNOME dash favorites.
readonly property var dockPinned: DesktopPreferences.get("dockPinned") readonly property var dockPinned: DesktopPreferences.get("dockPinned")
// Dash-to-Dock was set to intellihide against all windows: the dock hides // Dash-to-Dock was set to intellihide against all windows: the dock hides
+11 -11
View File
@@ -1,9 +1,9 @@
pragma Singleton pragma Singleton
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
// Tokyo Night Moon — the single source of truth for every colour, radius, // Tokyo Night Moon — the single source of truth for every color, radius,
// duration and font in the shell. Nothing below this file should hardcode a // duration and font in the shell. Nothing below this file should hardcode a
// colour; if you find yourself writing "#" outside this file, add a token here. // color; if you find yourself writing "#" outside this file, add a token here.
// //
// The bar keeps GNOME's 36px information density but sits directly on the // The bar keeps GNOME's 36px information density but sits directly on the
// desktop, edge to edge. The dock retains the former glass vocabulary: bottom, // desktop, edge to edge. The dock retains the former glass vocabulary: bottom,
@@ -18,7 +18,7 @@ import QtQuick
Singleton { Singleton {
id: root id: root
// ── Colour scheme ─────────────────────────────────────────────────────── // ── Color scheme ───────────────────────────────────────────────────────
// Tokyo Night ships an official light variant (Day), so light mode is that // Tokyo Night ships an official light variant (Day), so light mode is that
// rather than a palette invented to merely not be dark. The two share the // rather than a palette invented to merely not be dark. The two share the
// same hues at different lightness, which is what lets the Prism identity // same hues at different lightness, which is what lets the Prism identity
@@ -46,12 +46,12 @@ Singleton {
// //
// `accent` is the primary and carries every state meaning (focused, active, // `accent` is the primary and carries every state meaning (focused, active,
// on). `accentSecondary` never appears alone, only as the far end of a // on). `accentSecondary` never appears alone, only as the far end of a
// gradient. That restraint is the whole point: the two colours meeting is // gradient. That restraint is the whole point: the two colors meeting is
// the signature, so the second colour stops being special the moment it is // the signature, so the second color stops being special the moment it is
// used as a flat fill. // used as a flat fill.
// //
// NAMED accents rather than a free colour. Each name carries a curated // NAMED accents rather than a free color. Each name carries a curated
// triple per scheme, because an arbitrary hex cannot work in both: a colour // triple per scheme, because an arbitrary hex cannot work in both: a color
// legible on the Moon background is usually illegible on the Day one, and a // legible on the Moon background is usually illegible on the Day one, and a
// picker that lets someone choose an unreadable desktop is not a feature. // picker that lets someone choose an unreadable desktop is not a feature.
// Every pair below measures at least 3:1 against the ground it sits on. // Every pair below measures at least 3:1 against the ground it sits on.
@@ -72,7 +72,7 @@ Singleton {
// Falls back to blue for an unknown name, so a settings file written by a // Falls back to blue for an unknown name, so a settings file written by a
// newer Panama -- or edited by hand -- degrades to the shipped identity // newer Panama -- or edited by hand -- degrades to the shipped identity
// rather than to an undefined colour. // rather than to an undefined color.
readonly property var accentPair: root.accents[DesktopPreferences.get("accentName")] ?? root.accents["blue"] readonly property var accentPair: root.accents[DesktopPreferences.get("accentName")] ?? root.accents["blue"]
readonly property color accent: root.dark ? root.accentPair.dark : root.accentPair.light readonly property color accent: root.dark ? root.accentPair.dark : root.accentPair.light
@@ -136,7 +136,7 @@ Singleton {
// Nerd Font, used ONLY to draw icon glyphs — never for text. It is the // Nerd Font, used ONLY to draw icon glyphs — never for text. It is the
// pragmatic alternative to freedesktop symbolic icons, which ship with a // pragmatic alternative to freedesktop symbolic icons, which ship with a
// hardcoded dark fill Qt will not recolour (see widgets/ThemedIcon.qml). // hardcoded dark fill Qt will not recolor (see widgets/ThemedIcon.qml).
readonly property string fontMono: DesktopPreferences.get("iconFont") readonly property string fontMono: DesktopPreferences.get("iconFont")
// Apply to any text whose digits change in place — clocks, percentages, // Apply to any text whose digits change in place — clocks, percentages,
@@ -180,7 +180,7 @@ Singleton {
} }
// Blend two tokens — used for hover/pressed states so we never invent a // Blend two tokens — used for hover/pressed states so we never invent a
// colour that isn't derived from the palette. // color that isn't derived from the palette.
function mix(a: color, b: color, ratio: real): color { function mix(a: color, b: color, ratio: real): color {
return Qt.rgba(a.r + (b.r - a.r) * ratio, a.g + (b.g - a.g) * ratio, a.b + (b.b - a.b) * ratio, a.a + (b.a - a.a) * ratio); return Qt.rgba(a.r + (b.r - a.r) * ratio, a.g + (b.g - a.g) * ratio, a.b + (b.b - a.b) * ratio, a.a + (b.a - a.a) * ratio);
} }
@@ -191,7 +191,7 @@ Singleton {
// the active workspace pill and slider fills. // the active workspace pill and slider fills.
// //
// Where the gradient runs left-to-right the blue leads, because the eye // Where the gradient runs left-to-right the blue leads, because the eye
// reads left first and blue is the colour that carries meaning. // reads left first and blue is the color that carries meaning.
readonly property real prismStart: 0.22 readonly property real prismStart: 0.22
readonly property real prismEnd: 0.78 readonly property real prismEnd: 0.78
+1 -1
View File
@@ -83,7 +83,7 @@ ShellRoot {
} }
function refreshIdentityFixture(): string { function refreshIdentityFixture(): string {
const modes = Displays.normaliseModes([ const modes = Displays.normalizeModes([
"[email protected]", "[email protected]",
"[email protected]" "[email protected]"
]); ]);
+1 -1
View File
@@ -3,7 +3,7 @@
// //
// Fixture steps rather than the real list: the real one is empty until the // Fixture steps rather than the real list: the real one is empty until the
// first breaking schema change, and a mechanism that has never been run against // first breaking schema change, and a mechanism that has never been run against
// a failing step is not one to discover the behaviour of during an upgrade. // a failing step is not one to discover the behavior of during an upgrade.
import Quickshell import Quickshell
import QtQuick import QtQuick
+2 -2
View File
@@ -10,7 +10,7 @@
// Bar {} // Bar {}
// } // }
// //
// Layout mirrors GNOME's: workspaces + vitals left, clock + weather centre, // Layout mirrors GNOME's: workspaces + vitals left, clock + weather center,
// media + tray + status cluster right. // media + tray + status cluster right.
import Quickshell import Quickshell
@@ -83,7 +83,7 @@ PanelWindow {
} }
} }
// ── Centre ────────────────────────────────────────────────────────── // ── Center ──────────────────────────────────────────────────────────
Row { Row {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
+2 -2
View File
@@ -1,4 +1,4 @@
// The centre clock, kept in the same place and the same format GNOME had it: // The center clock, kept in the same place and the same format GNOME had it:
// weekday, date, 12-hour time with seconds. Clicking it opens the calendar, // weekday, date, 12-hour time with seconds. Clicking it opens the calendar,
// exactly like GNOME's date menu. // exactly like GNOME's date menu.
@@ -35,7 +35,7 @@ Pill {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: Qt.formatDateTime(clock.date, root.format) text: Qt.formatDateTime(clock.date, root.format)
font.family: Theme.fontFamily font.family: Theme.fontFamily
// Seconds tick once a second in the centre of the bar; without tabular // Seconds tick once a second in the center of the bar; without tabular
// figures the whole clock shifts sideways on every digit change. // figures the whole clock shifts sideways on every digit change.
font.features: Theme.tabularFigures font.features: Theme.tabularFigures
font.pixelSize: Theme.fontSize font.pixelSize: Theme.fontSize
@@ -62,7 +62,7 @@ Pill {
if (!root.wifiNetwork) if (!root.wifiNetwork)
return "\u{F092D}"; // md-wifi_strength_off return "\u{F092D}"; // md-wifi_strength_off
// NetworkManager reports strength 0-100; some backends normalise to // NetworkManager reports strength 0-100; some backends normalize to
// 0-1, so accept either rather than showing one bar forever. // 0-1, so accept either rather than showing one bar forever.
const raw = root.wifiNetwork.signalStrength ?? 0; const raw = root.wifiNetwork.signalStrength ?? 0;
const strength = raw <= 1 ? raw * 100 : raw; const strength = raw <= 1 ? raw * 100 : raw;
@@ -1,5 +1,5 @@
// One glyph + percentage pair inside VitalsWidget. Split out so the three // One glyph + percentage pair inside VitalsWidget. Split out so the three
// fields cannot drift apart in spacing, width or colour thresholds. // fields cannot drift apart in spacing, width or color thresholds.
import QtQuick import QtQuick
import qs.config import qs.config
@@ -2,7 +2,7 @@
// Vitals extension was configured with (_processor_usage_, _memory_usage_, // Vitals extension was configured with (_processor_usage_, _memory_usage_,
// _gpu#1_usage_). // _gpu#1_usage_).
// //
// Values are colour-coded rather than graphed: at bar size a number you can // Values are color-coded rather than graphed: at bar size a number you can
// glance at beats a sparkline you have to squint at. // glance at beats a sparkline you have to squint at.
import QtQuick import QtQuick
@@ -36,7 +36,7 @@ Item {
implicitWidth: row.implicitWidth + 12 implicitWidth: row.implicitWidth + 12
// Full bar height makes (0, 0) part of the wheel target. The dots stay // Full bar height makes (0, 0) part of the wheel target. The dots stay
// optically centred by the row and active-pill geometry below. // optically centered by the row and active-pill geometry below.
implicitHeight: Theme.barHeight implicitHeight: Theme.barHeight
// Scroll over the padding either side of the row, not just over a dot. // Scroll over the padding either side of the row, not just over a dot.
@@ -59,7 +59,7 @@ Item {
border.width: 0 // QTBUG-137166: rounded rects can lose their corners border.width: 0 // QTBUG-137166: rounded rects can lose their corners
// The prism, as a fill rather than a hairline. This is the one element // The prism, as a fill rather than a hairline. This is the one element
// in the bar that is always coloured, so it is where the pair belongs. // in the bar that is always colored, so it is where the pair belongs.
gradient: Gradient { gradient: Gradient {
orientation: Gradient.Horizontal orientation: Gradient.Horizontal
GradientStop { GradientStop {
@@ -1,7 +1,7 @@
// The floating control panel at the bottom of the capture overlay. // The floating control panel at the bottom of the capture overlay.
// //
// Layout is a deliberate copy of GNOME 42's screenshot UI: the area segmented // Layout is a deliberate copy of GNOME 42's screenshot UI: the area segmented
// control on top, and below it the shutter dead-centre with "Show Pointer" on // control on top, and below it the shutter dead-center with "Show Pointer" on
// the left and the screenshot/screencast switch on the right. // the left and the screenshot/screencast switch on the right.
import QtQuick import QtQuick
@@ -13,7 +13,7 @@ Rectangle {
signal captureRequested signal captureRequested
// Set by the overlay so the shutter can grey out when Window mode has // Set by the overlay so the shutter can gray out when Window mode has
// nothing pickable (no Hyprland, or an empty workspace). // nothing pickable (no Hyprland, or an empty workspace).
property bool captureEnabled: true property bool captureEnabled: true
@@ -1,7 +1,7 @@
// One segment of the Screen / Window / Selection control. // One segment of the Screen / Window / Selection control.
// //
// Icons are Nerd Font glyphs rather than freedesktop icons on purpose: the // Icons are Nerd Font glyphs rather than freedesktop icons on purpose: the
// Adwaita "symbolic" SVGs are flat black and Qt, unlike GTK, will not recolour // Adwaita "symbolic" SVGs are flat black and Qt, unlike GTK, will not recolor
// them, so they'd be invisible on this dark overlay. // them, so they'd be invisible on this dark overlay.
import QtQuick import QtQuick
@@ -1,6 +1,6 @@
// Selection mode: a GNOME-style rubber band over the frozen screen. // Selection mode: a GNOME-style rubber band over the frozen screen.
// //
// Behaviours copied from GNOME 42: the overlay opens with a sensible centred // Behaviors copied from GNOME 42: the overlay opens with a sensible centered
// selection already in place (so the shutter is immediately useful), dragging // selection already in place (so the shutter is immediately useful), dragging
// inside moves it, dragging a corner resizes from the opposite corner, and // inside moves it, dragging a corner resizes from the opposite corner, and
// dragging anywhere else starts a fresh band. // dragging anywhere else starts a fresh band.
@@ -52,7 +52,7 @@ Item {
root.resetSelection(); root.resetSelection();
} }
// Centred, roughly the middle half of the screen. // Centered, roughly the middle half of the screen.
function resetSelection(): void { function resetSelection(): void {
if (root.width <= 0 || root.height <= 0) if (root.width <= 0 || root.height <= 0)
return; return;
@@ -1,11 +1,11 @@
// The month grid from GNOME's date menu. // The month grid from GNOME's date menu.
// //
// Six week rows always, so the grid height never changes as you page through // Six week rows always, so the grid height never changes as you page through
// months and the panel below it never jumps. Days belonging to the neighbouring // months and the panel below it never jumps. Days belonging to the neighboring
// months fill the edges, dimmed. Today wears the accent. // months fill the edges, dimmed. Today wears the accent.
// //
// Days become real navigation targets now that Evolution Data Server backs the // Days become real navigation targets now that Evolution Data Server backs the
// panel. Up to three source-coloured markers show whether a date has events. // panel. Up to three source-colored markers show whether a date has events.
import Quickshell import Quickshell
import QtQuick import QtQuick
@@ -134,7 +134,7 @@ Item {
} }
} }
// Centred on the row rather than between the pill and the arrows, // Centered on the row rather than between the pill and the arrows,
// so it does not shift sideways when the pill appears. // so it does not shift sideways when the pill appears.
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
@@ -1,12 +1,12 @@
// GNOME's date/time menu: calendar, ongoing activity and messages in one panel, // GNOME's date/time menu: calendar, ongoing activity and messages in one panel,
// dropping from the top centre because that is where GNOME put it and the // dropping from the top center because that is where GNOME put it and the
// muscle memory is the whole point. // muscle memory is the whole point.
// //
// A layer-shell surface rather than a PopupWindow because the inline reply // A layer-shell surface rather than a PopupWindow because the inline reply
// fields have to be typeable, and because the clock it hangs from is owned by a // fields have to be typeable, and because the clock it hangs from is owned by a
// different module. // different module.
// //
// Layer-shell centres a surface on whichever axis it is not anchored to, so // Layer-shell centers a surface on whichever axis it is not anchored to, so
// anchoring only `top` is what puts it in the middle. // anchoring only `top` is what puts it in the middle.
import QtQuick import QtQuick
@@ -87,7 +87,7 @@ PanelWindow {
} }
// Grows out of the top edge it is anchored to, rather than out of its // Grows out of the top edge it is anchored to, rather than out of its
// own centre — the panel should look like it came from the bar. // own center — the panel should look like it came from the bar.
transform: Scale { transform: Scale {
id: openScale id: openScale
origin.x: surface.width / 2 origin.x: surface.width / 2
@@ -172,14 +172,14 @@ Rectangle {
} }
} }
// Centred on the hovered item, clamped inside the dock. Reads the item's // Centered on the hovered item, clamped inside the dock. Reads the item's
// x directly (rather than mapToItem) so the binding re-evaluates when // x directly (rather than mapToItem) so the binding re-evaluates when
// the row reflows. // the row reflows.
x: { x: {
if (!root.hoveredItem) if (!root.hoveredItem)
return 0; return 0;
const centre = row.x + root.hoveredItem.x + root.hoveredItem.width / 2; const center = row.x + root.hoveredItem.x + root.hoveredItem.width / 2;
return Math.max(4, Math.min(root.width - width - 4, centre - width / 2)); return Math.max(4, Math.min(root.width - width - 4, center - width / 2));
} }
y: -height - 8 y: -height - 8
@@ -1,5 +1,5 @@
// One dock icon: the app, its running-window dots, hover scale and click // One dock icon: the app, its running-window dots, hover scale and click
// behaviour. Reproduces Dash-to-Dock's item: 48px icon, up to four dots // behavior. Reproduces Dash-to-Dock's item: 48px icon, up to four dots
// underneath, grow-on-hover, click to focus / cycle / launch. // underneath, grow-on-hover, click to focus / cycle / launch.
import Quickshell import Quickshell
@@ -72,7 +72,7 @@ Item {
} }
// Last resort for an app with no resolvable icon: an initial, which is // Last resort for an app with no resolvable icon: an initial, which is
// still recognisable, unlike the theme's broken-icon placeholder. // still recognizable, unlike the theme's broken-icon placeholder.
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
visible: root.iconSource === "" visible: root.iconSource === ""
@@ -155,7 +155,7 @@ Item {
return; return;
// Quickshell reports the address without the 0x prefix Hyprland's // Quickshell reports the address without the 0x prefix Hyprland's
// window selector expects; normalise so either form works. // window selector expects; normalize so either form works.
if (next.address) { if (next.address) {
const addr = next.address.startsWith("0x") ? next.address : "0x" + next.address; const addr = next.address.startsWith("0x") ? next.address : "0x" + next.address;
Hyprland.dispatch(`hl.dsp.focus({ window = "address:${addr}" })`); Hyprland.dispatch(`hl.dsp.focus({ window = "address:${addr}" })`);
@@ -1,7 +1,7 @@
// One notification, rendered GNOME-style: app icon, app name, summary, body, // One notification, rendered GNOME-style: app icon, app name, summary, body,
// optional image, action buttons and an inline reply box. // optional image, action buttons and an inline reply box.
// //
// Shared by the toasts and the notification centre so a notification looks the // Shared by the toasts and the notification center so a notification looks the
// same wherever you meet it. // same wherever you meet it.
// //
// IconButton/ThemedIcon come from the quicksettings module — they belong in // IconButton/ThemedIcon come from the quicksettings module — they belong in
@@ -21,7 +21,7 @@ Rectangle {
required property var notification required property var notification
// The centre renders a denser card than a toast banner. // The center renders a denser card than a toast banner.
property bool compact: false property bool compact: false
signal dismissed signal dismissed
@@ -43,7 +43,7 @@ Rectangle {
} }
} }
// Critical notifications get a red edge rather than a colour wash, so the // Critical notifications get a red edge rather than a color wash, so the
// text contrast never changes. // text contrast never changes.
Rectangle { Rectangle {
anchors.left: parent.left anchors.left: parent.left
@@ -1,6 +1,6 @@
// One workspace in the overview: a proportional card holding live thumbnails // One workspace in the overview: a proportional card holding live thumbnails
// of its windows, labelled underneath. The focused workspace is outlined in the // of its windows, labeled underneath. The focused workspace is outlined in the
// accent colour, as GNOME outlines the current workspace. // accent color, as GNOME outlines the current workspace.
// //
// A card with a null `workspace` is the trailing "new workspace" affordance // A card with a null `workspace` is the trailing "new workspace" affordance
// that dynamic-workspace setups (GNOME's, and Hyprland's) always show. // that dynamic-workspace setups (GNOME's, and Hyprland's) always show.
@@ -114,7 +114,7 @@ Item {
width: parent.width width: parent.width
// BlueZ reports a plain freedesktop name ("audio-headphones"); // BlueZ reports a plain freedesktop name ("audio-headphones");
// the symbolic variant is the one that can be recoloured. // the symbolic variant is the one that can be recolored.
icon: deviceRow.modelData.icon !== "" ? deviceRow.modelData.icon + "-symbolic" : "bluetooth-symbolic" icon: deviceRow.modelData.icon !== "" ? deviceRow.modelData.icon + "-symbolic" : "bluetooth-symbolic"
iconFallback: "bluetooth-symbolic" iconFallback: "bluetooth-symbolic"
label: deviceRow.modelData.name || deviceRow.modelData.address label: deviceRow.modelData.name || deviceRow.modelData.address
@@ -29,7 +29,7 @@ Item {
property real backlightValue: 0 property real backlightValue: 0
readonly property int rowCount: (root.backlightAvailable ? 1 : 0) + Brightness.displays.length readonly property int rowCount: (root.backlightAvailable ? 1 : 0) + Brightness.displays.length
readonly property bool labelled: root.rowCount > 1 readonly property bool labeled: root.rowCount > 1
visible: root.rowCount > 0 visible: root.rowCount > 0
implicitHeight: rows.implicitHeight implicitHeight: rows.implicitHeight
@@ -89,7 +89,7 @@ Item {
// Hyprland already knows what each output is called, so the // Hyprland already knows what each output is called, so the
// name comes from there rather than from a second source that // name comes from there rather than from a second source that
// could disagree with the Displays page. The connector is the // could disagree with the Displays page. The connector is the
// fallback, so a display is never an unlabelled slider. // fallback, so a display is never an unlabeled slider.
label: Displays.monitorNamed(modelData.connector)?.description || modelData.connector label: Displays.monitorNamed(modelData.connector)?.description || modelData.connector
value: modelData.value / 100 value: modelData.value / 100
onMoved: v => Brightness.set(modelData.bus, Math.round(v * 100)) onMoved: v => Brightness.set(modelData.bus, Math.round(v * 100))
@@ -113,7 +113,7 @@ Item {
anchors.leftMargin: 6 anchors.leftMargin: 6
anchors.rightMargin: 6 anchors.rightMargin: 6
anchors.top: parent.top anchors.top: parent.top
visible: root.labelled visible: root.labeled
height: visible ? implicitHeight + 2 : 0 height: visible ? implicitHeight + 2 : 0
text: row.label text: row.label
color: Theme.fgDim color: Theme.fgDim
@@ -124,7 +124,7 @@ Item {
// The slider and its glyph share one strip so the two stay aligned // The slider and its glyph share one strip so the two stay aligned
// whether or not a caption sits above them. Anchoring the glyph to // whether or not a caption sits above them. Anchoring the glyph to
// both a caption and a centre line instead would conflict, and an // both a caption and a center line instead would conflict, and an
// anchor set to undefined is not released. // anchor set to undefined is not released.
Item { Item {
id: control id: control
@@ -134,7 +134,7 @@ Item {
height: 32 height: 32
// ValueSlider draws its own leading icon, but symbolic icons need // ValueSlider draws its own leading icon, but symbolic icons need
// recolouring to be visible — see ThemedIcon. // recoloring to be visible — see ThemedIcon.
ThemedIcon { ThemedIcon {
id: glyph id: glyph
anchors.left: parent.left anchors.left: parent.left
@@ -95,7 +95,7 @@ Rectangle {
} }
} }
// Full-height rather than vertically centred so slot contents can anchor // Full-height rather than vertically centered so slot contents can anchor
// their own verticalCenter to it without a size loop. // their own verticalCenter to it without a size loop.
Row { Row {
id: trailingRow id: trailingRow
@@ -2,10 +2,10 @@
// //
// Each swatch is drawn as the GRADIENT it will actually produce, not a flat // Each swatch is drawn as the GRADIENT it will actually produce, not a flat
// dot, because the gradient is the thing being chosen -- the focused window // dot, because the gradient is the thing being chosen -- the focused window
// border, the bar hairline and every active state are the two colours meeting. // border, the bar hairline and every active state are the two colors meeting.
// A row of flat circles would misrepresent all of them. // A row of flat circles would misrepresent all of them.
// //
// Named accents rather than a colour wheel: each name carries a curated pair // Named accents rather than a color wheel: each name carries a curated pair
// per scheme, so every choice stays legible in both light and dark. See // per scheme, so every choice stays legible in both light and dark. See
// config/Theme.qml for the palette and the reasoning. // config/Theme.qml for the palette and the reasoning.
@@ -46,7 +46,7 @@ Flow {
// The hit target is the whole swatch+label unit, not just the // The hit target is the whole swatch+label unit, not just the
// 46px circle: the label exists specifically so someone with a // 46px circle: the label exists specifically so someone with a
// colour vision deficiency can identify an accent without it, and // color vision deficiency can identify an accent without it, and
// a label that cannot itself be tapped defeats that. // a label that cannot itself be tapped defeats that.
HoverHandler { HoverHandler {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -66,7 +66,7 @@ Flow {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: "transparent" color: "transparent"
// The ring sits outside the gradient rather than over it, so a // The ring sits outside the gradient rather than over it, so a
// selected swatch still shows its true colours. // selected swatch still shows its true colors.
border.width: entry.selected ? 2 : 1 border.width: entry.selected ? 2 : 1
border.color: entry.selected ? Theme.fg : Theme.alpha(Theme.fg, 0.14) border.color: entry.selected ? Theme.fg : Theme.alpha(Theme.fg, 0.14)
@@ -83,8 +83,8 @@ Flow {
} }
} }
// Always shown, not a tooltip. Telling swatches apart by colour is // Always shown, not a tooltip. Telling swatches apart by color is
// exactly what someone with a colour vision deficiency cannot do, // exactly what someone with a color vision deficiency cannot do,
// and it is the reason the palette is named rather than freeform -- // and it is the reason the palette is named rather than freeform --
// hiding the names behind a hover would waste that. // hiding the names behind a hover would waste that.
Text { Text {
@@ -101,7 +101,7 @@ SettingsPage {
} }
SettingsCard { SettingsCard {
title: "Colour scheme" title: "Color scheme"
subtitle: ColorScheme.lastError !== "" subtitle: ColorScheme.lastError !== ""
? ColorScheme.lastError ? ColorScheme.lastError
: "Light is Tokyo Night Day, the official light variant — the same hues at a different lightness, so the blue-into-orchid signature survives the switch. Applications and window borders follow." : "Light is Tokyo Night Day, the official light variant — the same hues at a different lightness, so the blue-into-orchid signature survives the switch. Applications and window borders follow."
@@ -7,7 +7,7 @@
// because that genuinely is a Panama preference. // because that genuinely is a Panama preference.
// //
// Changing the timezone or network time needs privilege. timedatectl asks // Changing the timezone or network time needs privilege. timedatectl asks
// polkit, and a cancelled dialog surfaces as an error rather than as a value // polkit, and a canceled dialog surfaces as an error rather than as a value
// that appears to have been accepted. // that appears to have been accepted.
import QtQuick import QtQuick
@@ -33,7 +33,7 @@ SettingsPage {
SettingRow { SettingRow {
label: "Set automatically" label: "Set automatically"
detail: DateTime.ntpEnabled detail: DateTime.ntpEnabled
? (DateTime.ntpSynchronised ? "Synchronised with a time server" : "Waiting to synchronise") ? (DateTime.ntpSynchronized ? "Synchronized with a time server" : "Waiting to synchronize")
: "The clock is set by hand" : "The clock is set by hand"
controlWidth: 48 controlWidth: 48
divider: false divider: false
@@ -50,7 +50,7 @@ Rectangle {
// Stands in for the wallpaper. Static: an animated gradient here would // Stands in for the wallpaper. Static: an animated gradient here would
// repaint forever behind a settings page. // repaint forever behind a settings page.
// Follows the colour scheme. A preview that stays dark while the shell // Follows the color scheme. A preview that stays dark while the shell
// around it is light does not read as "your desktop" -- it reads as a // around it is light does not read as "your desktop" -- it reads as a
// screenshot of someone else's. // screenshot of someone else's.
gradient: Gradient { gradient: Gradient {
@@ -129,7 +129,7 @@ Rectangle {
required property bool focused required property bool focused
// The gradient border is drawn as a filled rounded rect with the window // The gradient border is drawn as a filled rounded rect with the window
// body inset on top of it: QML's Rectangle border takes a colour, not a // body inset on top of it: QML's Rectangle border takes a color, not a
// gradient, and the prism border is the whole signature here. // gradient, and the prism border is the whole signature here.
Rectangle { Rectangle {
id: frame id: frame
@@ -440,9 +440,9 @@ SettingsPage {
ActionRow { ActionRow {
objectName: "health-fedora-handoff:color" objectName: "health-fedora-handoff:color"
label: "Colour profiles" label: "Color profiles"
detail: "Assigning an ICC profile here has no visible effect in this session: the colord daemon that loads it onto a display isn't running under Hyprland" detail: "Assigning an ICC profile here has no visible effect in this session: the colord daemon that loads it onto a display isn't running under Hyprland"
action: "Open colour" action: "Open color"
onTriggered: SystemSettings.openGnomePanel("color") onTriggered: SystemSettings.openGnomePanel("color")
} }
@@ -20,8 +20,8 @@ SettingsPage {
title: "Mouse & Touchpad" title: "Mouse & Touchpad"
lede: InputDevices.hasTouchpad lede: InputDevices.hasTouchpad
? "Pointer behaviour for your mouse and touchpad." ? "Pointer behavior for your mouse and touchpad."
: "Pointer behaviour. Touchpad settings appear when a touchpad is attached." : "Pointer behavior. Touchpad settings appear when a touchpad is attached."
SettingsCard { SettingsCard {
title: "Mouse" title: "Mouse"
@@ -52,7 +52,7 @@ SettingsPage {
} }
SettingsCard { SettingsCard {
title: "Idle behaviour" title: "Idle behavior"
subtitle: IdleLock.managed subtitle: IdleLock.managed
? "Idle timings are managed here. Changes take effect immediately." ? "Idle timings are managed here. Changes take effect immediately."
: "hypridle is running its shipped configuration. Turn on management below to make these adjustable." : "hypridle is running its shipped configuration. Turn on management below to make these adjustable."
@@ -1,7 +1,7 @@
# Settings # Settings
The control centre for everything Panama owns. Anything the system owns — The control center for everything Panama owns. Anything the system owns —
hardware, accounts, printers — is delegated to GNOME Settings and labelled as hardware, accounts, printers — is delegated to GNOME Settings and labeled as
such rather than half-reimplemented. such rather than half-reimplemented.
## System Health ## System Health
@@ -28,7 +28,7 @@ package, runs `sudo`, deletes user data, or repairs a service Panama does not
own. A repair remains degraded until a fresh scan observes recovery. own. A repair remains degraded until a fresh scan observes recovery.
The final card is the ownership boundary. Network configuration and the exact The final card is the ownership boundary. Network configuration and the exact
Users, Sharing, Colour profiles, and Digital wellbeing handoffs open GNOME Users, Sharing, Color profiles, and Digital wellbeing handoffs open GNOME
Settings because Fedora's system services own those areas. Settings because Fedora's system services own those areas.
## Adding a setting ## Adding a setting
@@ -64,7 +64,7 @@ file.
Every preference has **one primary page**, derived from its schema `group` and Every preference has **one primary page**, derived from its schema `group` and
the route in `services/SettingsSearch.qml`. Search results always open that the route in `services/SettingsSearch.qml`. Search results always open that
owner. A control may appear on a second page only when the same adaptation is owner. A control may appear on a second page only when the same adaptation is
part of another established mental model; otherwise use a labelled handoff to part of another established mental model; otherwise use a labeled handoff to
the owner instead of duplicating it. the owner instead of duplicating it.
### Intentional mirrors ### Intentional mirrors
@@ -72,7 +72,7 @@ the owner instead of duplicating it.
| Setting | Primary page | Mirror | Why the mirror earns its place | | Setting | Primary page | Mirror | Why the mirror earns its place |
|---|---|---|---| |---|---|---|---|
| `animationsEnabled` | Appearance | Accessibility | Reduced motion belongs both to visual polish and motion accessibility. | | `animationsEnabled` | Appearance | Accessibility | Reduced motion belongs both to visual polish and motion accessibility. |
| `cursorInactiveTimeout` | Mouse | Accessibility | Pointer visibility is configured with pointer behaviour but affects motor and visual access. | | `cursorInactiveTimeout` | Mouse | Accessibility | Pointer visibility is configured with pointer behavior but affects motor and visual access. |
| `cursorSize` | Accessibility | Mouse | Large cursors are an accessibility adaptation that users also look for beside pointer controls. | | `cursorSize` | Accessibility | Mouse | Large cursors are an accessibility adaptation that users also look for beside pointer controls. |
| `inactiveOpacity` | Appearance | Accessibility | Window translucency is an appearance choice with a direct readability impact. | | `inactiveOpacity` | Appearance | Accessibility | Window translucency is an appearance choice with a direct readability impact. |
| `lockMinutes` | Power | Privacy | Idle timing owns the mechanism; privacy owns the expectation that the unattended desktop locks. | | `lockMinutes` | Power | Privacy | Idle timing owns the mechanism; privacy owns the expectation that the unattended desktop locks. |
@@ -93,7 +93,7 @@ Mirrors must remain the same schema-backed control, never a second preference
or a copied default. Additions to this table require a concrete discoverability or a copied default. Additions to this table require a concrete discoverability
reason and an update to `tests/quickshell/settings-ownership-contract.sh`. reason and an update to `tests/quickshell/settings-ownership-contract.sh`.
Window border colour follows the same ownership rule. The inactive border is a Window border color follows the same ownership rule. The inactive border is a
**scheme-relative role** owned by `ColorScheme.qml`: it changes only to retain **scheme-relative role** owned by `ColorScheme.qml`: it changes only to retain
neutral contrast in light and dark modes. The focused Prism border is the neutral contrast in light and dark modes. The focused Prism border is the
**accent role**, driven by the chosen `accentName` and also owned by **accent role**, driven by the chosen `accentName` and also owned by
@@ -165,7 +165,7 @@ file with `qs -p`, and discover its PID from the exact Config path in
| File | Holds | | File | Holds |
|---|---| |---|---|
| `~/.config/panama/settings.json` | Everything in the schema. Read by the shell *and* by `hypr/prefs.lua` | | `~/.config/panama/settings.json` | Everything in the schema. Read by the shell *and* by `hypr/prefs.lua` |
| `$XDG_STATE_HOME/panama/panama-home.json` | Home accessory favourites and aliases | | `$XDG_STATE_HOME/panama/panama-home.json` | Home accessory favorites and aliases |
| `$XDG_STATE_HOME/panama/backups/` | Settings snapshots | | `$XDG_STATE_HOME/panama/backups/` | Settings snapshots |
| `$XDG_STATE_HOME/panama/hypridle.conf` | Generated idle config | | `$XDG_STATE_HOME/panama/hypridle.conf` | Generated idle config |
| `$XDG_STATE_HOME/panama/hyprlock.conf` | Generated lock-screen appearance | | `$XDG_STATE_HOME/panama/hyprlock.conf` | Generated lock-screen appearance |
@@ -19,7 +19,7 @@ FocusScope {
id: root id: root
signal captured(string chord) signal captured(string chord)
signal cancelled signal canceled
property string message: "" property string message: ""
@@ -97,7 +97,7 @@ FocusScope {
event.accepted = true; event.accepted = true;
if (event.key === 0x01000000) { // Escape if (event.key === 0x01000000) { // Escape
root.cancelled(); root.canceled();
return; return;
} }
if (root.isModifierOnly(event.key)) if (root.isModifierOnly(event.key))
@@ -127,6 +127,6 @@ FocusScope {
onActiveFocusChanged: { onActiveFocusChanged: {
if (!activeFocus) if (!activeFocus)
root.cancelled(); root.canceled();
} }
} }
@@ -7,7 +7,7 @@
// description in hypr/keybinds.lua, and this page just groups and renders them. // description in hypr/keybinds.lua, and this page just groups and renders them.
// //
// The hardware settings above the list are real controls. Keyboard layout, // The hardware settings above the list are real controls. Keyboard layout,
// repeat behaviour, and pointer response are Hyprland's, so Panama owns them; // repeat behavior, and pointer response are Hyprland's, so Panama owns them;
// device-specific configuration stays with GNOME. // device-specific configuration stays with GNOME.
import QtQuick import QtQuick
@@ -174,7 +174,7 @@ SettingsPage {
Keybinds.rebind(bindRow.modelData.luaChord, chord); Keybinds.rebind(bindRow.modelData.luaChord, chord);
root.capturingChord = ""; root.capturingChord = "";
} }
onCancelled: root.capturingChord = "" onCanceled: root.capturingChord = ""
} }
Row { Row {
@@ -4,7 +4,7 @@
// //
// Range, step, label, explanation, and unit all come from PreferenceSchema. // Range, step, label, explanation, and unit all come from PreferenceSchema.
// //
// Three behaviours worth knowing: // Three behaviors worth knowing:
// //
// * The row is responsive. The Settings window is a normal tiled window, so // * The row is responsive. The Settings window is a normal tiled window, so
// its width is whatever the layout gives it -- anywhere from a half-screen // its width is whatever the layout gives it -- anywhere from a half-screen
@@ -63,7 +63,7 @@ Item {
// A fixed trailing width rather than a share of the row. A proportional // A fixed trailing width rather than a share of the row. A proportional
// control looks reasonable at 900px and absurd at 4500px, where the slider // control looks reasonable at 900px and absurd at 4500px, where the slider
// would be a metre long next to a two-word label -- and this window is // would be a meter long next to a two-word label -- and this window is
// tiled, so it really can be that wide. // tiled, so it really can be that wide.
readonly property int controlSpan: 300 readonly property int controlSpan: 300
@@ -8,7 +8,7 @@
// setting it edits. // setting it edits.
// //
// Rejected input is shown as rejected rather than silently dropped or quietly // Rejected input is shown as rejected rather than silently dropped or quietly
// sanitised. Several of these strings are serialised into an hl.config payload, // sanitised. Several of these strings are serialized into an hl.config payload,
// where stripping an unexpected character would turn a typo into a different // where stripping an unexpected character would turn a typo into a different
// working setting -- so the schema's pattern decides, the field turns red when // working setting -- so the schema's pattern decides, the field turns red when
// it fails, and nothing is committed until it passes. // it fails, and nothing is committed until it passes.
@@ -4,7 +4,7 @@
// clock. This is the same control with the readout formatted as a time, so the // clock. This is the same control with the readout formatted as a time, so the
// night light schedule says "5:30 PM" rather than a number you have to convert. // night light schedule says "5:30 PM" rather than a number you have to convert.
// //
// Honours the 24-hour clock preference, because a user who has asked for 18:30 // Honors the 24-hour clock preference, because a user who has asked for 18:30
// everywhere else should not be shown 6:30 PM here. // everywhere else should not be shown 6:30 PM here.
import QtQuick import QtQuick
@@ -135,7 +135,7 @@ def valid_home(value: Any) -> bool:
def validate_home(value: Any, label: str) -> dict[str, Any]: def validate_home(value: Any, label: str) -> dict[str, Any]:
if not valid_home(value): if not valid_home(value):
fail(f"{label} does not contain valid Home favourites.") fail(f"{label} does not contain valid Home favorites.")
return value return value
@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Propagates the colour scheme to applications that do not read the desktop # Propagates the color scheme to applications that do not read the desktop
# portal. # portal.
# #
# Most modern applications DO follow org.freedesktop.portal.Settings and need # Most modern applications DO follow org.freedesktop.portal.Settings and need
@@ -47,7 +47,7 @@ esac
# Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by # Same 8 named accents as config/Theme.qml's `accents` map (kept in sync by
# hand -- there is no shared source between QML and a shell script), primary # hand -- there is no shared source between QML and a shell script), primary
# hue per scheme only: both consumers below draw a flat colour, not the # hue per scheme only: both consumers below draw a flat color, not the
# two-stop gradient the window border does. # two-stop gradient the window border does.
accent_hex() { accent_hex() {
local name="$1" scheme="$2" local name="$1" scheme="$2"
@@ -108,7 +108,7 @@ status_hyprlock="skipped"
if [[ -r "$lock_template" ]]; then if [[ -r "$lock_template" ]]; then
# Written atomically: a lock triggered mid-write would otherwise read a # Written atomically: a lock triggered mid-write would otherwise read a
# truncated config and fall back to hyprlock's own defaults, which is a # truncated config and fall back to hyprlock's own defaults, which is a
# bright grey screen with none of this desktop's identity. # bright gray screen with none of this desktop's identity.
if sed -e "s/@FG@/$lock_fg/g" \ if sed -e "s/@FG@/$lock_fg/g" \
-e "s/@MUTED@/$lock_muted/g" \ -e "s/@MUTED@/$lock_muted/g" \
-e "s/@ACCENT@/$lock_accent/g" \ -e "s/@ACCENT@/$lock_accent/g" \
@@ -159,7 +159,7 @@ fi
# it on the next write. # it on the next write.
# #
# btop reads its theme once at startup, so a running instance keeps the old # btop reads its theme once at startup, so a running instance keeps the old
# colours until it is restarted. That is acceptable for a monitor you open when # colors until it is restarted. That is acceptable for a monitor you open when
# you want it, and forcing a restart would kill a process the user is watching. # you want it, and forcing a restart would kill a process the user is watching.
btop_conf="${XDG_CONFIG_HOME:-$HOME/.config}/btop/btop.conf" btop_conf="${XDG_CONFIG_HOME:-$HOME/.config}/btop/btop.conf"
btop_theme="tokyonight-moon" btop_theme="tokyonight-moon"
+2 -2
View File
@@ -220,7 +220,7 @@ Singleton {
} }
try { try {
root.entries = JSON.parse(text).map(row => root._normalise(row)); root.entries = JSON.parse(text).map(row => root._normalize(row));
} catch (e) { } catch (e) {
console.warn("Clipboard: could not parse history —", e); console.warn("Clipboard: could not parse history —", e);
root.entries = []; root.entries = [];
@@ -231,7 +231,7 @@ Singleton {
// Turn a raw row into what the UI actually asks questions of, so no // Turn a raw row into what the UI actually asks questions of, so no
// delegate has to know about column names or Vicinae's integer kinds. // delegate has to know about column names or Vicinae's integer kinds.
function _normalise(row: var): var { function _normalize(row: var): var {
const encrypted = row.enc !== 0; const encrypted = row.enc !== 0;
const textual = row.kind === root.kindText || row.kind === root.kindLink; const textual = row.kind === root.kindText || row.kind === root.kindLink;
const raw = row.preview || ""; const raw = row.preview || "";
@@ -6,7 +6,7 @@ pragma Singleton
// because every token there is a binding. Two other things draw on this desktop // because every token there is a binding. Two other things draw on this desktop
// and do not read Panama's store: // and do not read Panama's store:
// //
// GTK applications read gsettings colour-scheme and gtk-theme // GTK applications read gsettings color-scheme and gtk-theme
// the compositor draws window borders and shadows // the compositor draws window borders and shadows
// //
// A toolbar or a window border still wearing the other scheme is more jarring // A toolbar or a window border still wearing the other scheme is more jarring
@@ -31,10 +31,10 @@ Singleton {
readonly property string inactiveBorder: root.dark ? root.inactiveBorderDark : root.inactiveBorderLight readonly property string inactiveBorder: root.dark ? root.inactiveBorderDark : root.inactiveBorderLight
// The focused border follows the chosen accent. `ee` is the shipped alpha // The focused border follows the chosen accent. `ee` is the shipped alpha
// for the Prism gradient; Theme owns which colours, this owns the form the // for the Prism gradient; Theme owns which colors, this owns the form the
// compositor wants them in. // compositor wants them in.
function hyprColor(value: var): string { function hyprColor(value: var): string {
// Qt gives "#rrggbb" -- or "#aarrggbb" if the colour ever carries an // Qt gives "#rrggbb" -- or "#aarrggbb" if the color ever carries an
// alpha channel. slice(-6) keeps the trailing rrggbb either way; // alpha channel. slice(-6) keeps the trailing rrggbb either way;
// slice(0, 6) would instead grab "aarrgg" out of an 8-digit string and // slice(0, 6) would instead grab "aarrgg" out of an 8-digit string and
// call it RGB. Hyprland wants rgba(rrggbbaa). // call it RGB. Hyprland wants rgba(rrggbbaa).
@@ -59,7 +59,7 @@ Singleton {
id: runner id: runner
onExited: (exitCode, exitStatus) => { onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) if (exitCode !== 0)
root.lastError = "The colour scheme could not be applied everywhere."; root.lastError = "The color scheme could not be applied everywhere.";
root.drain(); root.drain();
} }
} }
@@ -153,16 +153,16 @@ Singleton {
// Each accent carries a separate pair for light and dark, so this // Each accent carries a separate pair for light and dark, so this
// runs on either kind of change: a scheme flip restates the same // runs on either kind of change: a scheme flip restates the same
// accent's other pair, and an accent change restates the same // accent's other pair, and an accent change restates the same
// scheme's other colours. // scheme's other colors.
// //
// A two-stop gradient at the shipped angle. Written as a Lua TABLE: // 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 // the string form of a gradient carries only one stop, and passing
// "rgba(a) rgba(b) 115deg" as a string is accepted and silently // "rgba(a) rgba(b) 115deg" as a string is accepted and silently
// keeps the previous value. Multi-stop must be the table form -- // keeps the previous value. Multi-stop must be the table form --
// built by the same serialiseValue() SystemSettings uses for every // built by the same serializeValue() SystemSettings uses for every
// other gradient, rather than a second hand-rolled copy of that // other gradient, rather than a second hand-rolled copy of that
// escaping here. // escaping here.
const activeBorder = SystemSettings.serialiseValue({ const activeBorder = SystemSettings.serializeValue({
colors: [root.accentBorderStart, root.accentBorderEnd], colors: [root.accentBorderStart, root.accentBorderEnd],
angle: 115 angle: 115
}); });
+4 -4
View File
@@ -23,7 +23,7 @@ Singleton {
property string timezone: "" property string timezone: ""
property bool ntpEnabled: false property bool ntpEnabled: false
property bool ntpSynchronised: false property bool ntpSynchronized: false
property string localTime: "" property string localTime: ""
property string universalTime: "" property string universalTime: ""
property string rtcTime: "" property string rtcTime: ""
@@ -79,7 +79,7 @@ Singleton {
// status is re-read either way. // status is re-read either way.
root.lastError = exitCode === 0 root.lastError = exitCode === 0
? "" ? ""
: "The system rejected that change, or authentication was cancelled."; : "The system rejected that change, or authentication was canceled.";
root.refresh(); root.refresh();
} }
} }
@@ -101,7 +101,7 @@ Singleton {
else if (key === "NTP") else if (key === "NTP")
root.ntpEnabled = value === "yes"; root.ntpEnabled = value === "yes";
else if (key === "NTPSynchronized") else if (key === "NTPSynchronized")
root.ntpSynchronised = value === "yes"; root.ntpSynchronized = value === "yes";
} }
root.lastError = ""; root.lastError = "";
} }
@@ -115,7 +115,7 @@ Singleton {
// caller-supplied text reaches the command. // caller-supplied text reaches the command.
function setTimezone(zone: string): bool { function setTimezone(zone: string): bool {
if (root.zones.indexOf(zone) < 0) { if (root.zones.indexOf(zone) < 0) {
root.lastError = "That is not a timezone this system recognises."; root.lastError = "That is not a timezone this system recognizes.";
return false; return false;
} }
if (writeRun.running) if (writeRun.running)
+2 -2
View File
@@ -141,7 +141,7 @@ Singleton {
? persistedPrimaries[0].name ? persistedPrimaries[0].name
: (origin?.name ?? raw[0]?.name ?? ""); : (origin?.name ?? raw[0]?.name ?? "");
root.monitors = raw.map(monitor => { root.monitors = raw.map(monitor => {
const modes = root.normaliseModes(monitor.availableModes ?? []); const modes = root.normalizeModes(monitor.availableModes ?? []);
const width = monitor.width ?? 0; const width = monitor.width ?? 0;
const height = monitor.height ?? 0; const height = monitor.height ?? 0;
const refreshRate = monitor.refreshRate ?? 0; const refreshRate = monitor.refreshRate ?? 0;
@@ -200,7 +200,7 @@ Singleton {
// resolution at distinct rates such as 60.00 and 59.94. Those identities // resolution at distinct rates such as 60.00 and 59.94. Those identities
// remain separate because confirmation and recovery must read back the // remain separate because confirmation and recovery must read back the
// exact mode the user chose, even when their rounded labels look similar. // exact mode the user chose, even when their rounded labels look similar.
function normaliseModes(raw: var): var { function normalizeModes(raw: var): var {
const seen = {}; const seen = {};
const out = []; const out = [];
for (const entry of raw) { for (const entry of raw) {
+1 -1
View File
@@ -114,7 +114,7 @@ Singleton {
} }
// Stores a chosen place. Coordinates are rounded to four decimals -- roughly // Stores a chosen place. Coordinates are rounded to four decimals -- roughly
// ten metres, far finer than a weather reading resolves, and it keeps a // ten meters, far finer than a weather reading resolves, and it keeps a
// precise home location out of the settings file. // precise home location out of the settings file.
function choose(place: var): bool { function choose(place: var): bool {
const latitude = Math.round(place.latitude * 10000) / 10000; const latitude = Math.round(place.latitude * 10000) / 10000;
@@ -81,7 +81,7 @@ Singleton {
} }
// "AMD ... [Radeon RX 7700 XT / 7800 XT] (rev c8)" is what lspci gives; the // "AMD ... [Radeon RX 7700 XT / 7800 XT] (rev c8)" is what lspci gives; the
// bracketed marketing name is the part anyone recognises. // bracketed marketing name is the part anyone recognizes.
function shortName(name: string): string { function shortName(name: string): string {
const bracketed = String(name).match(/\[([^\]]+)\]\s*(?:\(rev[^)]*\))?\s*$/); const bracketed = String(name).match(/\[([^\]]+)\]\s*(?:\(rev[^)]*\))?\s*$/);
return bracketed ? bracketed[1] : String(name).replace(/\s*\(rev[^)]*\)\s*$/, ""); return bracketed ? bracketed[1] : String(name).replace(/\s*\(rev[^)]*\)\s*$/, "");
@@ -43,7 +43,7 @@ Singleton {
readonly property bool scheduled: root.inWindow(clock.hours + clock.minutes / 60) readonly property bool scheduled: root.inWindow(clock.hours + clock.minutes / 60)
// A manual toggle always wins: it drops out of the schedule rather than // A manual toggle always wins: it drops out of the schedule rather than
// being silently reverted a minute later. Same as GNOME's behaviour when // being silently reverted a minute later. Same as GNOME's behavior when
// you flip night light off during a scheduled evening. // you flip night light off during a scheduled evening.
function toggle(): void { function toggle(): void {
if (root.automatic) { if (root.automatic) {
+2 -2
View File
@@ -10,7 +10,7 @@ pragma Singleton
// the *live* objects rather than copies: that keeps actions and inline replies // the *live* objects rather than copies: that keeps actions and inline replies
// working from the tray, which is what GNOME does. The cost is that a // working from the tray, which is what GNOME does. The cost is that a
// notification an app closes itself (progress bars, "download finished" // notification an app closes itself (progress bars, "download finished"
// replacing "downloading") disappears from history too — correct behaviour, // replacing "downloading") disappears from history too — correct behavior,
// but the reason history is not append-only. // but the reason history is not append-only.
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
@@ -34,7 +34,7 @@ Singleton {
// Suppresses toasts entirely. Notifications still reach history. // Suppresses toasts entirely. Notifications still reach history.
property bool doNotDisturb: false property bool doNotDisturb: false
// Cleared when the notification centre is opened. The bar binds to this. // Cleared when the notification center is opened. The bar binds to this.
property int unreadCount: 0 property int unreadCount: 0
// Kept separate from the persisted map so this version can safely run // Kept separate from the persisted map so this version can safely run
@@ -33,7 +33,7 @@ Singleton {
// Non-empty when the machine cannot actually deliver the profile it is set // Non-empty when the machine cannot actually deliver the profile it is set
// to -- thermal throttling, or a laptop running on battery. Worth showing, // to -- thermal throttling, or a laptop running on battery. Worth showing,
// because otherwise "performance" is a claim the hardware is not honouring. // because otherwise "performance" is a claim the hardware is not honoring.
property string degraded: "" property string degraded: ""
readonly property bool available: root.profiles.length > 0 readonly property bool available: root.profiles.length > 0
@@ -52,7 +52,7 @@ Singleton {
function detail(profile: string): string { function detail(profile: string): string {
switch (profile) { switch (profile) {
case "power-saver": return "Reduces performance to save energy and run quieter"; case "power-saver": return "Reduces performance to save energy and run quieter";
case "balanced": return "Standard behaviour, scaling up only when needed"; case "balanced": return "Standard behavior, scaling up only when needed";
case "performance": return "Holds higher clocks, using more power and making more noise"; case "performance": return "Holds higher clocks, using more power and making more noise";
default: return ""; default: return "";
} }
@@ -107,7 +107,7 @@ Singleton {
if (restoreAccepted) if (restoreAccepted)
root.lastError = ""; root.lastError = "";
else if (root.lastError === "") else if (root.lastError === "")
root.lastError = "Desktop settings were restored, but Home favourites could not be reloaded."; root.lastError = "Desktop settings were restored, but Home favorites could not be reloaded.";
if (!restoreAccepted) { if (!restoreAccepted) {
root.setDisplayBlocked(false); root.setDisplayBlocked(false);
root.protectedDisplays = ({}); root.protectedDisplays = ({});
@@ -192,10 +192,10 @@ Singleton {
if (actionRun.running) if (actionRun.running)
return; return;
actionRun.restoring = false; actionRun.restoring = false;
actionRun.exec([root.helperPath, "save", root.serialiseHomeState()]); actionRun.exec([root.helperPath, "save", root.serializeHomeState()]);
} }
function serialiseHomeState(): string { function serializeHomeState(): string {
const current = root.readHomeState(); const current = root.readHomeState();
const favorites = []; const favorites = [];
for (const favorite of current.favorites ?? []) { for (const favorite of current.favorites ?? []) {
@@ -59,7 +59,7 @@ Singleton {
// on a settings app that plainly has one. // on a settings app that plainly has one.
readonly property var extraEntries: [ readonly property var extraEntries: [
{ label: "Timezone", detail: "Set the system timezone", page: "datetime" }, { label: "Timezone", detail: "Set the system timezone", page: "datetime" },
{ label: "Network time", detail: "Synchronise the clock with a time server", page: "datetime" }, { label: "Network time", detail: "Synchronize the clock with a time server", page: "datetime" },
{ label: "Wi-Fi", detail: "Managed by GNOME Settings", page: "connectivity" }, { label: "Wi-Fi", detail: "Managed by GNOME Settings", page: "connectivity" },
{ label: "Bluetooth", detail: "Managed by GNOME Settings", page: "connectivity" }, { label: "Bluetooth", detail: "Managed by GNOME Settings", page: "connectivity" },
{ label: "Printers", detail: "Managed by GNOME Settings", page: "connectivity" }, { label: "Printers", detail: "Managed by GNOME Settings", page: "connectivity" },
@@ -68,7 +68,7 @@ Singleton {
{ label: "Keyboard shortcuts", detail: "Every shortcut the compositor has bound", page: "shortcuts" }, { label: "Keyboard shortcuts", detail: "Every shortcut the compositor has bound", page: "shortcuts" },
{ label: "System Health", detail: "Check Panama services, integrations, tools, and recovery actions", page: "services" }, { label: "System Health", detail: "Check Panama services, integrations, tools, and recovery actions", page: "services" },
{ label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" }, { label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" },
{ label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid colour", page: "appearance" }, { label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid color", page: "appearance" },
{ label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" }, { label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" },
{ label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" }, { label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" },
{ label: "Arrange displays", detail: "Drag connected displays into their physical positions", page: "displays" }, { label: "Arrange displays", detail: "Drag connected displays into their physical positions", page: "displays" },
@@ -1,6 +1,6 @@
pragma Singleton pragma Singleton
// GNOME and GTK applications already honour these desktop sound preferences. // GNOME and GTK applications already honor these desktop sound preferences.
// Panama controls the same durable keys so moving between sessions does not // Panama controls the same durable keys so moving between sessions does not
// create two competing notions of whether event feedback is enabled. // create two competing notions of whether event feedback is enabled.
@@ -277,7 +277,7 @@ Singleton {
function hyprValue(entry: var, value: var): var { function hyprValue(entry: var, value: var): var {
// Some options are phrased as a negative by the compositor -- the four // Some options are phrased as a negative by the compositor -- the four
// Hyprland notices are all `disable_x` -- while the setting reads as // Hyprland notices are all `disable_x` -- while the setting reads as
// "show x", because a switch labelled "Disable splash text" that must // "show x", because a switch labeled "Disable splash text" that must
// be ON to hide something is a small cruelty. `invert` bridges the two, // be ON to hide something is a small cruelty. `invert` bridges the two,
// in exactly one place, so nothing downstream has to remember which // in exactly one place, so nothing downstream has to remember which
// options are backwards. // options are backwards.
@@ -289,7 +289,7 @@ Singleton {
return value; return value;
} }
// Serialises validated values into a nested hl.config{} call. Table paths // Serializes validated values into a nested hl.config{} call. Table paths
// come from the schema and values have already passed coerce(), including // come from the schema and values have already passed coerce(), including
// the pattern check on constrained strings, so nothing caller-supplied // the pattern check on constrained strings, so nothing caller-supplied
// reaches the payload unchecked. // reaches the payload unchecked.
@@ -301,12 +301,12 @@ Singleton {
let node = tree; let node = tree;
for (let i = 0; i < path.length - 1; i++) for (let i = 0; i < path.length - 1; i++)
node = node[path[i]] = node[path[i]] ?? {}; node = node[path[i]] = node[path[i]] ?? {};
node[path[path.length - 1]] = root.serialiseValue(root.hyprValue(entry, requested[key])); node[path[path.length - 1]] = root.serializeValue(root.hyprValue(entry, requested[key]));
} }
return `hl.config(${root.serialiseTable(tree)})`; return `hl.config(${root.serializeTable(tree)})`;
} }
function serialiseValue(value: var): string { function serializeValue(value: var): string {
if (typeof value === "boolean") if (typeof value === "boolean")
return value ? "true" : "false"; return value ? "true" : "false";
if (typeof value === "number") if (typeof value === "number")
@@ -330,22 +330,22 @@ Singleton {
if (Array.isArray(value) && value.length === 2) if (Array.isArray(value) && value.length === 2)
return `{ ${Number(value[0])}, ${Number(value[1])} }`; return `{ ${Number(value[0])}, ${Number(value[1])} }`;
// Strings only reach here after the schema's pattern check; quoting is // Strings only reach here after the schema's pattern check; quoting is
// belt-and-braces rather than the primary defence. // belt-and-braces rather than the primary defense.
return `"${String(value).replace(/["\\]/g, "")}"`; return `"${String(value).replace(/["\\]/g, "")}"`;
} }
function serialiseTable(node: var): string { function serializeTable(node: var): string {
const parts = []; const parts = [];
for (const name in node) { for (const name in node) {
const child = node[name]; const child = node[name];
// A leaf arrives pre-serialised as a string; anything else is // A leaf arrives pre-serialized as a string; anything else is
// either a nested section or a structured value (gradient, vec2) // either a nested section or a structured value (gradient, vec2)
// that serialiseValue knows how to render. // that serializeValue knows how to render.
const rendered = typeof child === "string" const rendered = typeof child === "string"
? child ? child
: (Array.isArray(child) || (child && child.colors !== undefined) : (Array.isArray(child) || (child && child.colors !== undefined)
? root.serialiseValue(child) ? root.serializeValue(child)
: root.serialiseTable(child)); : root.serializeTable(child));
parts.push(`${name} = ${rendered}`); parts.push(`${name} = ${rendered}`);
} }
return `{ ${parts.join(", ")} }`; return `{ ${parts.join(", ")} }`;
@@ -403,11 +403,11 @@ Singleton {
function gradientMatches(expected: var, observed: string): bool { function gradientMatches(expected: var, observed: string): bool {
if (typeof observed !== "string") if (typeof observed !== "string")
return false; return false;
return root.normaliseGradient(expected) === root.normaliseGradient(observed); return root.normalizeGradient(expected) === root.normalizeGradient(observed);
} }
// Both notations reduced to "aarrggbb aarrggbb Ndeg". // Both notations reduced to "aarrggbb aarrggbb Ndeg".
function normaliseGradient(value: var): string { function normalizeGradient(value: var): string {
const stops = []; const stops = [];
let angle = 0; let angle = 0;
@@ -518,8 +518,8 @@ Singleton {
// Panama's shipped DP-2 placement and automatic placement elsewhere. // Panama's shipped DP-2 placement and automatic placement elsewhere.
// Home accessories keep their own store (panama-home.json), so a reset // Home accessories keep their own store (panama-home.json), so a reset
// that only cleared the schema store would silently leave a customised // that only cleared the schema store would silently leave a customized
// favourites list behind while claiming to restore Panama's defaults. // favorites list behind while claiming to restore Panama's defaults.
// //
// HomePreferences owns the write-through boundary so the state file is // HomePreferences owns the write-through boundary so the state file is
// rewritten before this reset can be considered complete. // rewritten before this reset can be considered complete.
@@ -593,7 +593,7 @@ Singleton {
// `subpage` reaches the panels GNOME 50 nests under System -- users, // `subpage` reaches the panels GNOME 50 nests under System -- users,
// about, datetime, region -- which its own desktop entries open as // about, datetime, region -- which its own desktop entries open as
// `gnome-control-center system users`. Without it, a row labelled "Users" // `gnome-control-center system users`. Without it, a row labeled "Users"
// lands on System's front page and leaves the user to navigate, which is // lands on System's front page and leaves the user to navigate, which is
// most of the way to a broken button. // most of the way to a broken button.
function openGnomePanel(panel: string, subpage: string): bool { function openGnomePanel(panel: string, subpage: string): bool {
+6 -6
View File
@@ -187,7 +187,7 @@ Singleton {
}; };
} }
function normalisePolicy(policy: var): var { function normalizePolicy(policy: var): var {
if (!policy || typeof policy !== "object") if (!policy || typeof policy !== "object")
return null; return null;
const mode = ["single", "slideshow", "per-monitor"].includes(policy.mode) const mode = ["single", "slideshow", "per-monitor"].includes(policy.mode)
@@ -220,8 +220,8 @@ Singleton {
function applyPolicy(policy: var, persist: bool, automatic: bool): bool { function applyPolicy(policy: var, persist: bool, automatic: bool): bool {
if (root.busy) if (root.busy)
return false; return false;
const normalised = root.normalisePolicy(policy); const normalized = root.normalizePolicy(policy);
if (normalised === null) { if (normalized === null) {
root.lastError = "That wallpaper policy is not valid."; root.lastError = "That wallpaper policy is not valid.";
return false; return false;
} }
@@ -231,8 +231,8 @@ Singleton {
return false; return false;
} }
const expected = WallpaperPolicy.effectiveMap( const expected = WallpaperPolicy.effectiveMap(
normalised.mode, normalised.globalPath, normalised.slideshowPath, normalized.mode, normalized.globalPath, normalized.slideshowPath,
normalised.assignments, outputs, normalised.candidates); normalized.assignments, outputs, normalized.candidates);
if (Object.keys(expected).length !== outputs.length if (Object.keys(expected).length !== outputs.length
|| Object.values(expected).some(path => path === "")) { || Object.values(expected).some(path => path === "")) {
root.lastError = "That wallpaper policy is not valid."; root.lastError = "That wallpaper policy is not valid.";
@@ -242,7 +242,7 @@ Singleton {
root.transaction = { root.transaction = {
expected, expected,
remaining: outputs.slice(), remaining: outputs.slice(),
policy: normalised, policy: normalized,
persist: persist === true, persist: persist === true,
automatic: automatic === true automatic: automatic === true
}; };
+2 -2
View File
@@ -24,7 +24,7 @@ Rectangle {
id: root id: root
// Horizontal inset, normally the parent's corner radius. Below that the // Horizontal inset, normally the parent's corner radius. Below that the
// line runs out onto the curve and looks like a rendering artefact. // line runs out onto the curve and looks like a rendering artifact.
property real inset: Theme.popoverRadius property real inset: Theme.popoverRadius
anchors.leftMargin: root.inset anchors.leftMargin: root.inset
@@ -33,7 +33,7 @@ Rectangle {
implicitHeight: 1 implicitHeight: 1
height: 1 height: 1
// The colour stops are the only place the two accents touch. // The color stops are the only place the two accents touch.
gradient: Gradient { gradient: Gradient {
orientation: Gradient.Horizontal orientation: Gradient.Horizontal
+2 -2
View File
@@ -1,7 +1,7 @@
// A freedesktop symbolic icon, recoloured to a Theme token. // A freedesktop symbolic icon, recolored to a Theme token.
// //
// Adwaita's symbolic SVGs carry a hardcoded near-black fill (#2e3436) and Qt // Adwaita's symbolic SVGs carry a hardcoded near-black fill (#2e3436) and Qt
// does not do GTK's runtime recolouring, so drawn as-is they are invisible on // does not do GTK's runtime recoloring, so drawn as-is they are invisible on
// a Tokyo Night background — verified on this machine. The fix is to paint a // a Tokyo Night background — verified on this machine. The fix is to paint a
// themed rectangle through the icon's alpha channel. // themed rectangle through the icon's alpha channel.
+2 -2
View File
@@ -1,5 +1,5 @@
// A GNOME-style quick-settings tile: big rounded button with an icon and a // A GNOME-style quick-settings tile: big rounded button with an icon and a
// label, filled with the accent colour when active. // label, filled with the accent color when active.
import QtQuick import QtQuick
import Quickshell import Quickshell
@@ -52,7 +52,7 @@ Rectangle {
spacing: 10 spacing: 10
// ThemedIcon, not a bare IconImage: Adwaita's symbolic SVGs carry a // ThemedIcon, not a bare IconImage: Adwaita's symbolic SVGs carry a
// hardcoded #2e3436 fill and Qt does not recolour them the way GTK // hardcoded #2e3436 fill and Qt does not recolor them the way GTK
// does, so drawn directly they are all but invisible on this palette. // does, so drawn directly they are all but invisible on this palette.
ThemedIcon { ThemedIcon {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -25,7 +25,7 @@ Item {
opacity: enabled ? 1.0 : 0.45 opacity: enabled ? 1.0 : 0.45
// ThemedIcon, not a bare IconImage — see the note in Toggle.qml: Adwaita // ThemedIcon, not a bare IconImage — see the note in Toggle.qml: Adwaita
// symbolic icons have a hardcoded near-black fill that Qt will not recolour. // symbolic icons have a hardcoded near-black fill that Qt will not recolor.
ThemedIcon { ThemedIcon {
id: leadingIcon id: leadingIcon
anchors.left: parent.left anchors.left: parent.left
@@ -57,7 +57,7 @@ Item {
border.width: 0 border.width: 0
// The gradient is anchored to the full track, not to the fill, so // The gradient is anchored to the full track, not to the fill, so
// the colour at a given position never moves as the value changes — // the color at a given position never moves as the value changes —
// dragging reveals the prism rather than squashing it. // dragging reveals the prism rather than squashing it.
gradient: Gradient { gradient: Gradient {
orientation: Gradient.Horizontal orientation: Gradient.Horizontal
+1 -1
View File
@@ -2,7 +2,7 @@
# #
# Same structure, same roles, Day's palette. Mapped role for role rather than by # Same structure, same roles, Day's palette. Mapped role for role rather than by
# hue: #1b1d2b was text drawn ON an accent block, so it becomes the LIGHT ground # hue: #1b1d2b was text drawn ON an accent block, so it becomes the LIGHT ground
# rather than a dark colour, or the status bar would be dark text on a dark # rather than a dark color, or the status bar would be dark text on a dark
# accent in light mode. # accent in light mode.
# #
# The accents are Day's DARKER variants, not its standard ones. Measured against # The accents are Day's DARKER variants, not its standard ones. Measured against
+1 -1
View File
@@ -3,7 +3,7 @@
# Extracted from tmux.conf so the two schemes can be swapped. tmux.conf sources # Extracted from tmux.conf so the two schemes can be swapped. tmux.conf sources
# current-theme.conf, which panama-theme-apps generates from one of these. # current-theme.conf, which panama-theme-apps generates from one of these.
# #
# Every colour here was already in tmux.conf; this file is that block verbatim. # Every color here was already in tmux.conf; this file is that block verbatim.
set -g mode-style "fg=#82aaff,bg=#3b4261" set -g mode-style "fg=#82aaff,bg=#3b4261"
+2 -2
View File
@@ -19,8 +19,8 @@ bind C-Space send-prefix
# Set status bar # Set status bar
#set -g status-bg pink #set -g status-bg pink
# Tokyo Night Moon color palette # Tokyo Night Moon color palette
# Colours live in themes/ and are generated into current-theme.conf by # Colors live in themes/ and are generated into current-theme.conf by
# panama-theme-apps, which regenerates it whenever the desktop colour scheme # panama-theme-apps, which regenerates it whenever the desktop color scheme
# changes. -q so a fresh checkout without the generated file still starts. # changes. -q so a fresh checkout without the generated file still starts.
source-file -q "~/.config/tmux/current-theme.conf" source-file -q "~/.config/tmux/current-theme.conf"
@@ -1,10 +1,10 @@
# Tokyo Night Day — the light counterpart to tokyonight-moon.toml. # Tokyo Night Day — the light counterpart to tokyonight-moon.toml.
# #
# Same schema and the same role for every colour; only the palette differs, so # Same schema and the same role for every color; only the palette differs, so
# the launcher keeps its identity when the desktop switches to light rather # the launcher keeps its identity when the desktop switches to light rather
# than becoming a different-looking application. # than becoming a different-looking application.
# #
# Colours are the Tokyo Night Day palette, taken from kitty/themes/tokyonight-day.conf # Colors are the Tokyo Night Day palette, taken from kitty/themes/tokyonight-day.conf
# so the launcher and the terminal cannot drift apart. # so the launcher and the terminal cannot drift apart.
[meta] [meta]
@@ -2,7 +2,7 @@
<!-- <!--
Panama Settings. Panama Settings.
A gear, because a settings icon has to be recognisable at 48px in a dock A gear, because a settings icon has to be recognizable at 48px in a dock
before it is anything else - but rendered in the Prism identity rather than before it is anything else - but rendered in the Prism identity rather than
the flat monochrome symbolic icon this replaces. Blue leads into orchid, the the flat monochrome symbolic icon this replaces. Blue leads into orchid, the
same pair that marks the focused window, the active workspace, and the same pair that marks the focused window, the active workspace, and the

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+2 -2
View File
@@ -2,14 +2,14 @@
<!-- <!--
Panama Settings. Panama Settings.
A gear, because a settings icon has to be recognisable at 48px in a dock A gear, because a settings icon has to be recognizable at 48px in a dock
before it is clever — but rendered in the Prism identity rather than the flat before it is clever — but rendered in the Prism identity rather than the flat
monochrome of the symbolic icon this replaces. Blue leads into orchid across monochrome of the symbolic icon this replaces. Blue leads into orchid across
the gradient, the same pair that marks the focused window, the active the gradient, the same pair that marks the focused window, the active
workspace and the hairline on every glass surface. workspace and the hairline on every glass surface.
Drawn on the dark tile (#222436) so it reads as an application icon among Drawn on the dark tile (#222436) so it reads as an application icon among
full-colour ones, rather than as a UI glyph that wandered out of a toolbar. full-color ones, rather than as a UI glyph that wandered out of a toolbar.
--> -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
<defs> <defs>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -90,7 +90,7 @@
- Produces: `ShellState.openOverview(workspaceId)`, filmstrip selection, large workspace preview, cross-workspace window search - Produces: `ShellState.openOverview(workspaceId)`, filmstrip selection, large workspace preview, cross-workspace window search
- [x] Add requested-overview-workspace state without coupling `ShellState` to the focus service. - [x] Add requested-overview-workspace state without coupling `ShellState` to the focus service.
- [x] Replace the centred equal-card row with a top filmstrip and a large selected-workspace preview. - [x] Replace the centered equal-card row with a top filmstrip and a large selected-workspace preview.
- [x] Keep the trailing dynamic-workspace card and one-shot thumbnail capture behavior. - [x] Keep the trailing dynamic-workspace card and one-shot thumbnail capture behavior.
- [x] Add wheel navigation over the strip and keep the overview mapped while switching workspaces. - [x] Add wheel navigation over the strip and keep the overview mapped while switching workspaces.
- [x] Add a keyboard-focused search field and filtered cross-workspace window results. - [x] Add a keyboard-focused search field and filtered cross-workspace window results.
@@ -328,7 +328,7 @@ FileView {
printErrors: false printErrors: false
atomicWrites: true atomicWrites: true
onSaved: root.saveError = "" onSaved: root.saveError = ""
onSaveFailed: error => root.saveError = "Could not save Home favourites." onSaveFailed: error => root.saveError = "Could not save Home favorites."
JsonAdapter { JsonAdapter {
id: values id: values
property bool initialized: false property bool initialized: false
@@ -15,10 +15,10 @@ holds the shipped defaults; one JSON file is the truth both sides read.
- Verify every write by reading the value back (`hyprctl getoption`), never by - Verify every write by reading the value back (`hyprctl getoption`), never by
trusting an exit code. trusting an exit code.
- No UI-supplied string is interpolated into `eval`, a shell command, or a config - No UI-supplied string is interpolated into `eval`, a shell command, or a config
value. Numbers range-checked, choices allow-listed, colours hex-validated. value. Numbers range-checked, choices allow-listed, colors hex-validated.
- A missing or malformed `settings.json` degrades to shipped defaults. The Lua - A missing or malformed `settings.json` degrades to shipped defaults. The Lua
read is `pcall`-wrapped so it can never take down the compositor config. read is `pcall`-wrapped so it can never take down the compositor config.
- Tokyo Night Moon and Prism stay the only identity; customisation adjusts its - Tokyo Night Moon and Prism stay the only identity; customization adjusts its
parameters, it does not replace it. parameters, it does not replace it.
- Motion stays event-driven at every setting. No idle repaint. - Motion stays event-driven at every setting. No idle repaint.
- Do not restart the running Quickshell process during development; it hot-reloads. - Do not restart the running Quickshell process during development; it hot-reloads.
@@ -30,7 +30,7 @@ holds the shipped defaults; one JSON file is the truth both sides read.
## Stage 0 — Stop the lying (ship first, standalone) ## Stage 0 — Stop the lying (ship first, standalone)
The display-policy toggles report success while doing nothing. This is a The display-policy toggles report success while doing nothing. This is a
correctness bug in shipped behaviour and does not depend on any of the correctness bug in shipped behavior and does not depend on any of the
architecture below. architecture below.
**Files:** Modify `config/dot/quickshell/services/SystemSettings.qml`; **Files:** Modify `config/dot/quickshell/services/SystemSettings.qml`;
@@ -40,7 +40,7 @@ Test `tests/quickshell/settings-hyprland-write-contract.sh`
asserts via `hyprctl getoption` that the compositor value actually changed — asserts via `hyprctl getoption` that the compositor value actually changed —
and that a rejected write leaves `lastError` non-empty. and that a rejected write leaves `lastError` non-empty.
- [x] Run it; confirm it fails against the current `hyprctl keyword` implementation. - [x] Run it; confirm it fails against the current `hyprctl keyword` implementation.
- [x] Add a single `applyOptions(values)` boundary that serialises validated values - [x] Add a single `applyOptions(values)` boundary that serializes validated values
into `hl.config{}` and runs them through `hyprctl eval`. into `hl.config{}` and runs them through `hyprctl eval`.
- [x] Route `setAutoHdr`, `setVrrPolicy`, and `setDirectScanoutPolicy` through it. - [x] Route `setAutoHdr`, `setVrrPolicy`, and `setDirectScanoutPolicy` through it.
- [x] Treat "wrote it back and read it back equal" as the only success condition; - [x] Treat "wrote it back and read it back equal" as the only success condition;
@@ -134,9 +134,9 @@ Test `tests/hypr/prefs-fallback-contract.sh`
- [x] Implement a dependency-free JSON reader exposing `prefs.get(key, fallback)`, - [x] Implement a dependency-free JSON reader exposing `prefs.get(key, fallback)`,
`pcall`-wrapped, reading `$XDG_CONFIG_HOME/panama/settings.json`. `pcall`-wrapped, reading `$XDG_CONFIG_HOME/panama/settings.json`.
- [x] Require it first in `hyprland.lua`, before `looks`. - [x] Require it first in `hyprland.lua`, before `looks`.
- [x] Convert the appearance and behaviour literals in `looks.lua` and `input.lua` - [x] Convert the appearance and behavior literals in `looks.lua` and `input.lua`
to `prefs.get("<key>", <current literal>)`, keeping every current value as to `prefs.get("<key>", <current literal>)`, keeping every current value as
the fallback so shipped behaviour is byte-identical. the fallback so shipped behavior is byte-identical.
- [x] Extend `PreferenceSchema.qml` with the Hyprland-owned keys, each carrying - [x] Extend `PreferenceSchema.qml` with the Hyprland-owned keys, each carrying
the `hl.config` path it maps to. the `hl.config` path it maps to.
- [x] Have `SystemSettings` derive its `eval` payload from that mapping, so a new - [x] Have `SystemSettings` derive its `eval` payload from that mapping, so a new
@@ -208,7 +208,7 @@ Modify all eleven `*Page.qml`; Test `tests/quickshell/settings-rows-contract.sh`
- [ ] Move the remaining pages (Home, Displays, Connectivity, Sound, - [ ] Move the remaining pages (Home, Displays, Connectivity, Sound,
Notifications, Screen Intelligence, Services, About) onto `SettingsPage`. Notifications, Screen Intelligence, Services, About) onto `SettingsPage`.
**Exit criteria:** no shipped behaviour value is reachable only by editing a file. **Exit criteria:** no shipped behavior value is reachable only by editing a file.
**Landed (first pass).** `SettingsPage` plus `ToggleRow`, `SliderRow`, **Landed (first pass).** `SettingsPage` plus `ToggleRow`, `SliderRow`,
`ChoiceRow`, `ActionRow`, and `TextRow`. A row names a schema key and needs `ChoiceRow`, `ActionRow`, and `TextRow`. A row names a schema key and needs
@@ -218,7 +218,7 @@ explanation, bounds, and unit from the schema, and writes through
apply-and-verify and local keys straight to the store. Rows never need to know apply-and-verify and local keys straight to the store. Rows never need to know
which kind they hold. which kind they hold.
`DesktopPreview` is the direction-B centrepiece: two tiled windows drawn at the `DesktopPreview` is the direction-B centerpiece: two tiled windows drawn at the
settings actually in effect, scaled by the ratio between the preview's width and settings actually in effect, scaled by the ratio between the preview's width and
the real monitor's, so a 10px gap on a 4500px display looks as small as it is. the real monitor's, so a 10px gap on a 4500px display looks as small as it is.
@@ -2,7 +2,7 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Expand Panama Quick Settings into the approved Control Center with secure Home Assistant favourites and capability-aware iPhone continuity. **Goal:** Expand Panama Quick Settings into the approved Control Center with secure Home Assistant favorites and capability-aware iPhone continuity.
**Architecture:** Two executable Python helpers own the external boundaries: KDE Connect CLI/D-Bus and the Home Assistant REST API/credential lookup. Quickshell singletons normalize those JSON contracts for focused Home and Phone components inside the existing Quick Settings window; Ongoing and Signal Glass consume transfer lifecycle without duplicating state. **Architecture:** Two executable Python helpers own the external boundaries: KDE Connect CLI/D-Bus and the Home Assistant REST API/credential lookup. Quickshell singletons normalize those JSON contracts for focused Home and Phone components inside the existing Quick Settings window; Ongoing and Signal Glass consume transfer lifecycle without duplicating state.
@@ -17,7 +17,7 @@
- Only one of Wi-Fi, Bluetooth, sink, source, Home, and Phone may be expanded at once. - Only one of Wi-Fi, Bluetooth, sink, source, Home, and Phone may be expanded at once.
- Never print Home Assistant tokens, Authorization headers, clipboard text, certificate fingerprints, IP addresses, response bodies, or full local file paths. - Never print Home Assistant tokens, Authorization headers, clipboard text, certificate fingerprints, IP addresses, response bodies, or full local file paths.
- Home Assistant tokens remain in the gitignored environment file or Secret Service; never write one into tracked content. - Home Assistant tokens remain in the gitignored environment file or Secret Service; never write one into tracked content.
- Home Assistant toggle commands accept only entity IDs from the configured favourites snapshot. - Home Assistant toggle commands accept only entity IDs from the configured favorites snapshot.
- KDE Connect actions derive from advertised plugins; no fake battery or remote-input control is allowed. - KDE Connect actions derive from advertised plugins; no fake battery or remote-input control is allowed.
- Every Home toggle, file chooser, file send, clipboard send, and Ring action requires a user click. - Every Home toggle, file chooser, file send, clipboard send, and Ring action requires a user click.
- Home toggle success, phone Ring, and clipboard success remain globally quiet. - Home toggle success, phone Ring, and clipboard success remain globally quiet.
@@ -49,8 +49,8 @@
- `config/dot/quickshell/modules/quicksettings/QuickSettings.qml` — tighter top attachment and service preparation. - `config/dot/quickshell/modules/quicksettings/QuickSettings.qml` — tighter top attachment and service preparation.
- `config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml` — expanded composition and exclusive section routing. - `config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml` — expanded composition and exclusive section routing.
- `config/dot/quickshell/modules/quicksettings/ControlSectionHeader.qml` — quiet section label and optional trailing action. - `config/dot/quickshell/modules/quicksettings/ControlSectionHeader.qml` — quiet section label and optional trailing action.
- `config/dot/quickshell/modules/quicksettings/HomeControls.qml` — four favourites, expanded configured list, loading/stale/failure states. - `config/dot/quickshell/modules/quicksettings/HomeControls.qml` — four favorites, expanded configured list, loading/stale/failure states.
- `config/dot/quickshell/modules/quicksettings/HomeTile.qml` — one domain-aware favourite tile. - `config/dot/quickshell/modules/quicksettings/HomeTile.qml` — one domain-aware favorite tile.
- `config/dot/quickshell/modules/quicksettings/PhoneControls.qml` — approved compact Continuity Card. - `config/dot/quickshell/modules/quicksettings/PhoneControls.qml` — approved compact Continuity Card.
- `config/dot/quickshell/modules/quicksettings/RecentExchange.qml` — one session-local completed transfer row. - `config/dot/quickshell/modules/quicksettings/RecentExchange.qml` — one session-local completed transfer row.
- `config/dot/quickshell/modules/bar/StatusCluster.qml` — reachable-phone glyph inside the existing click target. - `config/dot/quickshell/modules/bar/StatusCluster.qml` — reachable-phone glyph inside the existing click target.
@@ -340,7 +340,7 @@ Expected: all tests PASS and the fake server records no unexpected route.
- [ ] **Step 5: Add and run the redacted live contract** - [ ] **Step 5: Add and run the redacted live contract**
The contract runs `probe` and `snapshot`, validates the schema, and prints only The contract runs `probe` and `snapshot`, validates the schema, and prints only
`PASS (configured=true, N favourites; contents redacted)`. It must pipe JSON to `PASS (configured=true, N favorites; contents redacted)`. It must pipe JSON to
`jq` without printing friendly names, entity IDs, URL, or raw errors. `jq` without printing friendly names, entity IDs, URL, or raw errors.
- [ ] **Step 6: Commit Task 2** - [ ] **Step 6: Commit Task 2**
@@ -599,7 +599,7 @@ git commit -m "Build the Panama Control Center"
- [ ] **Step 1: Update desktop documentation** - [ ] **Step 1: Update desktop documentation**
Mark Home Assistant favourites and KDE Connect phone integration Live. Document Mark Home Assistant favorites and KDE Connect phone integration Live. Document
the 430-pixel Control Center, two-pixel bar attachment, environment/keyring the 430-pixel Control Center, two-pixel bar attachment, environment/keyring
configuration precedence, BlueBubbles messaging boundary, and current iOS configuration precedence, BlueBubbles messaging boundary, and current iOS
capabilities. Remove those two items from `System UI still worth adding` while capabilities. Remove those two items from `System UI still worth adding` while
@@ -628,7 +628,7 @@ inspect only the new process's journal for QML errors or warnings.
- [ ] **Step 4: Perform read-only live checks** - [ ] **Step 4: Perform read-only live checks**
Verify the Control Center reports one reachable phone with the confirmed action Verify the Control Center reports one reachable phone with the confirmed action
names and seven configured Home favourites without printing identities. Open names and seven configured Home favorites without printing identities. Open
and close the panel, expand Home and Phone, and confirm Wi-Fi, Bluetooth, audio, and close the panel, expand Home and Phone, and confirm Wi-Fi, Bluetooth, audio,
Focus, Caffeine, Night Light, settings, power, Escape, and outside-click remain Focus, Caffeine, Night Light, settings, power, Escape, and outside-click remain
usable. Do not trigger an external-device action. usable. Do not trigger an external-device action.
@@ -503,7 +503,7 @@ Accounts destinations while this feature was in review. Merge commit `4ef2f01`
preserves those routes and the newer Settings navigation architecture, keeps preserves those routes and the newer Settings navigation architecture, keeps
the stable `services` route rendered by `HealthPage`, and leaves the stable `services` route rendered by `HealthPage`, and leaves
`ServicesPage.qml` retired. Its useful Fedora handoffs for Users, Sharing, `ServicesPage.qml` retired. Its useful Fedora handoffs for Users, Sharing,
Colour profiles, and Digital wellbeing now live in the boundary-last System Color profiles, and Digital wellbeing now live in the boundary-last System
Health card alongside the existing network handoff, with focused static and Health card alongside the existing network handoff, with focused static and
isolated runtime coverage. isolated runtime coverage.
@@ -16,12 +16,12 @@ Panama gains a polished focus-session surface and a GNOME-like workspace overvie
## Signal Glass surface ## Signal Glass surface
- The main capsule is a top-centred blurred glass surface below the bar, carrying Panama's Prism edge, Tokyo Night Moon palette, and event-driven open/close motion. - The main capsule is a top-centered blurred glass surface below the bar, carrying Panama's Prism edge, Tokyo Night Moon palette, and event-driven open/close motion.
- Its primary row shows the focus icon, workspace label, remaining time, and whether DND and Caffeine are being held. - Its primary row shows the focus icon, workspace label, remaining time, and whether DND and Caffeine are being held.
- Clicking the primary row activates the bound workspace and dismisses the capsule without ending the session. - Clicking the primary row activates the bound workspace and dismisses the capsule without ending the session.
- The action row contains Pause or Resume, Workspace, and End. - The action row contains Pause or Resume, Workspace, and End.
- Workspace opens the Continuum overview with the session's bound workspace selected. - Workspace opens the Continuum overview with the session's bound workspace selected.
- A labelled close button hides the capsule while the session continues. - A labeled close button hides the capsule while the session continues.
- While a session exists, a compact bar indicator remains visible. Clicking it reopens the capsule, so dismissal never makes the running session undiscoverable. - While a session exists, a compact bar indicator remains visible. Clicking it reopens the capsule, so dismissal never makes the running session undiscoverable.
- No focus UI animates while idle. The only periodic update is the once-per-second remaining-time text while a session is running and not paused. - No focus UI animates while idle. The only periodic update is the once-per-second remaining-time text while a session is running and not paused.
@@ -107,7 +107,7 @@ Devices and Desktop & Dock. Its page contains three cards.
1. **Home Assistant** shows connection health, discovered-light count, Refresh, 1. **Home Assistant** shows connection health, discovered-light count, Refresh,
and Open Home Assistant. and Open Home Assistant.
2. **Control Center favourites** shows selected accessories as a two-column 2. **Control Center favorites** shows selected accessories as a two-column
card shelf. Each item exposes a drag handle, editable alias, source friendly card shelf. Each item exposes a drag handle, editable alias, source friendly
name, and Remove action. Dragging reorders the list; the first four are name, and Remove action. Dragging reorders the list; the first four are
explicitly marked as the resting Control Center shelf. explicitly marked as the resting Control Center shelf.
@@ -55,7 +55,7 @@ $ hyprctl getoption decoration:rounding -j → "int": 4
`eval` is also strictly more capable than `keyword` — it can set *any* config `eval` is also strictly more capable than `keyword` — it can set *any* config
value, including gradients, animation curves, and nested tables. It executes value, including gradients, animation curves, and nested tables. It executes
arbitrary Lua, so the existing "never interpolate UI text into a command" rule arbitrary Lua, so the existing "never interpolate UI text into a command" rule
must extend to it: values are validated and serialised numerically, never must extend to it: values are validated and serialized numerically, never
concatenated from user input. concatenated from user input.
### Finding 2: three disconnected sources of truth for the same three values ### Finding 2: three disconnected sources of truth for the same three values
@@ -206,7 +206,7 @@ promote the ~40 hardcoded `Settings.qml` values into real controls, and give
Appearance genuine content: accent pair, window rounding, gaps, border size, Appearance genuine content: accent pair, window rounding, gaps, border size,
blur, animation speed, bar height, font scale, and wallpaper. blur, animation speed, bar height, font scale, and wallpaper.
Appearance customisation stays inside the design language. The user picks how much Appearance customization stays inside the design language. The user picks how much
of it there is — spacing, softness, motion — not a free-form palette editor that of it there is — spacing, softness, motion — not a free-form palette editor that
would let the Prism identity be dismantled by accident. would let the Prism identity be dismantled by accident.
@@ -223,13 +223,13 @@ defaults, and Settings applies them live with `hyprctl eval`.
- No UI-supplied string is ever interpolated into an `eval`, a shell command, or - No UI-supplied string is ever interpolated into an `eval`, a shell command, or
a config value. Numbers are range-checked; choices are matched against an a config value. Numbers are range-checked; choices are matched against an
allow-list; colours are validated as hex before serialisation. allow-list; colors are validated as hex before serialization.
- A malformed or absent `settings.json` must degrade to shipped defaults, never - A malformed or absent `settings.json` must degrade to shipped defaults, never
to a broken compositor. to a broken compositor.
- `hyprctl keyword` is banned in this codebase. It exits 0 without acting. - `hyprctl keyword` is banned in this codebase. It exits 0 without acting.
- Every write path must be observably verified — read the value back rather than - Every write path must be observably verified — read the value back rather than
trusting an exit code. Finding 1 exists because an exit code was trusted. trusting an exit code. Finding 1 exists because an exit code was trusted.
- Tokyo Night Moon and Prism remain the only visual identity. Customisation - Tokyo Night Moon and Prism remain the only visual identity. Customization
adjusts its parameters, it does not replace it. adjusts its parameters, it does not replace it.
- Motion stays event-driven. No idle repaint, at any setting. - Motion stays event-driven. No idle repaint, at any setting.
- GNOME keeps ownership of hardware, accounts, printers, and users. - GNOME keeps ownership of hardware, accounts, printers, and users.
@@ -4,7 +4,7 @@
Panama will expand the existing top-right Quick Settings popover into a complete Panama will expand the existing top-right Quick Settings popover into a complete
Control Center. Wi-Fi, Bluetooth, Focus, Caffeine, Night Light, audio, account, Control Center. Wi-Fi, Bluetooth, Focus, Caffeine, Night Light, audio, account,
settings, and power remain in place. Home Assistant favourites and lightweight settings, and power remain in place. Home Assistant favorites and lightweight
iPhone continuity join that same surface instead of creating separate windows iPhone continuity join that same surface instead of creating separate windows
or permanent bar widgets. or permanent bar widgets.
@@ -18,7 +18,7 @@ the paired iPhone exposes only a useful subset of KDE Connect.
- Make the top-right popover feel like a complete desktop Control Center rather - Make the top-right popover feel like a complete desktop Control Center rather
than a small collection of connectivity toggles. than a small collection of connectivity toggles.
- Toggle the existing Home Assistant favourites without opening a browser. - Toggle the existing Home Assistant favorites without opening a browser.
- Preserve the user's seven enabled Home Assistant lights and use Home - Preserve the user's seven enabled Home Assistant lights and use Home
Assistant friendly names and live states. Assistant friendly names and live states.
- Show the paired iPhone's real reachability and expose Send File, Send - Show the paired iPhone's real reachability and expose Send File, Send
@@ -67,7 +67,7 @@ The resting panel is ordered by frequency and scope:
1. Wi-Fi, Bluetooth, Caffeine, and Night Light toggle grid. 1. Wi-Fi, Bluetooth, Caffeine, and Night Light toggle grid.
2. Focus and audio controls. 2. Focus and audio controls.
3. Home favourites. 3. Home favorites.
4. Phone continuity. 4. Phone continuity.
5. User, Panama Settings, and power footer. 5. User, Panama Settings, and power footer.
@@ -83,7 +83,7 @@ panel remains predictable.
## Home section ## Home section
The resting Home card shows the first four enabled favourites in their saved The resting Home card shows the first four enabled favorites in their saved
order. Each tile contains a domain-appropriate glyph, a friendly name, and a order. Each tile contains a domain-appropriate glyph, a friendly name, and a
plain state such as `On`, `Off`, or `Unavailable`. Enabled lights use a quiet plain state such as `On`, `Off`, or `Unavailable`. Enabled lights use a quiet
amber light treatment; Panama blue remains reserved for desktop selection and amber light treatment; Panama blue remains reserved for desktop selection and
@@ -96,7 +96,7 @@ without a toast or Signal Glass event. A failure restores the last known state
and displays a concise inline error inside the Home section. and displays a concise inline error inside the Home section.
The trailing `N accessories` action expands a full list of configured The trailing `N accessories` action expands a full list of configured
favourites inside the Control Center. No unconfigured Home Assistant entity is favorites inside the Control Center. No unconfigured Home Assistant entity is
shown. An `Open Home Assistant` action launches the configured base URL. shown. An `Open Home Assistant` action launches the configured base URL.
If Home Assistant is unreachable, Panama retains the last good states for the If Home Assistant is unreachable, Panama retains the last good states for the
@@ -165,7 +165,7 @@ reliable byte progress for this device.
Add `scripts/panama-home-assistant`, a small local bridge with four commands: Add `scripts/panama-home-assistant`, a small local bridge with four commands:
- `probe` reports configuration and API availability without entity details; - `probe` reports configuration and API availability without entity details;
- `snapshot` returns only configured favourites and normalized state; - `snapshot` returns only configured favorites and normalized state;
- `toggle ENTITY_ID` calls the Home Assistant service for an allow-listed - `toggle ENTITY_ID` calls the Home Assistant service for an allow-listed
configured entity; and configured entity; and
- `open` launches the configured Home Assistant URL. - `open` launches the configured Home Assistant URL.
@@ -212,7 +212,7 @@ outside-click behavior remain intact.
- Helpers never echo tokens, Authorization headers, clipboard text, entity - Helpers never echo tokens, Authorization headers, clipboard text, entity
response bodies, IP addresses, or full local file paths to logs. response bodies, IP addresses, or full local file paths to logs.
- Home Assistant commands accept only entity IDs present in the configured - Home Assistant commands accept only entity IDs present in the configured
favourites snapshot. favorites snapshot.
- KDE Connect device IDs and file paths are passed as separate process - KDE Connect device IDs and file paths are passed as separate process
arguments, never interpolated into a shell command. arguments, never interpolated into a shell command.
- A user click is required for every Home toggle, phone ring, clipboard send, - A user click is required for every Home toggle, phone ring, clipboard send,
@@ -231,7 +231,7 @@ outside-click behavior remain intact.
is offered. is offered.
- Invalid or expired token: Home shows `Authentication required` without - Invalid or expired token: Home shows `Authentication required` without
exposing the response body. exposing the response body.
- One malformed entity is omitted without suppressing healthy favourites. - One malformed entity is omitted without suppressing healthy favorites.
## Testing and verification ## Testing and verification
@@ -261,7 +261,7 @@ checks require an explicit user action from the finished Control Center.
## Documentation updates ## Documentation updates
- Update `config/dot/hypr/DESKTOP-PARITY.md` to mark Home Assistant favourites - Update `config/dot/hypr/DESKTOP-PARITY.md` to mark Home Assistant favorites
and KDE Connect phone integration live. and KDE Connect phone integration live.
- Document Control Center behavior, credential resolution, and iOS limitations - Document Control Center behavior, credential resolution, and iOS limitations
in `config/dot/hypr/README.md`. in `config/dot/hypr/README.md`.
@@ -52,7 +52,7 @@ The feature is deliberately local-first. Screen pixels are captured into Panama'
- Tokyo Night Moon tokens are the only colors. - Tokyo Night Moon tokens are the only colors.
- Processing uses a static status treatment; there is no spinner, pulse, shimmer, or idle animation. - Processing uses a static status treatment; there is no spinner, pulse, shimmer, or idle animation.
- OCR text is readable interface typography in a selectable editor, not a terminal-like dump. - OCR text is readable interface typography in a selectable editor, not a terminal-like dump.
- The primary action is Copy. Network-leaving actions are visually secondary and clearly labelled. - The primary action is Copy. Network-leaving actions are visually secondary and clearly labeled.
## Settings and discoverability ## Settings and discoverability
+6 -6
View File
@@ -77,7 +77,7 @@ for dir in "${dirs[@]}"; do
done done
# tmux.conf ends with a source-file of current-theme.conf, generated from the # tmux.conf ends with a source-file of current-theme.conf, generated from the
# colour scheme rather than committed. Seed it so a fresh checkout starts themed # color scheme rather than committed. Seed it so a fresh checkout starts themed
# -- the source-file is -q, so a missing file is silent, which would leave tmux # -- the source-file is -q, so a missing file is silent, which would leave tmux
# unstyled with nothing to explain it. # unstyled with nothing to explain it.
TMUX_THEME="$PANAMA_DOT/tmux/current-theme.conf" TMUX_THEME="$PANAMA_DOT/tmux/current-theme.conf"
@@ -95,9 +95,9 @@ elif [ -d "$PANAMA_DOT/tmux/themes" ]; then
log "Seeded tmux $tmux_scheme theme ($tmux_name) → $TMUX_THEME" log "Seeded tmux $tmux_scheme theme ($tmux_name) → $TMUX_THEME"
fi fi
# hyprlock.conf is generated from a template on every colour scheme change and # hyprlock.conf is generated from a template on every color scheme change and
# is not committed. Seed it so the FIRST lock of a fresh install is themed -- # is not committed. Seed it so the FIRST lock of a fresh install is themed --
# without it hyprlock falls back to its own defaults, which is a bare grey # without it hyprlock falls back to its own defaults, which is a bare gray
# screen with none of this desktop's identity, and the first time anyone would # screen with none of this desktop's identity, and the first time anyone would
# find out is when they walked away from the machine. # find out is when they walked away from the machine.
HYPRLOCK_TEMPLATE="$PANAMA_DOT/hypr/hyprlock.conf.template" HYPRLOCK_TEMPLATE="$PANAMA_DOT/hypr/hyprlock.conf.template"
@@ -176,9 +176,9 @@ for gtk_version in 3.0 4.0; do
done done
# kitty.conf ends with `include current-theme.conf`, and that file is generated # kitty.conf ends with `include current-theme.conf`, and that file is generated
# from the desktop colour scheme rather than committed -- it is machine state. # from the desktop color scheme rather than committed -- it is machine state.
# A fresh checkout therefore has no such file, and kitty starts by complaining # A fresh checkout therefore has no such file, and kitty starts by complaining
# about a missing include and falling back to its stock colours. Seed it from # about a missing include and falling back to its stock colors. Seed it from
# the scheme in settings.json (dark unless the user has chosen otherwise) so a # the scheme in settings.json (dark unless the user has chosen otherwise) so a
# first launch is themed; ColorScheme.qml overwrites it on every change after. # first launch is themed; ColorScheme.qml overwrites it on every change after.
KITTY_THEME="$PANAMA_DOT/kitty/current-theme.conf" KITTY_THEME="$PANAMA_DOT/kitty/current-theme.conf"
@@ -226,7 +226,7 @@ done
# the repository and expose it with a narrow per-file symlink. # the repository and expose it with a narrow per-file symlink.
# Panama ships its own application icons. Without these the desktop entries fall # Panama ships its own application icons. Without these the desktop entries fall
# back to a generic symbolic glyph, which is drawn for 16px toolbar use and # back to a generic symbolic glyph, which is drawn for 16px toolbar use and
# looks wrong beside full-colour application icons in the dock. # looks wrong beside full-color application icons in the dock.
PANAMA_ICON_DIR="$PANAMA_PATH/config/local/share/icons/hicolor/scalable/apps" PANAMA_ICON_DIR="$PANAMA_PATH/config/local/share/icons/hicolor/scalable/apps"
USER_ICON_DIR="$HOME/.local/share/icons/hicolor/scalable/apps" USER_ICON_DIR="$HOME/.local/share/icons/hicolor/scalable/apps"
mkdir -p "$USER_ICON_DIR" mkdir -p "$USER_ICON_DIR"
+4 -4
View File
@@ -48,11 +48,11 @@ grep -q 'timeout = 0' "$generated" && fail 'a zero timeout was written as an imm
grep -qE '^listener' "$generated" && fail 'a listener was written when everything is set to never' grep -qE '^listener' "$generated" && fail 'a listener was written when everything is set to never'
grep -q 'after_sleep_cmd' "$generated" || fail 'the general block was lost when all timers are off' grep -q 'after_sleep_cmd' "$generated" || fail 'the general block was lost when all timers are off'
# ── Values are honoured ────────────────────────────────────────────────────── # ── Values are honored ──────────────────────────────────────────────────────
run_with '{"screenBlankMinutes":2,"lockMinutes":7,"suspendMinutes":45}' run_with '{"screenBlankMinutes":2,"lockMinutes":7,"suspendMinutes":45}'
grep -q 'timeout = 120' "$generated" || fail '2 minute blank not honoured' grep -q 'timeout = 120' "$generated" || fail '2 minute blank not honored'
grep -q 'timeout = 420' "$generated" || fail '7 minute lock not honoured' grep -q 'timeout = 420' "$generated" || fail '7 minute lock not honored'
grep -q 'timeout = 2700' "$generated" || fail '45 minute suspend not honoured' grep -q 'timeout = 2700' "$generated" || fail '45 minute suspend not honored'
grep -q 'systemctl suspend' "$generated" || fail 'suspend listener missing when set' grep -q 'systemctl suspend' "$generated" || fail 'suspend listener missing when set'
# ── lockOnSleep off removes the pre-sleep lock ─────────────────────────────── # ── lockOnSleep off removes the pre-sleep lock ───────────────────────────────
+2 -2
View File
@@ -4,7 +4,7 @@
# monitors.lua. It is therefore the one piece of Panama that can cost the user a # monitors.lua. It is therefore the one piece of Panama that can cost the user a
# working compositor rather than merely a working feature. # working compositor rather than merely a working feature.
# #
# This contract pins the only behaviour that matters: whatever is in the # This contract pins the only behavior that matters: whatever is in the
# settings file -- including nothing, garbage, or values of the wrong type -- # settings file -- including nothing, garbage, or values of the wrong type --
# the config still parses, and every setting either takes the stored value or # the config still parses, and every setting either takes the stored value or
# falls back to the value shipped in the Lua. # falls back to the value shipped in the Lua.
@@ -106,7 +106,7 @@ result="$(probe '{"note": "a \"quoted\" value\nwith escapes", "nested": {"a": [1
# defaults is only useful if Hyprland still accepts the config around it. # defaults is only useful if Hyprland still accepts the config around it.
# #
# Hyprland resolves its own config from $HOME/.config/hypr regardless of # Hyprland resolves its own config from $HOME/.config/hypr regardless of
# XDG_CONFIG_HOME, while prefs.lua honours XDG_CONFIG_HOME. That asymmetry is # XDG_CONFIG_HOME, while prefs.lua honors XDG_CONFIG_HOME. That asymmetry is
# what makes this loop useful rather than vacuous: the *real* looks.lua and # what makes this loop useful rather than vacuous: the *real* looks.lua and
# input.lua are parsed against a *fixture* settings file. The probe cases above # input.lua are parsed against a *fixture* settings file. The probe cases above
# already establish that prefs.lua reads the fixture and not the live file. # already establish that prefs.lua reads the fixture and not the live file.
+1 -1
View File
@@ -69,7 +69,7 @@ fi
# ── Signal strength is a ratio, and the labels must reflect that ───────────── # ── Signal strength is a ratio, and the labels must reflect that ─────────────
while IFS='|' read -r value expect; do while IFS='|' read -r value expect; do
got="$(run ipc call connectivity-test labelFor "$value")" got="$(run ipc call connectivity-test labelFor "$value")"
[[ "$got" == "$expect" ]] || fail "signal $value labelled '$got', expected '$expect'" [[ "$got" == "$expect" ]] || fail "signal $value labeled '$got', expected '$expect'"
done <<'CASES' done <<'CASES'
1.0|Excellent 1.0|Excellent
0.85|Excellent 0.85|Excellent

Some files were not shown because too many files have changed in this diff Show More