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:
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user