Coherence: one component per idea, one spelling per word, one truth per claim
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,12 +507,9 @@ SettingsPage {
|
||||
divider: Dictation.lastError !== ""
|
||||
}
|
||||
|
||||
TextRow {
|
||||
visible: Dictation.lastError !== ""
|
||||
ErrorRow {
|
||||
label: "Problem"
|
||||
detail: Dictation.lastError
|
||||
value: ""
|
||||
divider: false
|
||||
message: Dictation.lastError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?? "");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -96,11 +96,9 @@ SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
visible: Projects.lastError !== ""
|
||||
ErrorRow {
|
||||
label: "Problem"
|
||||
detail: Projects.lastError
|
||||
divider: false
|
||||
message: Projects.lastError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user