diff --git a/README.md b/README.md index 87ace67..a7cf0fc 100644 --- a/README.md +++ b/README.md @@ -73,18 +73,21 @@ the comments of the components themselves, because knowing what a thing was modelled on explains why it behaves the way it does. GNOME is not gone from the machine: `gnome-control-center` is a declared -dependency, and Panama's own Settings hands off to it for the panels it -deliberately does not own — Online Accounts, Color, Sound, Network, Keyboard, -Privacy, Wellbeing, Accessibility, and System for users, date and time, region -and remote desktop. The allow-list in -[`services/SystemSettings.qml`](config/dot/quickshell/services/SystemSettings.qml) -is what decides; anything not on it is a panel Panama owns itself. +dependency, and two rows in Panama's own Settings still open it. Adding an +online account goes through the provider's dialog, because the OAuth sign-in +runs inside a library Fedora ships without a binding anything else can call. +And Digital wellbeing — screen time and break reminders — is the one panel of +GNOME's that still does something Panama does not. Everything else it used to +hand over is a page here now, and +[`tests/quickshell/gnome-handoff-contract`](tests/quickshell/gnome-handoff-contract) +fails the build if a door back opens onto a panel Panama owns, because those +rows kept working perfectly long after they stopped being true. Displays is one of the panels it owns, and the only one where a wrong answer can leave you unable to see well enough to undo it. So every change there is applied as one complete layout, read back from the compositor, and reverted after fifteen seconds unless you keep it — resolution, scale, rotation, -position and primary display, and now colour profile, bit depth, SDR +position and primary display, and now color profile, bit depth, SDR brightness and saturation, and mirroring with them. Two of those opt out of part of that, for reasons rather than convenience. A per-display variable refresh rate override is applied but never verified, because `hyprctl` reports @@ -100,7 +103,7 @@ DDC, and the buttons on the bezel change it behind our back. | `config/dot/hypr/` | Compositor config. **Lua, not hyprlang** — see its README | | `config/dot/quickshell/` | The shell: bar, dock, Continuum overview, Settings, Screen Intelligence, focus sessions, quick settings, notifications, screenshot UI | | `config/containers/` | Container definitions systemd runs as units — currently the speech-to-text server behind dictation | -| `config/dot/vicinae/` | Raycast-style launcher, themed. Its commands live in `config/local/share/vicinae/` — script commands (settings deep links, power menu, reminders, window switcher, kill process, SSH hosts, recent files, color picker), and one compiled extension that adds web search with live suggestions. File search, calculator, clipboard, and emoji are Vicinae's own | +| `config/dot/vicinae/` | Raycast-style launcher, themed. Its commands live in `config/local/share/vicinae/` — script commands (settings deep links, power actions, projects, reminders, window switcher, kill process, SSH hosts, recent files, color picker), and one compiled extension that adds web search with live suggestions. File search, calculator, clipboard, and emoji are Vicinae's own | | `config/dot/uwsm/` | Session environment (see the uwsm caveat in the hypr README) | | `config/dot/wofi/` | Fallback launcher, in case the shell fails to start | | `config/dot/xdg-desktop-portal/` | Portal backend routing | @@ -108,7 +111,11 @@ DDC, and the buttons on the bezel change it behind our back. New machines get a welcome card on first start, teaching the handful of keys that matter and reachable afterwards from the launcher. `SUPER + /` shows every shortcut the machine has, read from the live keymap so a rebind appears there -without anything being kept in sync. Settings carries a +without anything being kept in sync. Rebinding is Settings' job, and so is +adding a shortcut of your own or assigning the four-finger touchpad swipes: an +entry names an application, a shell action or a window move rather than +carrying a command, so a hand-edited settings file can move a key but cannot +make one run something arbitrary. Settings carries a [manual](config/dot/quickshell/manual/) written for the person using the desktop rather than the person building it, opening with a chapter for people arriving from GNOME, macOS or Windows. @@ -132,7 +139,8 @@ migrations/ One repair per file, for machines installed before a change config/ bash/ .bashrc, aliases, env (env is gitignored) copy/ Files copied verbatim over / (needs sudo) - dot/ Symlinked into ~/.config + dot/ Symlinked into ~/.config; quickshell/manual/ is the manual + Settings renders firefox/ Vendored Firefox chrome, linked into the browser profile containers/ Quadlets, linked into ~/.config/containers/systemd local/ Icons, the cursor theme, and the launcher's commands and @@ -145,7 +153,6 @@ setup/ packages/ One package per line; extras/ holds the optional categories scripts/ Run in order by ./install tests/ Contracts. See below - manual/ The user manual, rendered in Settings docs/ Settings reference, and the design specs behind the work ``` @@ -210,7 +217,7 @@ readable, and an indented line belongs to the entry above it — which is how OB carries its sixteen plugin extensions as one thing to tick. Hooks are the extension point: drop a script at `~/.config/panama/hooks/theme-set` -and it runs whenever the colour scheme changes, with the scheme and accent as +and it runs whenever the color scheme changes, with the scheme and accent as arguments. Same for `post-upgrade` and `post-migrate`, and a `.d/` directory beside each so several things can react without fighting over one file. A broken hook is reported and stepped over, never fatal. Samples are @@ -236,6 +243,8 @@ the current upstream and reports a failure rather than working around it. Adding one is adding a file to `setup/apps/`, and the file has to say why the exception exists. -None of the scripts in this repository carry a `.sh` extension. A shebang and -the executable bit already select the interpreter, and the extension only +No script in this repository carries a `.sh` extension, with one deliberate +exception: each repair in `migrations/` is named `.sh`, because +`panama-migrate` finds them by globbing exactly that. Everywhere else a shebang +and the executable bit already select the interpreter, and the extension only becomes something to keep in sync — which it did not stay. diff --git a/config/dot/quickshell/manual/01-coming-from-another-desktop.md b/config/dot/quickshell/manual/01-coming-from-another-desktop.md index 1ea643a..e6ecbb8 100644 --- a/config/dot/quickshell/manual/01-coming-from-another-desktop.md +++ b/config/dot/quickshell/manual/01-coming-from-another-desktop.md @@ -49,5 +49,32 @@ clock in the middle and status on the right. There is still a dock. Volume and brightness keys still work, and still work on the lock screen. Copy and paste are unchanged. Your files are where you left them. -GNOME Settings is still installed for the few panels Panama deliberately does -not own, and Panama's own Settings hands off to it where that applies. +## Where the settings went + +`Super + I` opens Panama's own Settings. The sidebar is where GNOME's panels +ended up, and it is worth one read so you never have to hunt: + +| If you are looking for | It is under | +|---|---| +| Themes, wallpaper, fonts, text size, titlebars | Appearance | +| The bar, the dock, Control Center, tiling, workspaces | Shell | +| Resolution, scale, arrangement, night light | Displays | +| Output and input devices, per-application volume | Sound | +| Do Not Disturb, per-application rules, focus modes | Notifications & Focus | +| Shortcuts, mouse and touchpad, dictation | Input | +| Wi-Fi and VPN, the firewall, sharing, printers | Network & Sharing | +| Default applications, autostart, gaming, Screen Intelligence | Applications | +| The accounts on this machine, and online ones | Users & Accounts | +| Permissions, saved passwords, what the machine remembers, SSH keys | Privacy & Security | +| Battery, the lid, the power button, idle and lock timings | Power & Lock | +| Magnifier, color filters, reduced motion, screen reader | Accessibility | +| Updates, storage, snapshots, date and time, backups | System | + +Home is the page `Super + I` lands on. The search box above the sidebar +searches the settings themselves rather than the page names, so typing what you +want to change is usually faster than remembering which of those it lives in. + +GNOME Settings is still installed, and Panama's Settings opens it in exactly +two places: adding an online account, which has to go through the provider's +own dialog, and Digital wellbeing, which is the one panel of GNOME's that does +something Panama does not. diff --git a/config/dot/quickshell/manual/02-the-keyboard.md b/config/dot/quickshell/manual/02-the-keyboard.md index 6ecbcd7..e779e09 100644 --- a/config/dot/quickshell/manual/02-the-keyboard.md +++ b/config/dot/quickshell/manual/02-the-keyboard.md @@ -58,3 +58,9 @@ one I have". Settings has a [Keyboard page](panama://settings/shortcuts), under Input, listing every bind, each of which can be reassigned. A rebind moves the shortcut and cannot change what it does, so there is no way to make a key do something unexpected by editing it. + +Shortcuts you invent live in the same list, in a **Custom** group above the +rest. One of those names an application, a shell action or a window move rather +than storing a command — for the same reason: what you are choosing is which +key, not what it runs. A chord something already holds is refused rather than +stolen, so a new shortcut can never quietly cost you an old one. diff --git a/config/dot/quickshell/manual/03-windows-and-workspaces.md b/config/dot/quickshell/manual/03-windows-and-workspaces.md index b1e1a22..daf2668 100644 --- a/config/dot/quickshell/manual/03-windows-and-workspaces.md +++ b/config/dot/quickshell/manual/03-windows-and-workspaces.md @@ -28,9 +28,11 @@ follows you between workspaces. A floating window can be moved by holding ## Workspaces -Ten of them, on `Alt + 1` through `Alt + 0`. `Alt + H` and `Alt + L` step -between neighbours, and `Alt + Shift` plus a number sends the current window -somewhere without following it. +Ten you can jump straight to, on `Alt + 1` through `Alt + 0`, though they are +created and destroyed as you use them rather than sitting there empty. `Alt + H` +and `Alt + L` step between neighbours — right past the last one makes a new one, +left from the first stays put — and `Alt + Shift` plus a number sends the +current window somewhere without following it. On a machine with more than one display, workspaces belong to whichever screen has focus. If you would rather pin workspaces one through ten to your @@ -41,6 +43,29 @@ How windows share the space — the layout, the master area, gaps and window edges — is Shell › Tiling. The workspace switches themselves, and the projects below, are Shell › Workspaces. +## The touchpad + +Three fingers sideways moves between workspaces and follows your fingers as it +goes; three fingers up opens the overview and three down closes it. Those are +GNOME's gestures, reproduced, and they do not move. + +Four fingers are yours. Each of the four directions can be given a job — a shell +action, a window or workspace move — from the same list your own shortcuts pick +from, on Input › Mouse & Touchpad. Assigning one reloads the compositor, so +expect a beat of black before it works. Unassigned directions are left alone +rather than bound to nothing. + +## Rules for one application + +Some windows want the same treatment every time: the password manager should +float, chat should open on workspace four, a picture viewer should not be +resized to fill half the screen. **Application window rules** on Shell › Tiling is where that +is written down. You pick an application and tick what should happen to it — +float, center it, pin it to every workspace, give it a fixed size or a +workspace to open on — rather than writing a match pattern. Panama's own +surfaces cannot be targeted, and a rule with nothing ticked is refused rather +than saved as a row you would wonder about later. + ## Projects A project is the set of windows you open together: which applications, which diff --git a/config/dot/quickshell/manual/04-when-something-breaks.md b/config/dot/quickshell/manual/04-when-something-breaks.md index 78ec38b..44010f1 100644 --- a/config/dot/quickshell/manual/04-when-something-breaks.md +++ b/config/dot/quickshell/manual/04-when-something-breaks.md @@ -10,8 +10,8 @@ From a terminal, the same check is `panama doctor`. ## The shell is gone -If the bar and dock disappear, the Quickshell process died. **Restart Shell** -from the launcher brings it back. If the launcher is also gone, press +If the bar and dock disappear, the Quickshell process died. **Restart Desktop +Shell** from the launcher brings it back. If the launcher is also gone, press `Super + Shift + R` for the fallback launcher, which depends on nothing but itself and exists for exactly this. @@ -32,7 +32,7 @@ Settings has a [Displays page](panama://settings/displays). It opens on a pictur one display or several — and everything under that picture belongs to whichever one you have selected. Every change there reverts itself after fifteen seconds unless you confirm it, so a mode your monitor cannot show -cannot strand you, and that covers colour, bit depth and mirroring as well as +cannot strand you, and that covers color, bit depth and mirroring as well as resolution, scale and rotation. If you are already stranded, `hyprctl monitors` from a terminal shows what is applied. @@ -45,6 +45,29 @@ labelled as an unverified claim, beside the real action. If you did not expect it at all, cancel. Nothing that needs privileges is harmed by being refused. +## A button did nothing except change its wording + +That is the button working. Anything in Settings that removes something takes +two presses: the first arms it, and the row then shows what is about to happen +beside a **Keep** that backs out. The second press is the one that does it. + +Only one button in Settings is armed at a time, so arming a second one disarms +the first, wherever it was. + +## The network I need is not in the list + +The Wi-Fi list is what is in range. **Saved networks**, further down +[Connections](panama://settings/connectivity), is what this machine remembers, +including the ones nowhere near you — which is where to tidy up a profile you +want rid of, rather than waiting until you are standing next to it again. A +network that broadcasts no name is joined with **Join a hidden network…**, at +the bottom of the list. + +Opening a connection shows what it actually got — address, gateway, DNS — and +is where you give it a fixed address instead when what it got does not work, +or mark it metered so updates and large downloads wait until you are somewhere +else. + ## After an update, something is not right `panama migrate` applies repairs this machine has not had yet. It is safe to diff --git a/config/dot/quickshell/modules/dock/DockPickOverlay.qml b/config/dot/quickshell/modules/dock/DockPickOverlay.qml index 085ffbe..da7df99 100644 --- a/config/dot/quickshell/modules/dock/DockPickOverlay.qml +++ b/config/dot/quickshell/modules/dock/DockPickOverlay.qml @@ -1,4 +1,4 @@ -// "Add app to dock", reachable without opening Settings. +// "Add application to dock", reachable without opening Settings. // // The Settings page is the place to curate the whole dock -- reorder it, unpin // things, change how it hides. Adding one application is a single decision made @@ -7,7 +7,7 @@ // // It embeds the same DockAppPicker the Dock page uses rather than growing a // second search: the exclusion of already-pinned applications, the icon lookup -// and the "nothing until you type" behaviour are all already there, and two +// and the "nothing until you type" behavior are all already there, and two // copies of them would drift. import Quickshell @@ -125,7 +125,7 @@ PanelWindow { Text { id: title - text: "Add app to dock" + text: "Add application to dock" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge diff --git a/config/dot/quickshell/modules/settings/AccentEditor.qml b/config/dot/quickshell/modules/settings/AccentEditor.qml index f591f2b..785ac6e 100644 --- a/config/dot/quickshell/modules/settings/AccentEditor.qml +++ b/config/dot/quickshell/modules/settings/AccentEditor.qml @@ -3,7 +3,7 @@ // This is the fine-tune behind a disclosure, not the fast path: the four wells // above cover what most edits are, and these six rows are for the last few // degrees. Each row is a labelled, keyboard-operable slider with a numeric -// readout, because a hue ring alone tells someone with a colour vision +// readout, because a hue ring alone tells someone with a color vision // deficiency nothing. // // Committed on a debounce rather than per move. Each move used to write BOTH @@ -140,27 +140,27 @@ Column { HsvRow { target: "primary"; channel: "h"; channelValue: editor.primaryHsv.h; channelMaximum: 360 - suffix: "°"; label: "Primary hue"; detail: "Colour family, measured from 0 to 360 degrees" + suffix: "°"; label: "Primary hue"; detail: "Color family, measured from 0 to 360 degrees" } HsvRow { target: "primary"; channel: "s"; channelValue: editor.primaryHsv.s; channelMaximum: 100 - label: "Primary saturation"; detail: "Colour intensity from grey to vivid" + label: "Primary saturation"; detail: "Color intensity from grey to vivid" } HsvRow { target: "primary"; channel: "v"; channelValue: editor.primaryHsv.v; channelMaximum: 100 - label: "Primary value"; detail: "Brightness from black to full colour" + label: "Primary value"; detail: "Brightness from black to full color" } HsvRow { target: "secondary"; channel: "h"; channelValue: editor.secondaryHsv.h; channelMaximum: 360 - suffix: "°"; label: "Secondary hue"; detail: "Colour family at the far end of the Prism gradient" + suffix: "°"; label: "Secondary hue"; detail: "Color family at the far end of the Prism gradient" } HsvRow { target: "secondary"; channel: "s"; channelValue: editor.secondaryHsv.s; channelMaximum: 100 - label: "Secondary saturation"; detail: "Colour intensity from grey to vivid" + label: "Secondary saturation"; detail: "Color intensity from grey to vivid" } HsvRow { target: "secondary"; channel: "v"; channelValue: editor.secondaryHsv.v; channelMaximum: 100 - label: "Secondary value"; detail: "Brightness from black to full colour" + label: "Secondary value"; detail: "Brightness from black to full color" divider: false } @@ -176,7 +176,7 @@ Column { } // Hands the sliders back to the stored pair. If the write was refused they - // snap back to what is really in effect rather than showing a colour + // snap back to what is really in effect rather than showing a color // nothing accepted. Timer { id: releaseTimer diff --git a/config/dot/quickshell/modules/settings/AccessibilityPage.qml b/config/dot/quickshell/modules/settings/AccessibilityPage.qml index 54edefe..dc94d4e 100644 --- a/config/dot/quickshell/modules/settings/AccessibilityPage.qml +++ b/config/dot/quickshell/modules/settings/AccessibilityPage.qml @@ -1,6 +1,6 @@ // Accessibility. // -// Organised by what a person came here unable to do -- see it, tolerate the +// Organized by what a person came here unable to do -- see it, tolerate the // motion, hear it, reach it from the keyboard, have it read aloud -- rather // than by which subsystem happens to implement each control. The old page was // grouped by mechanism (Pointer / Text / Motion / Magnifier / Contrast), which @@ -112,7 +112,11 @@ SettingsPage { // the preference at launch, and re-applying the value it is already // running would be a hyprctl call for nothing every time this page // opens. - ChoiceRow { + // + // A picker rather than a segmented control: five options with a + // sentence under each get ninety pixels apiece in a strip, which is + // room for neither. + OptionPickerRow { id: colorFilterRow property string appliedFilter: "" @@ -164,26 +168,21 @@ SettingsPage { title: "Hearing" subtitle: "What the desktop does instead of making a sound." - ToggleRow { setting: "visualAlerts" } + ToggleRow { setting: "visualAlerts"; divider: false } - SettingRow { - label: "Mono audio" - detail: "Not offered yet — folding stereo into one channel is a real change to the PipeWire graph, and a switch that only looked like it did that would be worse than its absence. Balance lives on the Sound page meanwhile." - controlWidth: 90 - divider: false + Item { width: 1; height: 10 } - SoundBadge { - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: "Not yet" - tone: Theme.fgMuted - } + // Said the same way the keyboard card says its absence, rather than as + // a row wearing a badge that stands for a control it never had. + SettingsNote { + headline: "Mono audio is not offered yet" + body: "Folding stereo into one channel is a real change to the PipeWire graph, and a switch that only looked like it did that would be worse than its absence. Balance lives on the Sound page meanwhile." } } SettingsCard { title: "Keyboard & pointer" - subtitle: "Key behaviour lives with the Keyboard settings; what the pointer does when you stop moving it lives here." + subtitle: "Key behavior lives with the Keyboard settings; what the pointer does when you stop moving it lives here." ActionRow { label: "Key repeat" @@ -219,7 +218,7 @@ SettingsPage { anchors.verticalCenter: parent.verticalCenter spacing: 9 - SoundBadge { + StatusBadge { anchors.verticalCenter: parent.verticalCenter text: Accessibility.orcaRunning ? "Running" : "Stopped" tone: Accessibility.orcaRunning ? Theme.ok : Theme.fgMuted @@ -239,12 +238,12 @@ SettingsPage { } SettingRow { - label: "This app, read aloud" + label: "This application, read aloud" detail: "Every settings row carries a spoken name and takes keyboard focus — Tab walks the page, Space flips a switch, the arrows move a slider or step a choice" controlWidth: 90 divider: false - SoundBadge { + StatusBadge { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Built in" diff --git a/config/dot/quickshell/modules/settings/AppearancePage.qml b/config/dot/quickshell/modules/settings/AppearancePage.qml index c88c8f8..a568619 100644 --- a/config/dot/quickshell/modules/settings/AppearancePage.qml +++ b/config/dot/quickshell/modules/settings/AppearancePage.qml @@ -20,7 +20,7 @@ SettingsPage { property string expandedPicker: "" // Whether the accent's HSV fine-tune is open. Collapsed by default: it is - // six sliders that answer a question the four colour wells above have + // six sliders that answer a question the four color wells above have // usually already answered. property bool fineTuneOpen: false @@ -270,7 +270,7 @@ SettingsPage { Text { width: parent.width - text: "Primary, secondary, background, foreground, effects — every colour yours, saved as a theme that lives in the galleries above" + text: "Primary, secondary, background, foreground, effects — every color yours, saved as a theme that lives in the galleries above" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall @@ -316,15 +316,10 @@ SettingsPage { : "Based on " + ThemeProfiles.activeProfile.name + " · every change previews live on the real desktop" - Text { - width: parent.width - visible: wells.lastError !== "" || ColorScheme.lastError !== "" - text: wells.lastError !== "" ? wells.lastError : ColorScheme.lastError - color: Theme.warn - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - wrapMode: Text.WordWrap - bottomPadding: 10 + ErrorRow { + label: "The theme needs attention" + message: wells.lastError !== "" ? wells.lastError : ColorScheme.lastError + divider: true } ThemeStartChips { @@ -422,7 +417,7 @@ SettingsPage { SettingRow { label: "Application" - detail: "GTK and libadwaita apps that follow the desktop default" + detail: "GTK and libadwaita applications that follow the desktop default" divider: false controlWidth: 8 } diff --git a/config/dot/quickshell/modules/settings/ApplicationsPage.qml b/config/dot/quickshell/modules/settings/ApplicationsPage.qml index 1848706..74f2fdc 100644 --- a/config/dot/quickshell/modules/settings/ApplicationsPage.qml +++ b/config/dot/quickshell/modules/settings/ApplicationsPage.qml @@ -268,24 +268,20 @@ SettingsPage { // a process each and only this page wants them, so a shell start where // nobody opens Applications pays for neither. Component.onCompleted: { - AppLibrary.refresh(); - AppLibrary.refreshCatalog(); + if (!AppLibrary.flatpaksLoaded) + AppLibrary.refresh(); + if (!AppLibrary.catalogLoaded) + AppLibrary.refreshCatalog(); } - TextRow { - visible: DefaultApps.lastError !== "" + ErrorRow { label: "Application settings need attention" - detail: DefaultApps.lastError - value: "" - divider: false + message: DefaultApps.lastError } - TextRow { - visible: String(AppLibrary.lastError ?? "") !== "" + ErrorRow { label: "The application list needs attention" - detail: String(AppLibrary.lastError ?? "") - value: "" - divider: false + message: String(AppLibrary.lastError ?? "") } // ── What is installed ──────────────────────────────────────────────────── @@ -361,7 +357,7 @@ SettingsPage { TextRow { visible: root.installedApps.length > root.installedLimit label: (root.installedApps.length - root.installedLimit) + " more" - detail: "Search finds any of them by name or by application id." + detail: "Search finds any of them by name or by application id" value: "" divider: false } @@ -369,7 +365,7 @@ SettingsPage { TextRow { visible: (AppLibrary.apps ?? []).length > 0 && root.installedApps.length === 0 label: "Nothing matches that" - detail: "Both the application's name and its id are searched." + detail: "Both the application's name and its id are searched" value: "" divider: false } @@ -449,7 +445,7 @@ SettingsPage { label: AppLibrary.catalogLoaded ? "The catalog is empty" : "Reading the catalog…" - detail: "It is parsed from setup/packages/extras, the same files the command line uses." + detail: "It is parsed from setup/packages/extras, the same files the command line uses" value: "" divider: false } @@ -547,7 +543,7 @@ SettingsPage { TextRow { visible: !DefaultApps.busy && DefaultApps.autostartEntries.length === 0 label: "No user autostart entries" - detail: "Applications can add entries to ~/.config/autostart." + detail: "Applications can add entries to ~/.config/autostart" value: "" } @@ -612,7 +608,7 @@ SettingsPage { // once at launch and give no sign that nothing had happened. TextRow { label: "Compositor autostart" - detail: "Started from the Hyprland configuration, and read-only here." + detail: "Started from the Hyprland configuration, and read-only here" value: DefaultApps.luaAutostartEntries.length > 0 ? DefaultApps.luaAutostartEntries.length + (DefaultApps.luaAutostartEntries.length === 1 ? " entry" : " entries") @@ -622,7 +618,7 @@ SettingsPage { TextRow { visible: !DefaultApps.busy && DefaultApps.luaAutostartEntries.length === 0 label: "No compositor entries found" - detail: "No hl.exec_cmd entries were found in config/dot/hypr/autostart.lua." + detail: "No hl.exec_cmd entries were found in config/dot/hypr/autostart.lua" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/ColorWell.qml b/config/dot/quickshell/modules/settings/ColorWell.qml index 05b406d..df9eaf9 100644 --- a/config/dot/quickshell/modules/settings/ColorWell.qml +++ b/config/dot/quickshell/modules/settings/ColorWell.qml @@ -175,7 +175,7 @@ Rectangle { clip: true Accessible.role: Accessible.EditableText - Accessible.name: root.role + " colour, hex" + Accessible.name: root.role + " color, hex" // Seeded once, then written only by the guarded handlers // below and by onSwatchColorChanged. A binding here would @@ -212,7 +212,7 @@ Rectangle { IconButton { glyph: "◐" - title: "Open the colour wheel for " + root.role + title: "Open the color wheel for " + root.role onTriggered: root.wheelRequested() } diff --git a/config/dot/quickshell/modules/settings/ConnectivityPage.qml b/config/dot/quickshell/modules/settings/ConnectivityPage.qml index 670f911..8176fa0 100644 --- a/config/dot/quickshell/modules/settings/ConnectivityPage.qml +++ b/config/dot/quickshell/modules/settings/ConnectivityPage.qml @@ -118,7 +118,8 @@ SettingsPage { NetworkTools.active = true; if (!WifiShare.scanned) WifiShare.refresh(); - Vpn.refresh(); + if (!Vpn.scanned) + Vpn.refresh(); } Component.onDestruction: { Connectivity.active = false; @@ -137,12 +138,9 @@ SettingsPage { NetworkTools.refreshDetails(root.wiredConnection); } - TextRow { - visible: NetworkTools.lastError !== "" + ErrorRow { label: "The network needs attention" - detail: NetworkTools.lastError - value: "" - divider: false + message: NetworkTools.lastError } // ── Wired ──────────────────────────────────────────────────────────────── @@ -406,12 +404,11 @@ SettingsPage { title: "VPN" subtitle: "WireGuard and OpenVPN profiles NetworkManager holds for you." - TextRow { + ErrorRow { width: parent.width - visible: Vpn.lastError !== "" label: "The last VPN action did not finish" - detail: Vpn.lastError - value: "" + message: Vpn.lastError + divider: true } Repeater { diff --git a/config/dot/quickshell/modules/settings/ContainersPage.qml b/config/dot/quickshell/modules/settings/ContainersPage.qml index eea996b..2f015dc 100644 --- a/config/dot/quickshell/modules/settings/ContainersPage.qml +++ b/config/dot/quickshell/modules/settings/ContainersPage.qml @@ -61,22 +61,19 @@ SettingsPage { }).join(", "); } - Component.onCompleted: Containers.refresh() + Component.onCompleted: if (!Containers.scanned) Containers.refresh() // ── the list ──────────────────────────────────────────────────────────── - TextRow { - visible: Containers.lastError !== "" + ErrorRow { label: "That did not work" - detail: Containers.lastError - value: "" - divider: false + message: Containers.lastError } TextRow { visible: Containers.scanned && !Containers.available label: "podman is not available" - detail: "Nothing here can be shown until podman is installed." + detail: "Nothing here can be shown until podman is installed" value: "" divider: false } @@ -449,7 +446,7 @@ SettingsPage { TextRow { visible: Containers.scanned && Containers.available && Containers.total === 0 label: "No containers" - detail: "Nothing has been created on this machine yet." + detail: "Nothing has been created on this machine yet" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/CountdownRing.qml b/config/dot/quickshell/modules/settings/CountdownRing.qml index fbdc538..3545a3e 100644 --- a/config/dot/quickshell/modules/settings/CountdownRing.qml +++ b/config/dot/quickshell/modules/settings/CountdownRing.qml @@ -20,7 +20,7 @@ Item { // A Canvas reads Theme inside onPaint, where nothing records a dependency // on it, so a palette change would leave the ring drawn in the old warn // tone until the next tick -- or for good, on a countdown sitting still. - // Naming the colour as a property gives the repaint something to watch. + // Naming the color as a property gives the repaint something to watch. readonly property color ringColor: Theme.warn onSecondsLeftChanged: ring.requestPaint() diff --git a/config/dot/quickshell/modules/settings/CustomShortcutEditor.qml b/config/dot/quickshell/modules/settings/CustomShortcutEditor.qml index 301c014..981b8ba 100644 --- a/config/dot/quickshell/modules/settings/CustomShortcutEditor.qml +++ b/config/dot/quickshell/modules/settings/CustomShortcutEditor.qml @@ -7,7 +7,7 @@ // one of the compositor's window verbs. // // Nothing typed here becomes a command. The editor reports an enum kind and a -// target that Keybinds.describeAction() already recognises; hypr/actions.lua +// target that Keybinds.describeAction() already recognizes; hypr/actions.lua // resolves that to something runnable through whitelist tables of its own. That // is the whole reason there is no "run this command" option: settings.json has // to stay a file it is safe to hand somebody. @@ -116,7 +116,7 @@ Column { ChoiceGrid { width: parent.width label: "What it does" - detail: "Three kinds, and only three: Panama resolves the name you pick when you press the keys, so no shortcut ever stores a command." + detail: "Three kinds, and only three: Panama resolves the name you pick when you press the keys, so no shortcut ever stores a command" current: root.kind options: [ { value: "app", label: "Launch an application" }, @@ -185,7 +185,7 @@ Column { width: parent.width visible: root.kind === "shell" label: "Shell action" - detail: "The shell's own surfaces, each one something Panama already answers over IPC." + detail: "The shell's own surfaces, each one something Panama already answers over IPC" current: root.target options: root.shellOptions divider: false @@ -199,7 +199,7 @@ Column { width: parent.width visible: root.kind === "window" label: "Window & workspace" - detail: "Compositor verbs: the first three act on the focused window, the rest go to a workspace." + detail: "Compositor verbs: the first three act on the focused window, the rest go to a workspace" current: root.target options: root.windowOptions divider: false diff --git a/config/dot/quickshell/modules/settings/DateTimePage.qml b/config/dot/quickshell/modules/settings/DateTimePage.qml index 54a10bf..d1ca2b7 100644 --- a/config/dot/quickshell/modules/settings/DateTimePage.qml +++ b/config/dot/quickshell/modules/settings/DateTimePage.qml @@ -385,10 +385,9 @@ SettingsPage { // measurement system and not its page size, and deriving Letter from a // country list would be Panama inventing an answer glibc already holds // -- a preview that guesses is worse than a preview that stops. - TextRow { + NotMeasuredRow { label: "Paper size is not previewed" - detail: "Qt can report a locale's measurement system but not its page size, and guessing it from the country would be this page making something up." - value: "" + because: "Qt can report a locale's measurement system but not its page size, and guessing it from the country would be this page making something up" divider: false } } diff --git a/config/dot/quickshell/modules/settings/DictationPage.qml b/config/dot/quickshell/modules/settings/DictationPage.qml index 670e015..ebec09a 100644 --- a/config/dot/quickshell/modules/settings/DictationPage.qml +++ b/config/dot/quickshell/modules/settings/DictationPage.qml @@ -507,12 +507,9 @@ SettingsPage { divider: Dictation.lastError !== "" } - TextRow { - visible: Dictation.lastError !== "" + ErrorRow { label: "Problem" - detail: Dictation.lastError - value: "" - divider: false + message: Dictation.lastError } } diff --git a/config/dot/quickshell/modules/settings/DisplayModePicker.qml b/config/dot/quickshell/modules/settings/DisplayModePicker.qml index 1ba0872..ab49ba5 100644 --- a/config/dot/quickshell/modules/settings/DisplayModePicker.qml +++ b/config/dot/quickshell/modules/settings/DisplayModePicker.qml @@ -10,7 +10,7 @@ // picture, and whether this is the one the panel was built for. // // Applying is the page's job. Every per-display edit on the Displays page goes -// through one funnel so the whole record -- position, colour, mirror state -- +// through one funnel so the whole record -- position, color, mirror state -- // rides the same keep-or-revert transaction; this reports which resolution was // asked for and lets that funnel do the rest. diff --git a/config/dot/quickshell/modules/settings/DisplaysPage.qml b/config/dot/quickshell/modules/settings/DisplaysPage.qml index 398a312..cc9377d 100644 --- a/config/dot/quickshell/modules/settings/DisplaysPage.qml +++ b/config/dot/quickshell/modules/settings/DisplaysPage.qml @@ -516,17 +516,7 @@ SettingsPage { } // ── Everything that belongs to no display in particular ───────────────── - Text { - width: parent.width - text: "All displays" - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.DemiBold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.7 - topPadding: 6 - } + SectionLabel { text: "All displays" } // Two cards side by side while there is room for two, and one above the // other when there is not. This window is tiled: its width is anywhere from diff --git a/config/dot/quickshell/modules/settings/DockPage.qml b/config/dot/quickshell/modules/settings/DockPage.qml index 243c326..dd688c6 100644 --- a/config/dot/quickshell/modules/settings/DockPage.qml +++ b/config/dot/quickshell/modules/settings/DockPage.qml @@ -1,7 +1,7 @@ // Dock. // // The pinned applications come first because they are what the Dock is; the -// behaviour card below is how it gets out of the way. The dock timings used to +// behavior card below is how it gets out of the way. The dock timings used to // be shown here as text -- "Instant", "250 ms" -- even though they were already // stored, mutable integers. They are controls now. @@ -80,35 +80,18 @@ SettingsPage { Repeater { model: Quickshell.screens - delegate: Rectangle { + delegate: SettingsChip { id: screenPill required property var modelData readonly property string screenName: String(screenPill.modelData.name ?? "") - // An empty list means all, so every pill reads as on. - readonly property bool on: Settings.dockScreens.length === 0 + + text: screenPill.screenName + // An empty list means all, so every chip reads as on. + active: Settings.dockScreens.length === 0 || Settings.dockScreens.indexOf(screenPill.screenName) >= 0 - - width: pillLabel.implicitWidth + 20 - height: 28 - radius: 8 - color: screenPill.on ? Theme.alpha(Theme.accent, 0.22) - : Theme.alpha(Theme.fg, 0.06) - border.width: screenPill.on ? 1 : 0 - border.color: Theme.alpha(Theme.accent, 0.5) - - Text { - id: pillLabel - anchors.centerIn: parent - text: screenPill.screenName - color: screenPill.on ? Theme.fg : Theme.fgDim - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - } - - HoverHandler { cursorShape: Qt.PointingHandCursor } - TapHandler { onTapped: root.toggleDockScreen(screenPill.screenName) } + onClicked: root.toggleDockScreen(screenPill.screenName) } } } diff --git a/config/dot/quickshell/modules/settings/FileTypePicker.qml b/config/dot/quickshell/modules/settings/FileTypePicker.qml index 698d28f..4324ed9 100644 --- a/config/dot/quickshell/modules/settings/FileTypePicker.qml +++ b/config/dot/quickshell/modules/settings/FileTypePicker.qml @@ -122,7 +122,7 @@ Column { width: parent.width visible: !root.busy && root.truncated label: "Only the closest matches are shown" - detail: "Narrow the search to reach the rest." + detail: "Narrow the search to reach the rest" value: "" divider: false } @@ -132,7 +132,7 @@ Column { visible: !root.busy && root.query.length >= root.minimumQuery && root.matches.length === 0 label: "No file type matches that" - detail: "Only types this system knows about can be given a handler." + detail: "Only types this system knows about can be given a handler" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/FirewallPage.qml b/config/dot/quickshell/modules/settings/FirewallPage.qml index bc79991..febf1e8 100644 --- a/config/dot/quickshell/modules/settings/FirewallPage.qml +++ b/config/dot/quickshell/modules/settings/FirewallPage.qml @@ -12,7 +12,6 @@ import Quickshell import QtQuick -import qs.config import qs.services SettingsPage { @@ -133,14 +132,11 @@ SettingsPage { return text; } - Component.onCompleted: Firewall.refresh() + Component.onCompleted: if (!Firewall.scanned) Firewall.refresh() - TextRow { - visible: Firewall.lastError !== "" + ErrorRow { label: "The firewall needs attention" - detail: Firewall.lastError - value: "" - divider: false + message: Firewall.lastError } // ── The finding, when there is one ─────────────────────────────────────── @@ -378,7 +374,7 @@ SettingsPage { : (root.addKind === "service" ? "That is not a service name firewalld would accept" : "That is not a port firewalld would accept") - detail: "This writes a permanent rule — the system will ask for your password." + detail: "This writes a permanent rule — the system will ask for your password" action: "Allow" enabled: root.addValid && !Firewall.busy divider: false @@ -565,43 +561,16 @@ SettingsPage { Repeater { model: Firewall.allZones ?? [] - delegate: Rectangle { + delegate: SettingsChip { id: chip required property var modelData readonly property string zoneName: String(chip.modelData) - readonly property bool current: root.browsingZone === chip.zoneName - width: chipLabel.implicitWidth + 20 - height: 26 - radius: Theme.pillRadius - color: chip.current - ? Theme.alpha(Theme.accent, 0.18) - : Theme.alpha(Theme.fg, chipHover.hovered ? 0.11 : 0.055) - border.width: 1 - border.color: chip.current - ? Theme.alpha(Theme.accent, 0.45) - : Theme.alpha(Theme.fg, 0.09) - - Text { - id: chipLabel - anchors.centerIn: parent - text: chip.zoneName - color: chip.current ? Theme.fg : Theme.fgDim - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: chip.current ? Font.DemiBold : Font.Normal - } - - HoverHandler { - id: chipHover - cursorShape: Qt.PointingHandCursor - } - - TapHandler { - onTapped: root.browsingZone = chip.current ? "" : chip.zoneName - } + text: chip.zoneName + active: root.browsingZone === chip.zoneName + onClicked: root.browsingZone = chip.active ? "" : chip.zoneName } } } diff --git a/config/dot/quickshell/modules/settings/FocusAllowChips.qml b/config/dot/quickshell/modules/settings/FocusAllowChips.qml index fbe8433..daf3130 100644 --- a/config/dot/quickshell/modules/settings/FocusAllowChips.qml +++ b/config/dot/quickshell/modules/settings/FocusAllowChips.qml @@ -85,7 +85,7 @@ Column { Text { width: parent.width - text: "Apps that break through while this mode is on" + text: "Applications that break through while this mode is on" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall @@ -156,37 +156,15 @@ Column { } } - Rectangle { + SettingsChip { id: addChip - height: 26 - width: addLabel.implicitWidth + 20 - radius: Theme.pillRadius - color: Theme.alpha(Theme.fg, addHover.hovered ? 0.1 : 0.05) - border.width: 1 - border.color: Theme.alpha(Theme.fg, 0.2) + text: root.adding ? "Cancel" : "+ Add application" - Text { - id: addLabel - anchors.centerIn: parent - text: root.adding ? "Cancel" : "+ Add app" - color: Theme.fgDim - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.Medium - } - - HoverHandler { - id: addHover - cursorShape: Qt.PointingHandCursor - } - - TapHandler { - onTapped: { - root.adding = !root.adding; - if (!root.adding) - search.text = ""; - } + onClicked: { + root.adding = !root.adding; + if (!root.adding) + search.text = ""; } } } @@ -215,7 +193,7 @@ Column { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - placeholder: "Search apps" + placeholder: "Search applications" } } diff --git a/config/dot/quickshell/modules/settings/FocusModeRow.qml b/config/dot/quickshell/modules/settings/FocusModeRow.qml index 4f0e7eb..c759e54 100644 --- a/config/dot/quickshell/modules/settings/FocusModeRow.qml +++ b/config/dot/quickshell/modules/settings/FocusModeRow.qml @@ -90,7 +90,7 @@ Column { case "workspace": return "on workspace " + String(root.trigger.id ?? 1); case "fullscreen": - return "when an app is fullscreen"; + return "when an application is fullscreen"; default: return "manually"; } @@ -111,7 +111,7 @@ Column { + (root.allow.length === 0 ? "" : " except " + root.allow.length - + (root.allow.length === 1 ? " app" : " apps"))); + + (root.allow.length === 1 ? " application" : " applications"))); } if (root.mode.keepAwake === true) effects.push(hypothetical ? "would keep the screen awake" : "keeps the screen awake"); @@ -349,7 +349,7 @@ Column { }, { value: "fullscreen", - label: "When an app is fullscreen", + label: "When an application is fullscreen", detail: "On while a window is fullscreen on the focused display" } ] @@ -398,41 +398,30 @@ Column { spacing: 5 Repeater { + // `name` is what a screen reader says: four of the seven + // initials repeat, so the letter alone is not a day. model: [ - { value: 1, label: "M" }, { value: 2, label: "T" }, - { value: 3, label: "W" }, { value: 4, label: "T" }, - { value: 5, label: "F" }, { value: 6, label: "S" }, - { value: 0, label: "S" } + { value: 1, label: "M", name: "Monday" }, + { value: 2, label: "T", name: "Tuesday" }, + { value: 3, label: "W", name: "Wednesday" }, + { value: 4, label: "T", name: "Thursday" }, + { value: 5, label: "F", name: "Friday" }, + { value: 6, label: "S", name: "Saturday" }, + { value: 0, label: "S", name: "Sunday" } ] - delegate: Rectangle { + // Seven of them across, so they share one width rather than + // each taking the width of its own letter. + delegate: SettingsChip { id: dayPill required property var modelData - readonly property bool on: - (root.trigger.days ?? []).indexOf(dayPill.modelData.value) >= 0 - width: 30 - height: 28 - radius: 8 - color: dayPill.on ? Theme.alpha(Theme.accent, 0.22) - : Theme.alpha(Theme.fg, 0.06) - border.width: 1 - border.color: dayPill.on ? Theme.alpha(Theme.accent, 0.5) - : Theme.alpha(Theme.fg, 0.08) - - Text { - anchors.centerIn: parent - text: String(dayPill.modelData.label) - color: dayPill.on ? Theme.fg : Theme.fgDim - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: dayPill.on ? Font.DemiBold : Font.Medium - } - - HoverHandler { cursorShape: Qt.PointingHandCursor } - TapHandler { onTapped: root.toggleDay(dayPill.modelData.value) } + text: String(dayPill.modelData.label) + active: (root.trigger.days ?? []).indexOf(dayPill.modelData.value) >= 0 + Accessible.name: String(dayPill.modelData.name) + onClicked: root.toggleDay(dayPill.modelData.value) } } } diff --git a/config/dot/quickshell/modules/settings/FocusPage.qml b/config/dot/quickshell/modules/settings/FocusPage.qml index b49dd87..39abfa0 100644 --- a/config/dot/quickshell/modules/settings/FocusPage.qml +++ b/config/dot/quickshell/modules/settings/FocusPage.qml @@ -42,7 +42,7 @@ SettingsPage { TextRow { visible: FocusModes.modes.length === 0 label: "No focus modes" - detail: "Add one below and give it something to react to." + detail: "Add one below and give it something to react to" divider: false } diff --git a/config/dot/quickshell/modules/settings/GamingPage.qml b/config/dot/quickshell/modules/settings/GamingPage.qml index 15e2439..f38f77b 100644 --- a/config/dot/quickshell/modules/settings/GamingPage.qml +++ b/config/dot/quickshell/modules/settings/GamingPage.qml @@ -25,17 +25,15 @@ SettingsPage { // Polling only while this page is on screen. Component.onCompleted: { - Gaming.refresh(); + if (!Gaming.scanned) + Gaming.refresh(); Gaming.watching = true; } Component.onDestruction: Gaming.watching = false - TextRow { - visible: Gaming.lastError !== "" + ErrorRow { label: "Gaming needs attention" - detail: Gaming.lastError - value: "" - divider: false + message: Gaming.lastError } // ── Right now ──────────────────────────────────────────────────────────── diff --git a/config/dot/quickshell/modules/settings/GradientSliderRow.qml b/config/dot/quickshell/modules/settings/GradientSliderRow.qml index 15d5d9e..01dfc2a 100644 --- a/config/dot/quickshell/modules/settings/GradientSliderRow.qml +++ b/config/dot/quickshell/modules/settings/GradientSliderRow.qml @@ -46,8 +46,8 @@ Item { // nothing in common but the digits. property string readout: "" - // Two or three colours, left to right. Painted across the whole track when - // `fullTrack` is set -- for a value whose range is the point, like colour + // Two or three colors, left to right. Painted across the whole track when + // `fullTrack` is set -- for a value whose range is the point, like color // temperature -- and as the fill alone otherwise. property var trackColors: [Theme.accent, Theme.accentSecondary] property bool fullTrack: false diff --git a/config/dot/quickshell/modules/settings/HealthPage.qml b/config/dot/quickshell/modules/settings/HealthPage.qml index 6b5a3c1..f552f91 100644 --- a/config/dot/quickshell/modules/settings/HealthPage.qml +++ b/config/dot/quickshell/modules/settings/HealthPage.qml @@ -12,7 +12,7 @@ SettingsPage { readonly property string ledeText: { if (Health.checks.length === 0) - return "Checks the parts of the desktop this app owns, and explains what needs attention."; + return "Checks the parts of the desktop this application owns, and explains what needs attention."; const count = Health.checks.length + (Health.checks.length === 1 ? " check" : " checks"); if (root.issueChecks.length === 0) return count + " · everything healthy."; @@ -43,7 +43,7 @@ SettingsPage { { group: "panama-tools", title: "Desktop tools", - subtitle: "Tracked links, launcher commands, apps, and inhibitors." + subtitle: "Tracked links, launcher commands, applications, and inhibitors." } ] readonly property var applicationTargets: ({ @@ -432,14 +432,14 @@ SettingsPage { // umbrella button reading "Open GNOME Settings", which landed on the System // panel. That button was the last door of its kind, and by the end it was // pointing at a house Panama had bought: Users, Sharing, Printers, Online - // Accounts, Privacy, Region, Colour and the whole of Connections are pages + // Accounts, Privacy, Region, Color and the whole of Connections are pages // here now. A generic front door to a settings app you no longer need is // not a boundary, it is a habit -- so it is gone, and gnome-handoff-contract // holds the door shut by naming `system` in its OWNED map. // // Screen time is the exception, and it is a real one: GNOME's wellbeing // panel does something Panama does not, and that button genuinely works. - // Colour profiles used to sit here too, with a detail line explaining that + // Color profiles used to sit here too, with a detail line explaining that // pressing the button changed nothing, because the colord daemon that // applies an ICC profile is not running under Hyprland. A handoff that // documents its own uselessness is a dead button with an apology attached, diff --git a/config/dot/quickshell/modules/settings/HealthSummary.qml b/config/dot/quickshell/modules/settings/HealthSummary.qml index ea76016..d933a67 100644 --- a/config/dot/quickshell/modules/settings/HealthSummary.qml +++ b/config/dot/quickshell/modules/settings/HealthSummary.qml @@ -18,7 +18,7 @@ SettingsCard { case "unavailable": return Health.lastError || "The latest health check could not be completed."; case "checking": - return "Checking the desktop services, tools, and integrations this app owns."; + return "Checking the desktop services, tools, and integrations this application owns."; case "error": return root.observationCount === 1 ? "One part of the desktop needs action." diff --git a/config/dot/quickshell/modules/settings/HomeFavoriteCard.qml b/config/dot/quickshell/modules/settings/HomeFavoriteCard.qml index 844d93e..aad42f6 100644 --- a/config/dot/quickshell/modules/settings/HomeFavoriteCard.qml +++ b/config/dot/quickshell/modules/settings/HomeFavoriteCard.qml @@ -126,27 +126,13 @@ Rectangle { elide: removeConfirm.armed ? Text.ElideNone : Text.ElideRight } - Rectangle { + StatusBadge { anchors.left: aliasFrame.left anchors.bottom: parent.bottom anchors.bottomMargin: 10 - width: badgeCopy.implicitWidth + 14 - height: 21 - radius: Theme.pillRadius visible: root.featured - color: Theme.alpha(Theme.accent, 0.1) - border.width: 1 - border.color: Theme.alpha(Theme.accent, 0.2) - - Text { - id: badgeCopy - anchors.centerIn: parent - text: "Control Center" - color: Theme.accent - font.family: Theme.fontFamily - font.pixelSize: 9 - font.weight: Font.DemiBold - } + text: "Control Center" + tone: Theme.accent } ConfirmAction { diff --git a/config/dot/quickshell/modules/settings/HomeLightTile.qml b/config/dot/quickshell/modules/settings/HomeLightTile.qml index 80890d8..7a49020 100644 --- a/config/dot/quickshell/modules/settings/HomeLightTile.qml +++ b/config/dot/quickshell/modules/settings/HomeLightTile.qml @@ -93,25 +93,11 @@ Rectangle { elide: Text.ElideRight } - Rectangle { + StatusBadge { visible: root.featured anchors.verticalCenter: parent.verticalCenter - width: featuredCaption.implicitWidth + 16 - height: 19 - radius: Theme.pillRadius - color: Theme.alpha(Theme.accent, 0.1) - border.width: 1 - border.color: Theme.alpha(Theme.accent, 0.2) - - Text { - id: featuredCaption - anchors.centerIn: parent - text: "Control Center" - color: Theme.accent - font.family: Theme.fontFamily - font.pixelSize: 9 - font.weight: Font.DemiBold - } + text: "Control Center" + tone: Theme.accent } } diff --git a/config/dot/quickshell/modules/settings/HomePage.qml b/config/dot/quickshell/modules/settings/HomePage.qml index 3bc8199..853caf9 100644 --- a/config/dot/quickshell/modules/settings/HomePage.qml +++ b/config/dot/quickshell/modules/settings/HomePage.qml @@ -247,7 +247,7 @@ SettingsPage { TextRow { visible: root.findings.length === 0 label: "Nothing needs your attention" - detail: "Anything worth acting on would appear here." + detail: "Anything worth acting on would appear here" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/InstalledAppRow.qml b/config/dot/quickshell/modules/settings/InstalledAppRow.qml index 4c559eb..09884f8 100644 --- a/config/dot/quickshell/modules/settings/InstalledAppRow.qml +++ b/config/dot/quickshell/modules/settings/InstalledAppRow.qml @@ -103,7 +103,7 @@ Column { return name === "" ? "" : Quickshell.iconPath(name, true); } - // A stable colour per application, so the tile keeps its identity as the + // A stable color per application, so the tile keeps its identity as the // list re-sorts or the search narrows it. readonly property color tileColor: { const palette = [Theme.accent, Theme.teal, Theme.magenta, Theme.cyan, @@ -190,31 +190,12 @@ Column { // Where it came from, said once and in the same place on every // row -- the difference decides what the body can offer. - Rectangle { + StatusBadge { id: badge anchors.verticalCenter: parent.verticalCenter - width: badgeText.implicitWidth + 14 - height: 17 - radius: Theme.pillRadius - color: root.flatpak - ? Theme.alpha(Theme.cyan, 0.09) - : Theme.alpha(Theme.fgMuted, 0.12) - border.width: 1 - border.color: root.flatpak - ? Theme.alpha(Theme.cyan, 0.28) - : Theme.alpha(Theme.fgMuted, 0.3) - - Text { - id: badgeText - - anchors.centerIn: parent - text: root.flatpak ? "FLATPAK" : "SYSTEM" - color: root.flatpak ? Theme.cyan : Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Math.max(8, Theme.fontSizeSmall - 2) - font.weight: Font.DemiBold - } + text: root.flatpak ? "Flatpak" : "System" + tone: root.flatpak ? Theme.cyan : Theme.fgMuted } } diff --git a/config/dot/quickshell/modules/settings/ManualPage.qml b/config/dot/quickshell/modules/settings/ManualPage.qml index 573d111..3de13c0 100644 --- a/config/dot/quickshell/modules/settings/ManualPage.qml +++ b/config/dot/quickshell/modules/settings/ManualPage.qml @@ -91,7 +91,7 @@ SettingsPage { // The scheme is Panama's own, so a chapter can never accidentally // hand an http link to openSettings, and an unknown page cannot be // smuggled in either: SettingsRoutes.resolve turns anything it does - // not recognise into Home rather than a blank loader. A fragment, + // not recognize into Home rather than a blank loader. A fragment, // where there is one, is the section within the page. onLinkActivated: link => { const target = String(link ?? ""); diff --git a/config/dot/quickshell/modules/settings/MousePage.qml b/config/dot/quickshell/modules/settings/MousePage.qml index 98d08af..3ab21cb 100644 --- a/config/dot/quickshell/modules/settings/MousePage.qml +++ b/config/dot/quickshell/modules/settings/MousePage.qml @@ -18,8 +18,10 @@ // knobs come back up with it: they are about gestures, not about the touchpad. import QtQuick +import Quickshell import qs.config import qs.services +import qs.modules.clipboard SettingsPage { id: root @@ -99,7 +101,7 @@ SettingsPage { ToggleRow { setting: "leftHanded" } ToggleRow { setting: "middleClickPaste" - detail: "Paste the primary selection in GTK and native Wayland applications; individual apps may choose not to support it" + detail: "Paste the primary selection in GTK and native Wayland applications; individual applications may choose not to support it" } OptionPickerRow { @@ -170,14 +172,19 @@ SettingsPage { Keybinds.applyReload(); } - // Nothing, then the shell's own actions, then the compositor's window - // verbs -- the same lists the shortcut editor offers, read from Keybinds so - // this page never becomes a second opinion on what resolves. + // Nothing, then an application, then the shell's own actions, then the + // compositor's window verbs -- the same lists the shortcut editor offers, + // read from Keybinds so this page never becomes a second opinion on what + // resolves. // - // Applications are deliberately absent: assigning one means searching - // several hundred desktop entries, which a picker of this shape cannot do. - // A gesture that already holds an application (written by the shortcut - // vocabulary elsewhere) still shows, so it can be read and cleared. + // Applications cannot be listed inline: a normal machine has several + // hundred desktop entries and a picker of this shape would be a page nobody + // can read. So the option is a door rather than a list -- picking it opens + // the same search the shortcut editor uses, below the row. A gesture that + // already holds an application shows it too, so it can be read and cleared + // without going back through the search. + readonly property string appPickerValue: "pick-application" + function gestureOptions(key: string): var { const out = [{ value: "", label: "Nothing", detail: "The swipe passes through to whatever is under it" }]; const stored = DesktopPreferences.get(key); @@ -187,6 +194,11 @@ SettingsPage { label: String(stored.label), detail: "Application · launch-or-focus" }); + out.push({ + value: root.appPickerValue, + label: "Launch an application…", + detail: "Search everything installed, the way a custom shortcut does" + }); for (const action of Keybinds.shellActions) out.push({ value: "shell:" + action.target, label: action.label, detail: "Shell action" }); for (const action of Keybinds.windowActions) @@ -194,6 +206,49 @@ SettingsPage { return out; } + // Which gesture is currently choosing an application, "" while none is. + property string pickingAppFor: "" + + // Capped at eight and only ever searched, never browsed -- the same rule + // and the same reason as CustomShortcutEditor's list. + readonly property var appMatches: { + const needle = gestureAppSearch.text.trim().toLowerCase(); + if (needle === "") + return []; + const out = []; + for (const entry of DesktopEntries.applications.values) { + if (entry.noDisplay) + continue; + if (String(entry.name).toLowerCase().indexOf(needle) >= 0) + out.push(entry); + if (out.length >= 8) + break; + } + return out; + } + + // The one path that writes an application onto a gesture. It builds the + // same three-field record assignGesture() writes for the named actions -- + // an entry id is never a command, and hypr/actions.lua resolves it the same + // way it resolves one written by a custom shortcut. + function assignGestureApp(key: string, entry: var): void { + const target = String(entry?.id ?? ""); + if (Keybinds.describeAction({ kind: "app", target: target }) === "") + return; + DesktopPreferences.set(key, { + kind: "app", + target: target, + label: "Launch " + String(entry?.name ?? target) + }); + root.closeAppPicker(); + Keybinds.applyReload(); + } + + function closeAppPicker(): void { + root.pickingAppFor = ""; + gestureAppSearch.text = ""; + } + SettingsCard { visible: InputDevices.hasTouchpad title: "Gestures" @@ -236,7 +291,67 @@ SettingsPage { current: root.gestureValue(String(gestureRow.modelData.key)) enabled: !Keybinds.reloading divider: gestureRow.index < root.gestureKeys.length - 1 - onPicked: value => root.assignGesture(String(gestureRow.modelData.key), String(value)) + onPicked: value => { + if (String(value) === root.appPickerValue) { + root.pickingAppFor = String(gestureRow.modelData.key); + gestureAppSearch.text = ""; + return; + } + root.closeAppPicker(); + root.assignGesture(String(gestureRow.modelData.key), String(value)); + } + } + } + + // The search, unfolded under the four rows rather than inside one of + // them: the picker has already collapsed by the time this appears, and + // a list of eight results inside a collapsed row has nowhere to go. + Column { + width: parent.width + visible: root.pickingAppFor !== "" + spacing: 0 + + Item { + width: parent.width + height: 40 + + SearchField { + id: gestureAppSearch + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + placeholder: "Search installed applications" + } + } + + Repeater { + model: root.appMatches + + SettingRow { + id: candidate + + required property var modelData + required property int index + + width: parent.width + label: String(candidate.modelData.name ?? "") + detail: String(candidate.modelData.id ?? "") + controlWidth: 96 + divider: candidate.index < root.appMatches.length - 1 + activatable: true + + onActivated: root.assignGestureApp(root.pickingAppFor, candidate.modelData) + } + } + + TextRow { + visible: root.appMatches.length === 0 + label: gestureAppSearch.text.trim() === "" + ? "Type to find an application" + : "Nothing installed matches that" + detail: "The gesture stores the application, never a command — the same three fields a custom shortcut stores" + divider: false } } diff --git a/config/dot/quickshell/modules/settings/MyHomePage.qml b/config/dot/quickshell/modules/settings/MyHomePage.qml index f552e98..f7834ea 100644 --- a/config/dot/quickshell/modules/settings/MyHomePage.qml +++ b/config/dot/quickshell/modules/settings/MyHomePage.qml @@ -111,15 +111,9 @@ SettingsPage { width: parent.width spacing: 8 - Text { + SectionLabel { visible: String(roomSection.modelData.name ?? "") !== "" - text: String(roomSection.modelData.name ?? "").toUpperCase() - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.DemiBold - font.letterSpacing: 0.8 - topPadding: 6 + text: String(roomSection.modelData.name ?? "") } Grid { @@ -382,15 +376,10 @@ SettingsPage { } } - Text { - width: parent.width - visible: HomeAssistantConfig.lastError !== "" - text: HomeAssistantConfig.lastError - color: Theme.danger - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - wrapMode: Text.WordWrap - bottomPadding: 9 + ErrorRow { + label: "Home Assistant needs attention" + message: HomeAssistantConfig.lastError + divider: true } SettingRow { @@ -432,7 +421,7 @@ SettingsPage { SettingRow { label: "Light catalog" - detail: "Light state is read through the Home Assistant helper." + detail: "Light state is read through the Home Assistant helper" divider: false controlWidth: 176 diff --git a/config/dot/quickshell/modules/settings/NotMeasuredRow.qml b/config/dot/quickshell/modules/settings/NotMeasuredRow.qml index 4cbb942..f091cc5 100644 --- a/config/dot/quickshell/modules/settings/NotMeasuredRow.qml +++ b/config/dot/quickshell/modules/settings/NotMeasuredRow.qml @@ -3,8 +3,8 @@ // by "because" -- a bare dash invites the reader to assume zero, and zero is // a measurement. // -// NotMeasuredRow { because: "The desktop portal's permission store is not answering, so what has asked for this cannot be read." } -// NotMeasuredRow { label: "Not read yet"; because: "Reading what the battery firmware has recorded." } +// NotMeasuredRow { because: "The desktop portal's permission store is not answering, so what has asked for this cannot be read" } +// NotMeasuredRow { label: "Not read yet"; because: "Reading what the battery firmware has recorded" } // // `because` is required in spirit: the row renders without one, but a consumer // leaving it empty is exactly the dishonesty this component exists to prevent, diff --git a/config/dot/quickshell/modules/settings/NotificationAppRow.qml b/config/dot/quickshell/modules/settings/NotificationAppRow.qml index 866fb57..867328f 100644 --- a/config/dot/quickshell/modules/settings/NotificationAppRow.qml +++ b/config/dot/quickshell/modules/settings/NotificationAppRow.qml @@ -3,7 +3,7 @@ // // The head is hand-built rather than a SettingRow because it needs two things // SettingRow has no room for -- the application's own icon, and a subtitle -// whose second half is coloured when the rule is no longer the default, so a +// whose second half is colored when the rule is no longer the default, so a // muted or redirected application is obvious without opening it. // // Every write leaves through `edited`/`forgotten`. The page owns the service @@ -66,8 +66,8 @@ Column { return name === "" ? "" : Quickshell.iconPath(name, true); } - // A stable colour per application, so the tile is a recognisable shape - // rather than a different colour every time the list re-sorts. + // A stable color per application, so the tile is a recognizable shape + // rather than a different color every time the list re-sorts. readonly property color tileColor: { const palette = [Theme.accent, Theme.teal, Theme.magenta, Theme.cyan, Theme.green, Theme.orange, Theme.pink]; @@ -263,11 +263,11 @@ Column { OptionPickerRow { width: parent.width label: "Urgency" - detail: "Override what the app claims its notifications are" + detail: "Override what the application claims its notifications are" options: [ { value: "auto", - label: "App decides", + label: "Application decides", detail: "Whatever urgency the notification arrives with" }, { @@ -287,7 +287,7 @@ Column { ActionRow { width: parent.width - label: "Forget this app" + label: "Forget this application" detail: "Remove its rule; it returns on its next notification" action: "Forget" divider: false diff --git a/config/dot/quickshell/modules/settings/NotificationsPage.qml b/config/dot/quickshell/modules/settings/NotificationsPage.qml index abee835..c790e27 100644 --- a/config/dot/quickshell/modules/settings/NotificationsPage.qml +++ b/config/dot/quickshell/modules/settings/NotificationsPage.qml @@ -278,32 +278,18 @@ SettingsPage { SettingsCard { title: "Applications" - subtitle: "Apps appear after their first notification. Recent senders and apps you have customized stay up top; the rest wait in All apps." + subtitle: "Applications appear after their first notification. Recent senders and applications you have customized stay up top; the rest wait in All applications." TextRow { visible: Notifs.applications.length === 0 label: "No applications remembered yet" - detail: "Application controls will appear after the first notification arrives." + detail: "Application controls will appear after the first notification arrives" divider: false } - Item { - width: parent.width - height: root.recentApps.length > 0 ? 28 : 0 - visible: height > 0 - - Text { - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.bottomMargin: 4 - text: "Recent" - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 1) - font.weight: Font.Bold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.8 - } + SectionLabel { + visible: root.recentApps.length > 0 + text: "Recent" } Repeater { @@ -329,23 +315,9 @@ SettingsPage { } } - Item { - width: parent.width - height: root.customizedApps.length > 0 ? 28 : 0 - visible: height > 0 - - Text { - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.bottomMargin: 4 - text: "Customized" - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 1) - font.weight: Font.Bold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.8 - } + SectionLabel { + visible: root.customizedApps.length > 0 + text: "Customized" } Repeater { @@ -400,7 +372,7 @@ SettingsPage { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - placeholder: "Search apps" + placeholder: "Search applications" } } diff --git a/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml b/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml index 7e1a37c..396be81 100644 --- a/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml +++ b/config/dot/quickshell/modules/settings/OnlineAccountsPage.qml @@ -168,27 +168,11 @@ SettingsPage { anchors.verticalCenter: parent.verticalCenter spacing: 8 - Rectangle { + StatusBadge { anchors.verticalCenter: parent.verticalCenter visible: HomeAssistant.phase === "ready" - width: homeBadge.implicitWidth + 14 - height: 17 - radius: Theme.pillRadius - color: Theme.alpha(Theme.ok, 0.1) - border.width: 1 - border.color: Theme.alpha(Theme.ok, 0.3) - - Text { - id: homeBadge - - anchors.centerIn: parent - text: "CONNECTED" - color: Theme.ok - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2) - font.weight: Font.DemiBold - font.letterSpacing: 0.5 - } + text: "Connected" + tone: Theme.ok } SettingsButton { @@ -211,27 +195,11 @@ SettingsPage { anchors.verticalCenter: parent.verticalCenter spacing: 8 - Rectangle { + StatusBadge { anchors.verticalCenter: parent.verticalCenter visible: KdeConnect.pairedCount > 0 - width: phoneBadge.implicitWidth + 14 - height: 17 - radius: Theme.pillRadius - color: Theme.alpha(Theme.ok, 0.1) - border.width: 1 - border.color: Theme.alpha(Theme.ok, 0.3) - - Text { - id: phoneBadge - - anchors.centerIn: parent - text: "PAIRED" - color: Theme.ok - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2) - font.weight: Font.DemiBold - font.letterSpacing: 0.5 - } + text: "Paired" + tone: Theme.ok } SettingsButton { @@ -259,7 +227,7 @@ SettingsPage { TextRow { visible: OnlineAccounts.scanned && OnlineAccounts.accounts.length === 0 label: "No accounts yet" - detail: "Adding one lets mail, calendar, contacts and file managers share a single sign-in." + detail: "Adding one lets mail, calendar, contacts and file managers share a single sign-in" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/PowerPage.qml b/config/dot/quickshell/modules/settings/PowerPage.qml index 69c6287..e37cb1f 100644 --- a/config/dot/quickshell/modules/settings/PowerPage.qml +++ b/config/dot/quickshell/modules/settings/PowerPage.qml @@ -372,10 +372,10 @@ SettingsPage { controlWidth: 160 divider: false - // SoundBadge is the pill Panama already has for "how this thing is - // attached, or why it is not here"; the name is where it was first - // needed rather than what it is. - SoundBadge { + // The state, in the pill the whole settings app uses for one. It + // is a fact about the session, not a control, which is why there + // is a badge here and no switch. + StatusBadge { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: LidPolicy.inhibited ? "Docked · staying awake" : "Will suspend" diff --git a/config/dot/quickshell/modules/settings/PrintersPage.qml b/config/dot/quickshell/modules/settings/PrintersPage.qml index 63e0235..75f95fa 100644 --- a/config/dot/quickshell/modules/settings/PrintersPage.qml +++ b/config/dot/quickshell/modules/settings/PrintersPage.qml @@ -66,14 +66,11 @@ SettingsPage { }); } - Component.onCompleted: Printers.refresh() + Component.onCompleted: if (!Printers.scanned) Printers.refresh() - TextRow { - visible: Printers.lastError !== "" + ErrorRow { label: "Printing needs attention" - detail: Printers.lastError - value: "" - divider: false + message: Printers.lastError } // ── Which printers exist ───────────────────────────────────────────────── @@ -255,7 +252,7 @@ SettingsPage { // was built around: holding keeps the job, cancelling throws it // away and the document has to be sent from the app again. detail: cancelJob.armed - ? "The job is thrown away — printing it means sending it from the app again. Hold keeps it in the queue instead." + ? "The job is thrown away — printing it means sending it from the application again. Hold keeps it in the queue instead." : String(jobRow.modelData.printer ?? "") + " · " + String(jobRow.modelData.state ?? "") + (Number(jobRow.modelData.pages ?? 0) > 0 diff --git a/config/dot/quickshell/modules/settings/PrivacyLiveTile.qml b/config/dot/quickshell/modules/settings/PrivacyLiveTile.qml index 4243f0d..f9ddd68 100644 --- a/config/dot/quickshell/modules/settings/PrivacyLiveTile.qml +++ b/config/dot/quickshell/modules/settings/PrivacyLiveTile.qml @@ -42,7 +42,7 @@ Rectangle { return root.measured ? "Idle" : "Not measured"; } - // The neutral third state is neutral in the colour too: warn means in use, + // The neutral third state is neutral in the color too: warn means in use, // dim means idle, and "not measured" must not read as either. readonly property color toneColor: root.active ? Theme.warn diff --git a/config/dot/quickshell/modules/settings/PrivacyPage.qml b/config/dot/quickshell/modules/settings/PrivacyPage.qml index 08ad549..046e849 100644 --- a/config/dot/quickshell/modules/settings/PrivacyPage.qml +++ b/config/dot/quickshell/modules/settings/PrivacyPage.qml @@ -58,25 +58,25 @@ SettingsPage { table: "camera", label: "Camera", detail: "", - empty: "No app has asked for the camera. The portal records an answer the first time each one asks." + empty: "No application has asked for the camera. The portal records an answer the first time each one asks." }, { table: "microphone", label: "Microphone", detail: "", - empty: "No app has asked for the microphone. The portal records an answer the first time each one asks." + empty: "No application has asked for the microphone. The portal records an answer the first time each one asks." }, { table: "screencast", label: "Screen sharing", detail: "Can capture the screen", - empty: "No app holds a screen-capture grant." + empty: "No application holds a screen-capture grant." }, { table: "remote-desktop", label: "Remote desktop", detail: "Can move the pointer and type", - empty: "No app holds a remote-control grant." + empty: "No application holds a remote-control grant." }, { table: "location", @@ -89,7 +89,7 @@ SettingsPage { function askedCount(count: int): string { if (count === 0) return ""; - return count + (count === 1 ? " app has asked" : " apps have asked"); + return count + (count === 1 ? " application has asked" : " applications have asked"); } // What a stored secret is FOR, from its attributes. Never its value. @@ -210,7 +210,7 @@ SettingsPage { // An empty table means "nothing has asked" only once the store // has been read and answered. Before that, and when the // permission store is not running at all, every table is empty - // for the same reason -- and "no app has asked for the camera" + // for the same reason -- and "no application has asked for the camera" // is then a measurement nobody took. TextRow { width: section.width @@ -246,7 +246,7 @@ SettingsPage { // session, so an app that has shared four times is four // entries and one question. detail: grantRow.confirming - ? "The recorded answer goes away, and this app is asked again the next time it tries." + ? "The recorded answer goes away, and this application is asked again the next time it tries." : (!section.simple && !section.revokeOnly ? (grantRow.modelData.allowed === true ? "Allowed" : "Withheld") : String(section.modelData.detail) @@ -311,13 +311,14 @@ SettingsPage { } } + Item { width: 1; height: 10 } + // The limit, stated rather than left to be discovered. Saying "your // camera is protected" when a system package can open /dev/video0 // would be a claim this page cannot back up. - TextRow { - label: "Only sandboxed apps are covered" - detail: "These are the answers Flatpak apps gave the permission portal. Programs installed as system packages are not sandboxed, never ask, and reach these devices directly." - divider: false + SettingsNote { + headline: "Only sandboxed applications are covered" + body: "These are the answers Flatpak applications gave the permission portal. Programs installed as system packages are not sandboxed, never ask, and reach these devices directly." } } @@ -334,8 +335,8 @@ SettingsPage { visible: Permissions.available title: "Run in the background" subtitle: backgroundCard.rows.length === 0 - ? "No app has asked to keep running with no window open." - : backgroundCard.rows.length + " app" + ? "No application has asked to keep running with no window open." + : backgroundCard.rows.length + " application" + (backgroundCard.rows.length === 1 ? " is" : "s are") + " allowed to keep running with no window open." @@ -412,10 +413,9 @@ SettingsPage { divider: Keyring.available || Keyring.lastError !== "" } - SettingRow { - visible: Keyring.lastError !== "" + ErrorRow { label: "Keyring problem" - detail: Keyring.lastError + message: Keyring.lastError divider: Keyring.available } @@ -544,7 +544,7 @@ SettingsPage { TextRow { visible: root.showingSecrets && Keyring.listed && Keyring.storedCount === 0 label: "Nothing stored yet" - detail: "Applications that save a password or token will appear here." + detail: "Applications that save a password or token will appear here" value: "" divider: false } @@ -553,7 +553,7 @@ SettingsPage { // ── Traces ─────────────────────────────────────────────────────────────── // // Panama's own, now. Each row clears one thing, once, when asked -- there - // is no "clean up your machine" button here and no number coloured red, + // is no "clean up your machine" button here and no number colored red, // because none of this is a problem to be solved. It is a list of what is // remembered, with a way to stop remembering it. @@ -569,7 +569,7 @@ SettingsPage { label: "Recent files" detail: recentsRow.confirming ? "This empties the list. Applications start filling it again as you open things." - : "The recents list GTK apps show" + : "The recents list GTK applications show" + (Traces.measured ? " · " + Traces.recentsEntries + " entr" + (Traces.recentsEntries === 1 ? "y" : "ies") @@ -710,21 +710,19 @@ SettingsPage { } } + Item { width: 1; height: 10 } + // A pointer, not a control: the history belongs to Vicinae, which owns // the database Super+V browses. Offering a Clear here would mean // reaching into another program's storage behind its back. - TextRow { - label: "Clipboard history" - detail: "Kept by Vicinae, which owns the database Super+V browses. Panama reads that history and does not clear it." - value: "" - divider: Traces.lastError !== "" + SettingsNote { + headline: "Clipboard history is kept by Vicinae" + body: "Vicinae owns the database Super+V browses. Panama reads that history and does not clear it." } - SettingRow { - visible: Traces.lastError !== "" + ErrorRow { label: "That did not work" - detail: Traces.lastError - divider: false + message: Traces.lastError } } @@ -787,8 +785,8 @@ SettingsPage { } ActionRow { - label: "Per-app notifications" - detail: "Which apps may notify, and how much they may show on a locked screen" + label: "Per-application notifications" + detail: "Which applications may notify, and how much they may show on a locked screen" action: "Open Notifications" divider: false onTriggered: ShellState.openSettings("notifications") diff --git a/config/dot/quickshell/modules/settings/README.md b/config/dot/quickshell/modules/settings/README.md index a488dd7..c623ac7 100644 --- a/config/dot/quickshell/modules/settings/README.md +++ b/config/dot/quickshell/modules/settings/README.md @@ -116,7 +116,7 @@ disclosure. Background and foreground are never stored alone: the five surfaces and two text tints are mixed from them, so changing the ground moves the whole family instead of leaving twelve tokens pointing at the old one. -Every route that writes color — typed hex, the colour wheel, the eyedropper, +Every route that writes color — typed hex, the color wheel, the eyedropper, the HSV sliders — ends in `ThemeProfiles.commitActive`, which also recomputes `accentName` as the nearest curated accent. That is what keeps GNOME's accent enum, kitty's border and the lock screen from going stale after a custom edit. @@ -339,7 +339,7 @@ erase a user-selected accent or a theme switch to leave the border behind. `SettingsNote` is deliberately quiet and must stay that way. It explains absences and boundaries — what this session cannot do, and why — and a -warning-coloured box would turn "not offered yet, here is the reason" into +warning-colored box would turn "not offered yet, here is the reason" into "something is wrong here". ### The rows are keyboard-reachable diff --git a/config/dot/quickshell/modules/settings/SectionLabel.qml b/config/dot/quickshell/modules/settings/SectionLabel.qml index 6f6813a..19965fc 100644 --- a/config/dot/quickshell/modules/settings/SectionLabel.qml +++ b/config/dot/quickshell/modules/settings/SectionLabel.qml @@ -1,11 +1,16 @@ -// A small uppercase heading inside a card, with an optional count beside it. +// A small uppercase heading, with an optional count beside it. // -// SectionLabel { text: "Camera"; count: "2 apps have asked" } +// SectionLabel { text: "Camera"; count: "2 applications have asked" } // // A card whose rows come from four different permission tables needs to say // which table a row belongs to, and a second SettingsCard per table would give // four headers, four subtitles and four borders to one subject. This is the // lighter divider: it groups rows without pretending they are separate topics. +// +// It does the same job one level up, between cards, where a page has more +// cards than a reader can hold at once and they fall into two or three +// stretches -- Sound's "Alerts & feedback", Displays' "All displays". Same +// shape either way, which is the point of it being one component. import QtQuick import qs.config @@ -41,6 +46,9 @@ Item { text: root.count color: Theme.alpha(Theme.fgMuted, 0.75) font.family: Theme.fontFamily + // Counts are the usual thing to put here, and a count that changes + // as a filter narrows should not shuffle the digits beside it. + font.features: Theme.tabularFigures font.pixelSize: Math.max(9, Theme.fontSizeSmall - 1) font.letterSpacing: 0.7 } diff --git a/config/dot/quickshell/modules/settings/SettingRow.qml b/config/dot/quickshell/modules/settings/SettingRow.qml index 56bd610..426a5e9 100644 --- a/config/dot/quickshell/modules/settings/SettingRow.qml +++ b/config/dot/quickshell/modules/settings/SettingRow.qml @@ -7,9 +7,14 @@ Item { default property alias trailingData: trailing.data property string icon: "" property string label: "" - // The headline's colour. Foreground for every ordinary row; ErrorRow sets + // The headline's color. Foreground for every ordinary row; ErrorRow sets // it to Theme.danger so a failure reads as one at a glance. property color labelColor: Theme.fg + // The line under the headline. One sentence takes no full stop -- it is a + // caption, not a paragraph, and a lone trailing dot under two hundred rows + // is a speck. More than one sentence takes them throughout, last one + // included, because at that length the reader is reading prose and prose + // that stops without punctuation reads as truncated. property string detail: "" property string value: "" property bool divider: true diff --git a/config/dot/quickshell/modules/settings/SettingsNote.qml b/config/dot/quickshell/modules/settings/SettingsNote.qml index 0928e5d..690b167 100644 --- a/config/dot/quickshell/modules/settings/SettingsNote.qml +++ b/config/dot/quickshell/modules/settings/SettingsNote.qml @@ -7,7 +7,7 @@ // } // // Deliberately quiet. This is not a warning banner and must never grow one's -// colouring: the things it explains are absences and boundaries, not problems, +// coloring: the things it explains are absences and boundaries, not problems, // and a red-edged box would make "this desktop cannot do that yet" read as // "something is wrong here". It sits darker than the card it is in, which is // the whole visual signal it needs -- a note, set into the surface. diff --git a/config/dot/quickshell/modules/settings/SettingsTabs.qml b/config/dot/quickshell/modules/settings/SettingsTabs.qml index e1cd282..77cce11 100644 --- a/config/dot/quickshell/modules/settings/SettingsTabs.qml +++ b/config/dot/quickshell/modules/settings/SettingsTabs.qml @@ -9,7 +9,7 @@ // Only for pages long enough that scrolling hides the control someone came for. // Appearance was six cards deep, so light and dark -- the thing reached most -- // sat below a wallpaper grid and an entire lock screen. Tabs are not a way to -// make a short page look organised; they are for when the page is long enough +// make a short page look organized; they are for when the page is long enough // that the order stops being a suggestion and starts being a burial. // // The strip scrolls horizontally when it overflows: the Settings window tiles diff --git a/config/dot/quickshell/modules/settings/SharingPage.qml b/config/dot/quickshell/modules/settings/SharingPage.qml index 3b90741..f00f0ba 100644 --- a/config/dot/quickshell/modules/settings/SharingPage.qml +++ b/config/dot/quickshell/modules/settings/SharingPage.qml @@ -20,7 +20,7 @@ SettingsPage { title: "Sharing" lede: "What this machine offers to other machines on the network." - Component.onCompleted: Sharing.refresh() + Component.onCompleted: if (!Sharing.scanned) Sharing.refresh() SettingsCard { title: "This machine" @@ -29,12 +29,11 @@ SettingsPage { // The error belongs to the machine, not to a floating banner above the // title: a bare row above the first card read as part of the page // furniture, which is exactly what an error must not do. - TextRow { + ErrorRow { width: parent.width - visible: Sharing.lastError !== "" label: "Sharing needs attention" - detail: Sharing.lastError - value: "" + message: Sharing.lastError + divider: true } TextFieldRow { @@ -78,7 +77,7 @@ SettingsPage { TextRow { visible: Sharing.remoteLoginOn && Sharing.remoteSessions.length === 0 label: "Nobody is signed in remotely" - detail: "Remote login is on, and no one is connected from another machine." + detail: "Remote login is on, and no one is connected from another machine" value: "" divider: false } @@ -219,7 +218,7 @@ SettingsPage { TextRow { visible: Sharing.mediaSharing?.installed !== true label: "Share music and video to devices" - detail: "Needs Rygel, which is not installed." + detail: "Needs Rygel, which is not installed" value: "Not installed" divider: false } diff --git a/config/dot/quickshell/modules/settings/ShortcutRow.qml b/config/dot/quickshell/modules/settings/ShortcutRow.qml index 1475d7a..25d7362 100644 --- a/config/dot/quickshell/modules/settings/ShortcutRow.qml +++ b/config/dot/quickshell/modules/settings/ShortcutRow.qml @@ -88,26 +88,11 @@ Item { font.pixelSize: Theme.fontSizeSmall } - Rectangle { + StatusBadge { anchors.verticalCenter: parent.verticalCenter visible: root.overridden - width: badge.implicitWidth + 14 - height: 18 - radius: Theme.pillRadius - color: Theme.alpha(Theme.warn, 0.10) - border.width: 1 - border.color: Theme.alpha(Theme.warn, 0.32) - - Text { - id: badge - - anchors.centerIn: parent - text: "CHANGED" - color: Theme.warn - font.family: Theme.fontFamily - font.pixelSize: Math.max(8, Theme.fontSizeSmall - 2) - font.weight: Font.DemiBold - } + text: "Changed" + tone: Theme.warn } SettingsButton { diff --git a/config/dot/quickshell/modules/settings/ShortcutsPage.qml b/config/dot/quickshell/modules/settings/ShortcutsPage.qml index 39c0007..da5c3c4 100644 --- a/config/dot/quickshell/modules/settings/ShortcutsPage.qml +++ b/config/dot/quickshell/modules/settings/ShortcutsPage.qml @@ -433,41 +433,12 @@ SettingsPage { width: parent ? parent.width : 620 spacing: 0 - Item { - width: parent.width - height: 30 - - Row { - anchors.left: parent.left - anchors.leftMargin: 6 - anchors.bottom: parent.bottom - anchors.bottomMargin: 4 - spacing: 8 - - Text { - anchors.baseline: groupCount.baseline - text: groupColumn.modelData.name - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.DemiBold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.8 - } - - Text { - id: groupCount - - text: root.filtering - ? "· showing " + groupColumn.modelData.binds.length - + " of " + groupColumn.modelData.total - : "· " + groupColumn.modelData.total - color: Theme.fgMuted - font.family: Theme.fontFamily - font.features: Theme.tabularFigures - font.pixelSize: Theme.fontSizeSmall - } - } + SectionLabel { + text: groupColumn.modelData.name + count: root.filtering + ? "· showing " + groupColumn.modelData.binds.length + + " of " + groupColumn.modelData.total + : "· " + groupColumn.modelData.total } Repeater { diff --git a/config/dot/quickshell/modules/settings/SnapshotsPage.qml b/config/dot/quickshell/modules/settings/SnapshotsPage.qml index a29fa64..b4c6656 100644 --- a/config/dot/quickshell/modules/settings/SnapshotsPage.qml +++ b/config/dot/quickshell/modules/settings/SnapshotsPage.qml @@ -74,14 +74,11 @@ SettingsPage { return Number((config?.limits ?? ({}))[horizon] ?? 0); } - Component.onCompleted: Snapshots.refresh() + Component.onCompleted: if (!Snapshots.scanned) Snapshots.refresh() - TextRow { - visible: Snapshots.lastError !== "" + ErrorRow { label: "Snapshots need attention" - detail: Snapshots.lastError - value: "" - divider: false + message: Snapshots.lastError } // What just happened to the file that was already there. @@ -224,7 +221,7 @@ SettingsPage { TextRow { visible: volumeCard.snapshots.length === 0 label: "No points in time yet" - detail: "One is taken automatically on the schedule above." + detail: "One is taken automatically on the schedule above" value: "" divider: false } @@ -285,7 +282,7 @@ SettingsPage { } SettingsButton { - text: pointRow.confirming ? "Keep" : "Delete" + text: pointRow.confirming ? "Keep" : "Delete…" enabled: !Snapshots.busy onClicked: root.confirmingDelete = pointRow.confirming ? "" : pointRow.token @@ -476,10 +473,10 @@ SettingsPage { // Honest about what cannot be measured: per-snapshot size needs btrfs // quota groups, which cost performance on every write. Reporting a // made-up number would be worse than saying so. - TextRow { + NotMeasuredRow { label: "Space used by snapshots" - detail: "Measuring this per snapshot needs btrfs quotas, which slow down every write. Free space above is the number that matters." value: "Not measured" + because: "Measuring this per snapshot needs btrfs quotas, which slow down every write. Free space above is the number that matters." divider: false } } diff --git a/config/dot/quickshell/modules/settings/SoundBadge.qml b/config/dot/quickshell/modules/settings/SoundBadge.qml deleted file mode 100644 index ca665dc..0000000 --- a/config/dot/quickshell/modules/settings/SoundBadge.qml +++ /dev/null @@ -1,39 +0,0 @@ -// A pill beside a device name: how the device is attached, or why it is not -// here at all. -// -// Nine outputs named after their chipsets look alike in a list. Which of them -// arrives over the network, over Bluetooth, or is simply away in its case is -// the fact that tells them apart, so it is said next to the name rather than -// left to be inferred from a subtitle. - -import QtQuick -import qs.config - -Rectangle { - id: root - - property string text: "" - // The token the badge is drawn from -- cyan for network devices, the accent - // for Bluetooth, muted for a device that is not present. - property color tone: Theme.accent - - implicitWidth: label.implicitWidth + 14 - implicitHeight: 17 - radius: Theme.pillRadius - color: Theme.alpha(root.tone, 0.10) - border.width: 1 - border.color: Theme.alpha(root.tone, 0.28) - - Text { - id: label - - anchors.centerIn: parent - text: root.text - color: root.tone - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2) - font.weight: Font.DemiBold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.5 - } -} diff --git a/config/dot/quickshell/modules/settings/SoundCaptureRow.qml b/config/dot/quickshell/modules/settings/SoundCaptureRow.qml index 17bcd25..e8a9ea4 100644 --- a/config/dot/quickshell/modules/settings/SoundCaptureRow.qml +++ b/config/dot/quickshell/modules/settings/SoundCaptureRow.qml @@ -47,7 +47,7 @@ SettingRow { readonly property bool muted: AudioDevices.applicationMuted(chip.modelData) implicitWidth: name.implicitWidth + muteButton.width + 20 - implicitHeight: 28 + implicitHeight: 26 radius: Theme.pillRadius color: Theme.alpha(Theme.fg, 0.06) border.width: 1 diff --git a/config/dot/quickshell/modules/settings/SoundChannelStrip.qml b/config/dot/quickshell/modules/settings/SoundChannelStrip.qml index 4e2ec36..8ea0aac 100644 --- a/config/dot/quickshell/modules/settings/SoundChannelStrip.qml +++ b/config/dot/quickshell/modules/settings/SoundChannelStrip.qml @@ -1,6 +1,6 @@ // One chip per channel the selected output actually has. // -// "Test" used to play a single front-centre sample, which answers "is this the +// "Test" used to play a single front-center sample, which answers "is this the // right device" and nothing else. A surround receiver wired to the wrong socket // passes that test. Each chip plays its own channel's sample, so the answer to // "is the rear left speaker actually rear left" is one click rather than a diff --git a/config/dot/quickshell/modules/settings/SoundDeviceRow.qml b/config/dot/quickshell/modules/settings/SoundDeviceRow.qml index 3848bd9..db0a2a4 100644 --- a/config/dot/quickshell/modules/settings/SoundDeviceRow.qml +++ b/config/dot/quickshell/modules/settings/SoundDeviceRow.qml @@ -183,7 +183,7 @@ Item { clip: true // Full-width gradient behind a clipped window, so a - // given level is always the same colour rather than a + // given level is always the same color rather than a // squashed copy of the whole ramp. Rectangle { width: meter.width @@ -298,19 +298,19 @@ Item { anchors.verticalCenter: parent.verticalCenter spacing: 6 - SoundBadge { + StatusBadge { visible: root.airplay && !root.ghost text: "AirPlay" tone: Theme.cyan } - SoundBadge { + StatusBadge { visible: root.bluetooth && !root.ghost text: "Bluetooth" tone: Theme.accent } - SoundBadge { + StatusBadge { visible: root.ghost text: "Returns when connected" tone: Theme.fgMuted diff --git a/config/dot/quickshell/modules/settings/SoundPage.qml b/config/dot/quickshell/modules/settings/SoundPage.qml index 02c26d5..211942a 100644 --- a/config/dot/quickshell/modules/settings/SoundPage.qml +++ b/config/dot/quickshell/modules/settings/SoundPage.qml @@ -27,12 +27,20 @@ SettingsPage { // moves and the region past 100% is marked. readonly property real volumeMaximum: Settings.overAmplification ? 1.5 : 1 - // The three services that read the machine rather than the graph do it once - // per page open, the way the Displays page refreshes hardware brightness. + // The three services that read the machine rather than the graph are asked + // when the page opens, the way the Displays page probes hardware + // brightness. Two of them read hardware that does not change while Settings + // is open, so they are asked once a session and not once a visit. + // + // SoundFeedback is not one of them: it reads GSettings keys any GNOME + // control panel can rewrite behind this page's back, so it is re-read every + // open rather than trusted from the first one. Component.onCompleted: { SoundFeedback.refresh(); - SoundDefaults.refresh(); - SoundCards.refresh(); + if (!SoundDefaults.loaded) + SoundDefaults.refresh(); + if (!SoundCards.loaded) + SoundCards.refresh(); } SettingsCard { @@ -136,17 +144,7 @@ SettingsPage { } // ── What the machine says when it wants attention ─────────────────────── - Text { - width: parent.width - text: "Alerts & feedback" - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.DemiBold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.7 - topPadding: 6 - } + SectionLabel { text: "Alerts & feedback" } SettingsCard { title: "Alerts" @@ -189,17 +187,7 @@ SettingsPage { } // ── The hardware underneath the devices ───────────────────────────────── - Text { - width: parent.width - text: "Advanced" - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.DemiBold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.7 - topPadding: 6 - } + SectionLabel { text: "Advanced" } SettingsCard { title: "Device profiles" diff --git a/config/dot/quickshell/modules/settings/SshKeysPage.qml b/config/dot/quickshell/modules/settings/SshKeysPage.qml index 84bd63e..705ed4a 100644 --- a/config/dot/quickshell/modules/settings/SshKeysPage.qml +++ b/config/dot/quickshell/modules/settings/SshKeysPage.qml @@ -157,7 +157,7 @@ SettingsPage { TextRow { visible: SshKeys.scanned && SshKeys.keys.length === 0 label: "Nothing here yet" - detail: "A key you generate below shows up here, with its fingerprint and whether the agent is holding it." + detail: "A key you generate below shows up here, with its fingerprint and whether the agent is holding it" value: "" } @@ -346,7 +346,7 @@ SettingsPage { TextRow { visible: SshKeys.agent?.available === true && root.heldKeys.length === 0 label: "Nothing loaded" - detail: "Add a key above and the agent will offer it without asking for its passphrase again." + detail: "Add a key above and the agent will offer it without asking for its passphrase again" value: "" } @@ -390,12 +390,14 @@ SettingsPage { // moment later, because it is read back off disk. The Remove button // above stays -- the helper refuses and says this, which is a better // answer than a button that is not there. - TextRow { + Item { width: 1; height: agentNote.visible ? 10 : 0 } + + SettingsNote { + id: agentNote + visible: SshKeys.agent?.available === true && !SshKeys.durableRemoval - label: "Removing a key from this agent does not stick" - detail: "It lists every key in ~/.ssh, so one removed comes straight back at the next sign-in. That is the keyring agent's design, not a bug — move the file out of ~/.ssh to stop it being offered." - value: "" - divider: false + headline: "Removing a key from this agent does not stick" + body: "It lists every key in ~/.ssh, so one removed comes straight back at the next sign-in. That is the keyring agent's design, not a bug — move the file out of ~/.ssh to stop it being offered." } } diff --git a/config/dot/quickshell/modules/settings/StatusBadge.qml b/config/dot/quickshell/modules/settings/StatusBadge.qml new file mode 100644 index 0000000..c8f4e99 --- /dev/null +++ b/config/dot/quickshell/modules/settings/StatusBadge.qml @@ -0,0 +1,47 @@ +// A pill beside a name: the one fact about that thing which decides how to +// read the rest of the row. +// +// StatusBadge { text: "Bluetooth"; tone: Theme.accent } +// StatusBadge { text: "No reader"; tone: Theme.warn } +// +// It was born on the Sound page as SoundBadge, where nine outputs named after +// their chipsets look alike in a list and which of them arrives over the +// network, over Bluetooth, or is simply away in its case is what tells them +// apart. That turned out to be the general shape -- an account that is +// connected, a package that came from Flatpak, a shortcut that has been +// changed -- so it lost the "Sound" and became the one pill the settings use. +// +// Always a single short token, always upper-cased here rather than by the +// caller. A badge carrying a sentence is a detail line wearing a border. + +import QtQuick +import qs.config + +Rectangle { + id: root + + property string text: "" + // The token the badge is drawn from -- cyan for network devices, the accent + // for Bluetooth, muted for a device that is not present. + property color tone: Theme.accent + + implicitWidth: label.implicitWidth + 14 + implicitHeight: 17 + radius: Theme.pillRadius + color: Theme.alpha(root.tone, 0.10) + border.width: 1 + border.color: Theme.alpha(root.tone, 0.28) + + Text { + id: label + + anchors.centerIn: parent + text: root.text + color: root.tone + font.family: Theme.fontFamily + font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2) + font.weight: Font.DemiBold + font.capitalization: Font.AllUppercase + font.letterSpacing: 0.5 + } +} diff --git a/config/dot/quickshell/modules/settings/StoragePage.qml b/config/dot/quickshell/modules/settings/StoragePage.qml index 52418f7..94c5d95 100644 --- a/config/dot/quickshell/modules/settings/StoragePage.qml +++ b/config/dot/quickshell/modules/settings/StoragePage.qml @@ -51,14 +51,16 @@ SettingsPage { readonly property var removableDrives: Disks.drives.filter(entry => entry.removable) + // Unguarded on purpose, unlike the other refresh-on-open pages: this is the + // cheap snapshot, not the walk. Free space moves while Settings is open -- + // that is most of the reason someone comes back to this page -- and + // disks-contract pins the bare call so a later sweep cannot quietly turn it + // into a once-a-session read. The expensive walks are asked for by hand. Component.onCompleted: Disks.refresh() - TextRow { - visible: Disks.lastError !== "" + ErrorRow { label: "Storage needs attention" - detail: Disks.lastError - value: "" - divider: false + message: Disks.lastError } // ── Space ──────────────────────────────────────────────────────────────── @@ -113,7 +115,7 @@ SettingsPage { visible: Disks.breakdownMeasured && Disks.breakdown && Disks.breakdown.complete === false label: "The walk ran out of time" - detail: "Home, applications and caches are floors rather than totals, so the remainder is larger than it should be." + detail: "Home, applications and caches are floors rather than totals, so the remainder is larger than it should be" value: "" divider: false } @@ -249,11 +251,10 @@ SettingsPage { } } - TextRow { + NotMeasuredRow { visible: Disks.foldersMeasured && Disks.folderScanTruncated label: "Some folders were not measured" - detail: "The walk ran out of time before reaching them, so this list is incomplete." - value: "" + because: "The walk ran out of time before reaching them, so this list is incomplete" divider: false } } @@ -343,7 +344,7 @@ SettingsPage { TextRow { visible: Disks.cleanablesMeasured && (Disks.cleanables ?? []).length === 0 label: "Nothing itemized" - detail: "Caches, the trash, unused Flatpak runtimes and the package download cache were all measured and none of them holds anything." + detail: "Caches, the trash, unused Flatpak runtimes and the package download cache were all measured and none of them holds anything" value: "" divider: false } @@ -473,7 +474,7 @@ SettingsPage { TextRow { visible: root.removableDrives.length === 0 label: "None connected" - detail: "Drives you plug in appear here, with a way to unmount them safely." + detail: "Drives you plug in appear here, with a way to unmount them safely" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/SyncPage.qml b/config/dot/quickshell/modules/settings/SyncPage.qml index a239d45..05daf08 100644 --- a/config/dot/quickshell/modules/settings/SyncPage.qml +++ b/config/dot/quickshell/modules/settings/SyncPage.qml @@ -13,7 +13,7 @@ // and restoring defaults both used to happen on a single press, which is the // one interaction on this page nobody can take back: the two-stage confirm is // the same one Snapshots and Privacy use, where the first press only arms the -// second and the second is the one wearing the danger colour. +// second and the second is the one wearing the danger color. import QtQuick import qs.config diff --git a/config/dot/quickshell/modules/settings/ThemeCard.qml b/config/dot/quickshell/modules/settings/ThemeCard.qml index 52f044c..4e72734 100644 --- a/config/dot/quickshell/modules/settings/ThemeCard.qml +++ b/config/dot/quickshell/modules/settings/ThemeCard.qml @@ -219,7 +219,7 @@ Rectangle { ? "Delete " + root.theme.name + ", press again to confirm" : "Delete " + root.theme.name Accessible.description: root.removeArmed - ? "This theme's palette, terminal colours and effects go with it" + ? "This theme's palette, terminal colors and effects go with it" : "" Accessible.focusable: true Accessible.focused: remove.activeFocus diff --git a/config/dot/quickshell/modules/settings/ThemeEditorWells.qml b/config/dot/quickshell/modules/settings/ThemeEditorWells.qml index edcab70..e11cecd 100644 --- a/config/dot/quickshell/modules/settings/ThemeEditorWells.qml +++ b/config/dot/quickshell/modules/settings/ThemeEditorWells.qml @@ -7,7 +7,7 @@ // whole family with it rather than leaving twelve tokens pointing at the old // one. // -// One eyedropper process and one colour wheel are shared by all four wells -- +// One eyedropper process and one color wheel are shared by all four wells -- // the target is remembered rather than duplicated four times. import QtQuick @@ -46,7 +46,7 @@ Grid { function apply(which: string, hex: string): void { const value = String(hex).trim().toLowerCase(); if (!/^#[0-9a-f]{6}$/.test(value)) { - root.lastError = "That is not a colour this theme can use."; + root.lastError = "That is not a color this theme can use."; return; } root.lastError = ""; @@ -97,7 +97,7 @@ Grid { ColorDialog { id: wheel - title: "Choose a colour" + title: "Choose a color" // Qt hands back a color value, not a string; toString() on it is // "#aarrggbb" when there is alpha, so the channels are read directly. onAccepted: root.apply(root.target, Qt.rgba( @@ -113,7 +113,7 @@ Grid { } onExited: (exitCode, exitStatus) => { if (exitCode !== 0) - root.lastError = "Screen colour picking was cancelled or unavailable."; + root.lastError = "Screen color picking was cancelled or unavailable."; } } } diff --git a/config/dot/quickshell/modules/settings/ThemeGallery.qml b/config/dot/quickshell/modules/settings/ThemeGallery.qml index a010ad4..1775a4e 100644 --- a/config/dot/quickshell/modules/settings/ThemeGallery.qml +++ b/config/dot/quickshell/modules/settings/ThemeGallery.qml @@ -6,7 +6,6 @@ // 900px is narrower than its own caption. import QtQuick -import qs.config import qs.services Column { @@ -25,15 +24,7 @@ Column { readonly property real cellWidth: root.columns > 0 ? (root.width - (root.columns - 1) * 12) / root.columns : 200 - Text { - text: root.heading - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Theme.fontSizeSmall - font.weight: Font.DemiBold - font.capitalization: Font.AllUppercase - font.letterSpacing: 0.7 - } + SectionLabel { text: root.heading } Grid { width: parent.width diff --git a/config/dot/quickshell/modules/settings/ThemeSaveRow.qml b/config/dot/quickshell/modules/settings/ThemeSaveRow.qml index 8534c4e..c06fad0 100644 --- a/config/dot/quickshell/modules/settings/ThemeSaveRow.qml +++ b/config/dot/quickshell/modules/settings/ThemeSaveRow.qml @@ -1,6 +1,6 @@ // Naming what you built. // -// Saving snapshots the palette, the terminal colours and the ten effect values +// Saving snapshots the palette, the terminal colors and the ten effect values // as they stand, so a saved theme restores the whole look rather than two // accent hexes. Delete appears only for a saved theme, because a shipped one // cannot be removed -- it is read from the catalog every launch. @@ -16,10 +16,10 @@ SettingRow { label: "Save as" // Armed, the row says what the delete actually costs: a saved theme is the - // only copy of its palette, terminal colours and effect values. + // only copy of its palette, terminal colors and effect values. detail: deleteConfirm.armed ? "Deletes “" + String(ThemeProfiles.activeProfile.name ?? "this theme") - + "” — its palette, terminal colours and effects go with it" + + "” — its palette, terminal colors and effects go with it" : (root.custom ? "Saving again under a new name keeps both" : "Editing a shipped theme forks it; give the fork a name to keep it") diff --git a/config/dot/quickshell/modules/settings/ThemeStartChips.qml b/config/dot/quickshell/modules/settings/ThemeStartChips.qml index 2494b87..e207a28 100644 --- a/config/dot/quickshell/modules/settings/ThemeStartChips.qml +++ b/config/dot/quickshell/modules/settings/ThemeStartChips.qml @@ -30,7 +30,7 @@ Flow { readonly property bool active: chip.modelData.id === ThemeProfiles.activeId implicitWidth: caption.implicitWidth + dot.width + 26 - implicitHeight: 31 + implicitHeight: 26 radius: Theme.pillRadius color: chip.active ? Theme.alpha(Theme.accent, 0.12) diff --git a/config/dot/quickshell/modules/settings/TilingPage.qml b/config/dot/quickshell/modules/settings/TilingPage.qml index 592ac36..0906232 100644 --- a/config/dot/quickshell/modules/settings/TilingPage.qml +++ b/config/dot/quickshell/modules/settings/TilingPage.qml @@ -32,7 +32,7 @@ SettingsPage { // one application is what somebody came here to write, and the gaps and // borders below are set once and left alone. SettingsCard { - title: "App rules" + title: "Application window rules" subtitle: "How specific applications behave when they open. Behaviors, not regexes: pick an application, tick what it should do. Panama's own surfaces cannot be matched." Repeater { diff --git a/config/dot/quickshell/modules/settings/UpdatesPage.qml b/config/dot/quickshell/modules/settings/UpdatesPage.qml index d05d22e..abbe175 100644 --- a/config/dot/quickshell/modules/settings/UpdatesPage.qml +++ b/config/dot/quickshell/modules/settings/UpdatesPage.qml @@ -40,7 +40,8 @@ SettingsPage { property string expandedPackage: "" Component.onCompleted: { - Updates.refresh(); + if (!Updates.scanned) + Updates.refresh(); // A first visit with nothing cached should not show a confident "up to // date" it has no basis for, so it goes and finds out. if (!Updates.everChecked && !Updates.checking) @@ -103,12 +104,9 @@ SettingsPage { // ── What just happened ─────────────────────────────────────────────────── - TextRow { - visible: Updates.lastError !== "" + ErrorRow { label: "Updates need attention" - detail: Updates.lastError - value: "" - divider: false + message: Updates.lastError } TextRow { @@ -288,17 +286,18 @@ SettingsPage { // A check that failed is not a machine that is current. "Current" is // only ever said about a list that was actually read. - TextRow { + NotMeasuredRow { visible: Updates.sourceError("flatpak") !== "" label: "Flatpak" - detail: Updates.sourceError("flatpak") value: "Not checked" + because: Updates.sourceError("flatpak") + divider: true } TextRow { visible: Updates.sourceError("flatpak") === "" && Updates.flatpak?.available === false label: "Flatpak" - detail: "Flatpak is not installed." + detail: "Flatpak is not installed" value: "Unavailable" } @@ -376,18 +375,18 @@ SettingsPage { onTriggered: Updates.apply("flatpak") } - TextRow { + NotMeasuredRow { visible: Updates.sourceError("firmware") !== "" label: "Firmware" - detail: Updates.sourceError("firmware") value: "Not checked" + because: Updates.sourceError("firmware") divider: false } TextRow { visible: Updates.sourceError("firmware") === "" && Updates.firmware?.available === false label: "Firmware" - detail: "Firmware updating is not available on this machine." + detail: "Firmware updating is not available on this machine" value: "Unavailable" divider: false } @@ -460,7 +459,7 @@ SettingsPage { TextRow { visible: Updates.historyLoaded && Updates.history.length === 0 label: "Nothing recorded" - detail: "Neither dnf nor flatpak has a transaction history here." + detail: "Neither dnf nor flatpak has a transaction history here" value: "" divider: false } diff --git a/config/dot/quickshell/modules/settings/UsersPage.qml b/config/dot/quickshell/modules/settings/UsersPage.qml index c1c347b..9f6b643 100644 --- a/config/dot/quickshell/modules/settings/UsersPage.qml +++ b/config/dot/quickshell/modules/settings/UsersPage.qml @@ -155,20 +155,19 @@ SettingsPage { } Component.onCompleted: { - UserAccounts.refresh(); + if (!UserAccounts.scanned) + UserAccounts.refresh(); // Probing fprintd bus-activates it, so it waits for the page rather // than costing every shell launch. - Fingerprint.refresh(); + if (!Fingerprint.scanned) + Fingerprint.refresh(); } // A write that failed is a row, not a page. Everything below stays usable, // because the thing that refused is almost never the thing being read. - TextRow { - visible: UserAccounts.lastError !== "" + ErrorRow { label: "That change did not go through" - detail: UserAccounts.lastError - value: "" - divider: false + message: UserAccounts.lastError } // ── You ────────────────────────────────────────────────────────────────── @@ -466,26 +465,10 @@ SettingsPage { anchors.verticalCenter: parent.verticalCenter spacing: 8 - Rectangle { + StatusBadge { anchors.verticalCenter: parent.verticalCenter - width: noReader.implicitWidth + 14 - height: 17 - radius: Theme.pillRadius - color: Theme.alpha(Theme.warn, 0.1) - border.width: 1 - border.color: Theme.alpha(Theme.warn, 0.3) - - Text { - id: noReader - - anchors.centerIn: parent - text: "NO READER" - color: Theme.warn - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2) - font.weight: Font.DemiBold - font.letterSpacing: 0.5 - } + text: "No reader" + tone: Theme.warn } SettingsButton { @@ -513,17 +496,7 @@ SettingsPage { onToggled: value => Fingerprint.setUnlockEnabled(value) } - Text { - width: parent.width - text: "ENROLLED FINGERS" - color: Theme.fgMuted - font.family: Theme.fontFamily - font.pixelSize: Math.max(9, Theme.fontSizeSmall - 1) - font.weight: Font.DemiBold - font.letterSpacing: 0.8 - topPadding: 12 - bottomPadding: 4 - } + SectionLabel { text: "Enrolled fingers" } TextRow { width: parent.width @@ -620,12 +593,9 @@ SettingsPage { } } - TextRow { - visible: Fingerprint.lastError !== "" + ErrorRow { label: "The reader had something to say" - detail: Fingerprint.lastError - value: "" - divider: false + message: Fingerprint.lastError } } diff --git a/config/dot/quickshell/modules/settings/WindowRuleEditor.qml b/config/dot/quickshell/modules/settings/WindowRuleEditor.qml index 65403ee..10e0612 100644 --- a/config/dot/quickshell/modules/settings/WindowRuleEditor.qml +++ b/config/dot/quickshell/modules/settings/WindowRuleEditor.qml @@ -110,7 +110,7 @@ Column { width: parent.width visible: root.openWindows.length > 0 label: "Open right now" - detail: "The class comes from the compositor, so these are exact." + detail: "The class comes from the compositor, so these are exact" current: root.windowClass options: root.openWindows divider: false @@ -248,7 +248,7 @@ Column { width: parent.width visible: root.workspaceOn label: "Workspace" - detail: "The ten the keymap reaches." + detail: "The ten the keymap reaches" current: root.workspace options: root.workspaceOptions divider: false diff --git a/config/dot/quickshell/modules/settings/WorkspacesPage.qml b/config/dot/quickshell/modules/settings/WorkspacesPage.qml index 33743eb..7fe1134 100644 --- a/config/dot/quickshell/modules/settings/WorkspacesPage.qml +++ b/config/dot/quickshell/modules/settings/WorkspacesPage.qml @@ -96,11 +96,9 @@ SettingsPage { } } - TextRow { - visible: Projects.lastError !== "" + ErrorRow { label: "Problem" - detail: Projects.lastError - divider: false + message: Projects.lastError } } } diff --git a/config/dot/quickshell/modules/settings/qmldir b/config/dot/quickshell/modules/settings/qmldir index 0846254..fb18df3 100644 --- a/config/dot/quickshell/modules/settings/qmldir +++ b/config/dot/quickshell/modules/settings/qmldir @@ -97,7 +97,6 @@ PasswordField 1.0 PasswordField.qml AudioBalance 1.0 AudioBalance.qml SoundDeviceList 1.0 SoundDeviceList.qml SoundDeviceRow 1.0 SoundDeviceRow.qml -SoundBadge 1.0 SoundBadge.qml SoundChannelStrip 1.0 SoundChannelStrip.qml SoundVolumeRow 1.0 SoundVolumeRow.qml SoundThemeRow 1.0 SoundThemeRow.qml @@ -117,6 +116,7 @@ TextEntryRow 1.0 TextEntryRow.qml PrivacyPage 1.0 PrivacyPage.qml PrivacyLiveTile 1.0 PrivacyLiveTile.qml SectionLabel 1.0 SectionLabel.qml +StatusBadge 1.0 StatusBadge.qml SearchPicker 1.0 SearchPicker.qml OnlineAccountsPage 1.0 OnlineAccountsPage.qml TextFieldRow 1.0 TextFieldRow.qml diff --git a/config/dot/quickshell/modules/welcome/Welcome.qml b/config/dot/quickshell/modules/welcome/Welcome.qml index c097892..91468f1 100644 --- a/config/dot/quickshell/modules/welcome/Welcome.qml +++ b/config/dot/quickshell/modules/welcome/Welcome.qml @@ -52,19 +52,19 @@ PanelWindow { find: "Overview", fallback: "Super + `", title: "See every window", - detail: "All your workspaces at once, and search across them." + detail: "All your workspaces at once, and search across them" }, { find: "Settings", fallback: "Super + I", title: "Change anything", - detail: "Displays, sound, network, appearance, and everything else." + detail: "Displays, sound, network, appearance, and everything else" }, { find: "Keyboard shortcuts", fallback: "Super + /", title: "The rest of the keys", - detail: "Every shortcut this desktop has, whenever you want it." + detail: "Every shortcut this desktop has, whenever you want it" } ] diff --git a/config/dot/quickshell/services/ColorScheme.qml b/config/dot/quickshell/services/ColorScheme.qml index 23ef871..9be8627 100644 --- a/config/dot/quickshell/services/ColorScheme.qml +++ b/config/dot/quickshell/services/ColorScheme.qml @@ -61,7 +61,7 @@ Singleton { // // The theme id alone is not enough: editing a custom theme's background // keeps the same id, and the shell would repaint from the binding while - // kitty, GTK and the lock screen kept the old colours -- the change would + // kitty, GTK and the lock screen kept the old colors -- the change would // look like it had only half landed. The values are in the key too, so any // edit to the active palette is a change worth pushing. readonly property string palettePrint: { @@ -192,7 +192,7 @@ Singleton { // A theme change is a palette change: selecting another theme, or // editing the active one. accentName is the NEAREST CURATED name, so // it can hold still across both -- Moon to Moon Rose keeps "blue" and - // moves every other colour. + // moves every other color. const themeChanged = force || themeId !== root.appliedThemeId || palettePrint !== root.appliedPalettePrint; diff --git a/config/dot/quickshell/services/Displays.qml b/config/dot/quickshell/services/Displays.qml index 139969b..5c81a87 100644 --- a/config/dot/quickshell/services/Displays.qml +++ b/config/dot/quickshell/services/Displays.qml @@ -1,7 +1,7 @@ pragma Singleton // Display configuration: resolution, refresh rate, scale, rotation, position, -// colour management, variable refresh rate, and mirroring. +// color management, variable refresh rate, and mirroring. // // This is the only page in Panama Settings where a wrong value can leave you // unable to SEE the screen well enough to undo it. A mode the display cannot @@ -85,7 +85,7 @@ Singleton { { value: 3, label: "Portrait (flipped)" } ] - // Colour management presets Hyprland accepts as `cm`. "auto" is a policy, + // Color management presets Hyprland accepts as `cm`. "auto" is a policy, // not a state: the compositor resolves it to a concrete preset and reports // that one back, so it is never verified against readback. readonly property var colorProfiles: [ @@ -510,7 +510,7 @@ Singleton { } // Everything past `primary` is optional so that arrangements stored before - // the colour and mirror fields existed still load. Present but invalid is + // the color and mirror fields existed still load. Present but invalid is // not optional: a half-written record is one Panama refuses rather than // guesses at, exactly as it treats a half-written position. function isPersistedLayoutEntry(entry: var): bool { @@ -675,7 +675,7 @@ Singleton { return root.formatBitdepth(String(monitor.currentFormat ?? "")); } - // The colour half of the readback comparison. Each field is asserted only + // The color half of the readback comparison. Each field is asserted only // where the compositor reports something to compare against; vrrMode is // absent by design, since `vrr` reads back live state rather than policy. function matchesColor(monitor: var, requested: var): bool { @@ -732,7 +732,7 @@ Singleton { // One-field controls remain callers of the complete-layout transaction. // Their edit is cloned into the current layout so every output's position, - // colour and mirror state participates in apply, verification, and + // color and mirror state participates in apply, verification, and // rollback. `partial` carries only the fields being changed; anything it // leaves out keeps the value currentLayout just read from the compositor. function applyRecord(output: string, partial: var): bool { diff --git a/config/dot/quickshell/services/FocusModes.qml b/config/dot/quickshell/services/FocusModes.qml index 902db45..71e0a90 100644 --- a/config/dot/quickshell/services/FocusModes.qml +++ b/config/dot/quickshell/services/FocusModes.qml @@ -204,7 +204,7 @@ Singleton { effects.push("keeps the screen awake"); const allow = Array.isArray(mode.allow) ? mode.allow : []; if (allow.length > 0) - effects.push(allow.length + (allow.length === 1 ? " app may interrupt" : " apps may interrupt")); + effects.push(allow.length + (allow.length === 1 ? " application may interrupt" : " applications may interrupt")); const when = triggers.length > 0 ? "When " + triggers.join(" or ") : "Never turns on"; return effects.length > 0 ? when + " · " + effects.join(", ") : when; } diff --git a/config/dot/quickshell/services/Health.qml b/config/dot/quickshell/services/Health.qml index 5d8db96..628868c 100644 --- a/config/dot/quickshell/services/Health.qml +++ b/config/dot/quickshell/services/Health.qml @@ -67,7 +67,7 @@ Singleton { } } - // "danger" | "warn" | "muted" | "ok". Named rather than a colour: Theme is + // "danger" | "warn" | "muted" | "ok". Named rather than a color: Theme is // a UI concern and this is a service. readonly property string tone: { switch (root.headlineState) { diff --git a/config/dot/quickshell/services/Permissions.qml b/config/dot/quickshell/services/Permissions.qml index f34d3e2..0af5e0a 100644 --- a/config/dot/quickshell/services/Permissions.qml +++ b/config/dot/quickshell/services/Permissions.qml @@ -77,7 +77,7 @@ Singleton { } // The devices half of the store, in the shape the applications list has - // always read it in. Kept so that "this app has a privacy rule" keeps + // always read it in. Kept so that "this application has a privacy rule" keeps // working there without that page having to learn about tables. readonly property var devices: { const known = root.tables; diff --git a/config/dot/quickshell/services/SettingsBackup.qml b/config/dot/quickshell/services/SettingsBackup.qml index 5c0f615..83922d1 100644 --- a/config/dot/quickshell/services/SettingsBackup.qml +++ b/config/dot/quickshell/services/SettingsBackup.qml @@ -310,14 +310,14 @@ Singleton { applyRestoredState.restart(); } - // The colour, depth and mirror fields a stored arrangement may carry beyond + // The color, depth and mirror fields a stored arrangement may carry beyond // its geometry. Optional exactly as Displays.isPersistedLayoutEntry has // them: an arrangement written before these existed still restores, and a // field that is present but invalid refuses the whole entry rather than // being guessed at. // // Leaving them out was a real loss, not a cosmetic one. The restored record - // is built on top of the LIVE layout, so a snapshot's colour profile, + // is built on top of the LIVE layout, so a snapshot's color profile, // bit depth and SDR levels were silently replaced by whatever the display // is showing right now -- and layoutsEqual, comparing only geometry, then // judged the two identical and skipped the apply that would have put them diff --git a/config/dot/quickshell/services/SettingsSearch.qml b/config/dot/quickshell/services/SettingsSearch.qml index 076a2d3..e83e700 100644 --- a/config/dot/quickshell/services/SettingsSearch.qml +++ b/config/dot/quickshell/services/SettingsSearch.qml @@ -223,7 +223,7 @@ Singleton { // permissions" appears twice on purpose: the Applications page answers // what a Flatpak's sandbox exposes, this one answers what the portal // recorded, and they are different questions with the same name. - { label: "Background apps", detail: "Which applications may keep running after you close them", page: "privacy" }, + { label: "Background applications", detail: "Which applications may keep running after you close them", page: "privacy" }, { label: "Screen sharing permission", detail: "Take back a screen-capture grant an application was given", page: "privacy" }, { label: "Remote desktop permission", detail: "Take back a grant to control this desktop's pointer and keyboard", page: "privacy" }, { label: "Clear recent files", detail: "Empty the list of documents this desktop remembers you opening", page: "privacy" }, @@ -295,8 +295,8 @@ Singleton { { label: "Quiet hours", detail: "The schedule the Sleep focus mode keeps", page: "notifications" }, { label: "Critical alerts break through", detail: "Let urgent notifications past Do Not Disturb", page: "notifications" }, { label: "Application notification rules", detail: "Which applications may notify you, and how", page: "notifications" }, - { label: "Forget an app's notifications", detail: "Remove its rule; it returns on its next notification", page: "notifications" }, - { label: "Per-app notification sound", detail: "Turn the chime off for one application", page: "notifications" }, + { label: "Forget an application's notifications", detail: "Remove its rule; it returns on its next notification", page: "notifications" }, + { label: "Per-application notification sound", detail: "Turn the chime off for one application", page: "notifications" }, { label: "Banners or history", detail: "Send one application straight to history without a popup", page: "notifications" }, { label: "Focus session duration", detail: "How long a focus session runs before it ends itself", page: "focus" }, { label: "Keyboard shortcuts", detail: "Every shortcut the compositor has bound", page: "shortcuts" }, @@ -309,8 +309,8 @@ Singleton { // of the three is a preference with a label to find it by. { label: "Custom shortcut", detail: "Bind your own keys to an application, a shell action, or a window action", page: "shortcuts" }, { label: "Add a shortcut", detail: "Press the chord you want, then pick what it should do", page: "shortcuts" }, - { label: "Launch an app with a shortcut", detail: "Give an application its own key combination", page: "shortcuts" }, - { label: "App rules", detail: "Per-application window rules: float, centre, size, workspace, and no dimming", page: "tiling" }, + { label: "Launch an application with a shortcut", detail: "Give an application its own key combination", page: "shortcuts" }, + { label: "Application window rules", detail: "Per-application window rules: float, center, size, workspace, and no dimming", page: "tiling" }, { label: "Window rules", detail: "Make one application always float, open on a workspace, or skip the animations", page: "tiling" }, { label: "Always float a window", detail: "A per-application rule, so one application stops being tiled", page: "tiling" }, { label: "Gestures", detail: "Three-finger swipes as shipped, and four-finger swipes you assign yourself", page: "mouse" }, @@ -333,10 +333,10 @@ Singleton { { label: "Screen reader", detail: "Start Orca and see whether the accessibility bus is up", page: "accessibility" }, { label: "Orca", detail: "The screen reader: whether it is running, and starting or stopping it", page: "accessibility" }, { label: "Sticky keys", detail: "Why sticky, slow and bounce keys are not offered in this session", page: "accessibility" }, - // The colour filter is a compositor shader rather than a switch, so it + // The color filter is a compositor shader rather than a switch, so it // is findable by the condition rather than only by the word "filter". - { label: "Color filter", detail: "A whole-screen filter the compositor renders: grayscale, or one for each kind of colour blindness", page: "accessibility" }, - { label: "Grayscale", detail: "Drain the colour out of the whole screen", page: "accessibility" }, + { label: "Color filter", detail: "A whole-screen filter the compositor renders: grayscale, or one for each kind of color blindness", page: "accessibility" }, + { label: "Grayscale", detail: "Drain the color out of the whole screen", page: "accessibility" }, { label: "Color blindness", detail: "Protanopia, deuteranopia and tritanopia filters applied to the whole screen", page: "accessibility" }, { label: "Dictation", detail: "Speech to text with Super+D, typed where the cursor is", page: "dictation" }, { label: "Speech to text", detail: "Set up the local speech server and model", page: "dictation" }, @@ -354,8 +354,8 @@ Singleton { { label: "Rotation", detail: "Turn a display a quarter, half, or three-quarter turn", page: "displays" }, { label: "HDR", detail: "High dynamic range for a display that supports it", page: "displays" }, { label: "Color profile", detail: "Automatic, sRGB, wide gamut, or HDR per display", page: "displays" }, - { label: "Bit depth", detail: "8-bit or 10-bit colour, per display", page: "displays" }, - { label: "10-bit", detail: "Deeper colour with less gradient banding", page: "displays" }, + { label: "Bit depth", detail: "8-bit or 10-bit color, per display", page: "displays" }, + { label: "10-bit", detail: "Deeper color with less gradient banding", page: "displays" }, { label: "SDR brightness", detail: "How bright ordinary content sits inside HDR", page: "displays" }, { label: "Mirror displays", detail: "Show the same picture on a second display", page: "displays" }, { label: "Variable refresh rate", detail: "Override the gaming policy for one display", page: "displays" }, @@ -372,7 +372,7 @@ Singleton { { label: "Tokyo Night", detail: "Moon and Day, the shipped defaults", page: "appearance", section: "themes" }, { label: "Video wallpaper", detail: "A looping video as the desktop background", page: "appearance", section: "background" }, { label: "Advanced accent", detail: "Adjust primary and secondary hue, saturation, and value", page: "appearance", section: "editor" }, - { label: "Pick colour from screen", detail: "Sample an accent colour with hyprpicker", page: "appearance" } + { label: "Pick color from screen", detail: "Sample an accent color with hyprpicker", page: "appearance" } ] function pageFor(group: string): string { diff --git a/config/dot/quickshell/services/SoundDefaults.qml b/config/dot/quickshell/services/SoundDefaults.qml index 7c80d63..94d4fff 100644 --- a/config/dot/quickshell/services/SoundDefaults.qml +++ b/config/dot/quickshell/services/SoundDefaults.qml @@ -112,7 +112,7 @@ Singleton { // node, and the node is gone -- so the stored name is all there is. These // names are structured, though, and the structure carries the useful part: // the Bluetooth address, the AirPlay speaker's hostname, the USB device's - // product string. Anything this does not recognise is tidied rather than + // product string. Anything this does not recognize is tidied rather than // guessed at, because a wrong name is worse than an ugly one. function label(name: string): string { const raw = String(name ?? "").trim(); diff --git a/docs/UPSTREAM-INSPIRATION.md b/docs/UPSTREAM-INSPIRATION.md index 4d2b700..fff1c28 100644 --- a/docs/UPSTREAM-INSPIRATION.md +++ b/docs/UPSTREAM-INSPIRATION.md @@ -73,7 +73,7 @@ reachable afterwards. Panama's reads the chords from the live keymap so it cannot teach keys the machine does not have. **HyDE** ([wallbash templates](https://github.com/HyDE-Project/HyDE)) and -omarchy's `default/themed/`: colour values as data consumed by templates rather +omarchy's `default/themed/`: color values as data consumed by templates rather than restated per consumer. Panama took the principle and not the template engine: `config/palette.json` is now the single source for everything outside QML, which removed six hand-synced copies of the eight accents. A per-app @@ -83,28 +83,31 @@ waiting beneficiary. **caelestia** ([caelestia-dots/shell](https://github.com/caelestia-dots/shell)): lightweight system toasts as a category distinct from the OSD and the -notification centre. Panama already had this as `StatusEvents`, so what was +notification center. Panama already had this as `StatusEvents`, so what was taken was the observation that a charger belongs in it; the layer itself needed no work. **DankMaterialShell** ([repo](https://github.com/AvengeMedia/DankMaterialShell)): surfacing agent usage in the bar, and a plugin registry with a lockfile. The -first is planned; the second remains deferred, see below. +first shipped, as a bar widget with a switch of its own on Shell › Bar; the +second remains deferred, see below. ### Declined, and why -- **Wallpaper-derived dynamic colour** (HyDE wallbash, matugen). The headline +- **Wallpaper-derived dynamic color** (HyDE wallbash, matugen). The headline feature of this whole genre, and declined on identity: Tokyo Night Moon is a - deliberate choice, and dynamic colour is how rices end up looking like each + deliberate choice, and dynamic color is how rices end up looking like each other. Reconsider as one more theme profile, never as the default. - **A per-app template engine for theming.** See above. -- **Audio visualisers.** A continuously repainting equaliser is exactly the +- **Audio visualizers.** A continuously repainting equalizer is exactly the GPU-pegging animation this project avoids on high-refresh displays. - **Theming applications by patching their internals** (Spotify, Discord). Breaks on every application update; the maintenance is unbounded and falls on whoever is holding the desktop that day. - **A plugin host.** Still premature, for the reason already recorded below. - User hooks are the thirty-line version and are the next thing to build. + User hooks were the thirty-line version and shipped instead: `panama-hook` + runs `~/.config/panama/hooks/` and its `.d/` directory for + `theme-set`, `post-upgrade` and `post-migrate`. - **Package channels and pacman hooks.** Arch-specific, and blocking `dnf upgrade` on a Fedora workstation would be hostile. diff --git a/tests/quickshell/disks-contract b/tests/quickshell/disks-contract index 5c47da1..e6bd79f 100755 --- a/tests/quickshell/disks-contract +++ b/tests/quickshell/disks-contract @@ -50,7 +50,7 @@ grep -qE 'function measureBreakdown|function measureCleanables' "$service" \ # Both walks are asked for, never done on open: they cost the same as the # folder scan the page already refuses to start by itself. -grep -qE 'Component\.onCompleted:.*Disks\.(scan|measureBreakdown|measureCleanables)' "$page" \ +grep -qE 'Component\.onCompleted:.*Disks\.(scan|measureBreakdown|measureCleanables)\b' "$page" \ && fail 'the page starts an expensive walk the moment it opens' # The expensive read must not run on open; that is the entire reason it is a diff --git a/tests/quickshell/notification-app-rules-contract b/tests/quickshell/notification-app-rules-contract index 5e80c28..e1fc4ca 100755 --- a/tests/quickshell/notification-app-rules-contract +++ b/tests/quickshell/notification-app-rules-contract @@ -347,10 +347,10 @@ for (const [required, why] of [ } // Wording the spec pins, because it is the only place the consequence of // forgetting is explained. -if (!familyText.includes("Forget this app")) - fail("the expanded row has no Forget this app action"); +if (!familyText.includes("Forget this application")) + fail("the expanded row has no Forget this application action"); if (!/returns on its next notification/.test(familyText)) - fail("Forget this app does not say the rule comes back when the application notifies again"); + fail("Forget this application does not say the rule comes back when the application notifies again"); // The Quiet card owns the breakthrough switch, and it belongs on the page the // notifications schema group routes to. diff --git a/tests/quickshell/settings-search-contract b/tests/quickshell/settings-search-contract index 4fc0b4a..3145201 100755 --- a/tests/quickshell/settings-search-contract +++ b/tests/quickshell/settings-search-contract @@ -110,7 +110,7 @@ hidden network|Hidden network|connectivity static ip|Static IP address|connectivity color filter|Color filter|accessibility custom shortcut|Custom shortcut|shortcuts -app rules|App rules|tiling +application window rules|Application window rules|tiling window rules|Window rules|tiling do not disturb|Do Not Disturb|notifications four-finger swipe|Four-finger swipe|mouse diff --git a/tests/quickshell/sound-page-contract b/tests/quickshell/sound-page-contract index dd760f2..d6e5aba 100755 --- a/tests/quickshell/sound-page-contract +++ b/tests/quickshell/sound-page-contract @@ -32,7 +32,7 @@ device_row="$settings/SoundDeviceRow.qml" application_mixer="$settings/ApplicationMixer.qml" application_row="$settings/ApplicationVolumeRow.qml" balance="$settings/AudioBalance.qml" -badge="$settings/SoundBadge.qml" +badge="$settings/StatusBadge.qml" capture_row="$settings/SoundCaptureRow.qml" channel_strip="$settings/SoundChannelStrip.qml" theme_row="$settings/SoundThemeRow.qml"