Files
Panama/config/dot/quickshell/modules/settings/HomePage.qml
T
Gabriel Brown 44124d72fa Take one machine's fingerprints off everyone's desktop
The audit's second tier: values that were measurements of the author's
desktop, shipped to every machine as if they were defaults.

Settings greeted every human as Gabriel; it now greets whoever
accountsservice says is signed in, and nobody when it says nothing. The
weather shipped his home coordinates and confidently reported his forecast
anywhere on earth; it now ships unset, fetches nothing until a location is
chosen, and the location row says so. The GTK bookmarks carried seven
/home/gib paths and his file server into every file dialog; they are now
generated per machine from a template and gitignored -- Nautilus edits the
instance freely, the way settings.ini already worked one file over. Web
search routed through his personal bang redirector; the engine is now the
webSearchUrl preference with a DuckDuckGo default, read by both the script
command and the suggestions extension, which the launcher-search contract
already pins to one another. The GPU vitals path defaulted to his card1 and
lost the readout on any machine enumerated differently; a machine with
exactly one GPU now adopts it. And the Containers and Snapshots pages hide
once a scan proves their backing stack absent, instead of rendering
permanently empty on machines that never had podman or snapper.

Lesser residue swept in the same pass: the DP-2 hyprpaper block one machine
needed, the author's username-typo expansions (moved to his personal seed in
user/, where personal content belongs), a capture fallback into /home/gib,
and a parity table asserting one machine's hardware as fact.

Claude-Session: https://claude.ai/code/session_01Epx9ZC1gwm81K3jm9x9CKh
2026-08-23 11:55:43 -04:00

300 lines
12 KiB
QML

// Home — the page you open to do something, not to read a report.
//
// It used to lead with a diagram of the attached monitor: DP-2, 4500 x 3000,
// 1.13x scale, XRGB2101010. That is Displays-page data, it was the largest
// thing on the screen, and nobody has ever needed it here. Below it sat a
// permanently open search field for a weather location that is set about once a
// year, and then roughly half a page of nothing.
//
// What Panama already knows is the useful material: whether anything needs
// attention, what is running, and the two or three things worth doing next. A
// finding is shown when there is one and the page is quiet when there is not,
// which is the same shape the Firewall and Containers pages use.
//
// Weather stays. It is the one genuinely ambient thing here, and it belongs
// next to a greeting rather than in a card of its own with a search box open.
import QtQuick
import qs.config
import qs.services
SettingsPage {
id: root
readonly property int openedHour: new Date().getHours()
readonly property string greeting: openedHour < 12
? "Good morning"
: (openedHour < 18 ? "Good afternoon" : "Good evening")
// Findings, in the order they deserve attention. Each names the page that
// can actually resolve it, because a home page that reports a problem it
// cannot help with is just an alarm.
readonly property var findings: {
const found = [];
const exposed = Firewall.exposedDataStores ?? [];
if (exposed.length > 0) {
found.push({
label: exposed.length === 1
? "A database is reachable from your network"
: "Databases are reachable from your network",
detail: exposed.map(entry => String(entry.name)).join(" and ")
+ ", published on every interface",
page: "firewall",
action: "Review"
});
}
if (Updates.securityCount > 0) {
found.push({
label: Updates.securityCount + " update"
+ (Updates.securityCount === 1 ? "" : "s") + " carry a security advisory",
detail: "Worth installing before the rest",
page: "updates",
action: "Open"
});
}
if (Updates.rebootNeeded) {
found.push({
label: "A newer kernel is installed than the one running",
detail: "Restart to use it",
page: "updates",
action: "Open"
});
}
if (Health.summary?.errors > 0 || Health.summary?.warnings > 0) {
const count = Number(Health.summary?.errors ?? 0) + Number(Health.summary?.warnings ?? 0);
found.push({
label: count + " health check" + (count === 1 ? "" : "s") + " need attention",
detail: "Something the desktop owns is not in its expected state",
page: "services",
action: "Open"
});
}
return found;
}
// One line of everything that is fine, so the quiet case still says
// something rather than showing an empty page.
readonly property string reassurance: {
const parts = [];
if (Health.checks.length > 0)
parts.push(Health.checks.length + " health checks pass");
if (Disks.rootFilesystem)
parts.push(Disks.formatBytes(Number(Disks.rootFilesystem.available ?? 0)) + " free");
const newest = Snapshots.configs.length > 0
? (Snapshots.configs[0].snapshots ?? [])[0]
: null;
if (newest)
parts.push("snapshots ran at " + root.clockOf(String(newest.date ?? "")));
return parts.join(" · ");
}
// snapper prints a full date; only the time is wanted in a summary line.
function clockOf(stamp: string): string {
const match = /(\d{1,2}:\d{2})(?::\d{2})?\s*(AM|PM)?/i.exec(stamp);
if (!match)
return stamp;
return match[2] ? match[1] + " " + match[2].toUpperCase() : match[1];
}
// Greets whoever is signed in, from the same accountsservice record the
// lock screen shows. This once hardcoded the author's first name, which
// made the first screen of Settings wrong for every other human.
readonly property string greetingName: {
if (!UserAccounts.me)
return "";
const full = UserAccounts.displayName(UserAccounts.me);
return full.split(" ")[0] || "";
}
title: root.greetingName !== "" ? `${root.greeting}, ${root.greetingName}` : root.greeting
lede: Weather.available
? Math.round(Weather.temperature) + Weather.unitSuffix + " and "
+ Weather.description.toLowerCase() + " in " + Settings.weatherLocation
: "Your desktop is configured and ready."
Component.onCompleted: {
if (!Firewall.scanned)
Firewall.refresh();
if (!Containers.scanned)
Containers.refresh();
if (!Snapshots.scanned)
Snapshots.refresh();
if (!Disks.scanned)
Disks.refresh();
}
// ── What you came here to do ────────────────────────────────────────────
Grid {
id: doing
width: parent.width
columns: width >= 720 ? 2 : 1
columnSpacing: 16
rowSpacing: 16
SettingsCard {
width: doing.columns === 2
? (doing.width - doing.columnSpacing) / 2
: doing.width
title: "Focus"
subtitle: FocusSession.active
? "A session is running · " + FocusSession.remainingText + " left"
: (FocusModes.active
? FocusModes.activeName + " is on because " + FocusModes.activeReason
: (Notifs.doNotDisturb
? "Do Not Disturb is on"
: "Nothing is quieting this machine"))
SettingRow {
label: FocusSession.active ? "End the session" : "Start a focus session"
detail: FocusSession.active
? "Puts Do Not Disturb and Caffeine back as they were"
: Settings.focusDurationMinutes + " minutes · Super+Shift+F"
controlWidth: 120
SettingsButton {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: FocusSession.active ? "End" : "Start"
tone: FocusSession.active ? "normal" : "accent"
onClicked: FocusSession.active ? FocusSession.end(false) : FocusSession.startDefault()
}
}
SettingRow {
label: "Do Not Disturb"
detail: FocusModes.active
? "Held on by the " + FocusModes.activeName + " mode"
: "Banners are held; the notification centre still fills"
divider: false
controlWidth: 48
SettingsToggle {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
enabled: !FocusModes.active && !FocusSession.active
checked: Notifs.doNotDisturb
onToggled: value => Notifs.doNotDisturb = value
}
}
}
SettingsCard {
width: doing.columns === 2
? (doing.width - doing.columnSpacing) / 2
: doing.width
title: "Right now"
subtitle: Containers.running > 0
? Containers.running + " of " + Containers.total + " containers running"
: "No containers are running"
TextRow {
label: Containers.running > 0
? Containers.projects.filter(project => project.running > 0)
.map(project => String(project.title)).join(", ")
: "Nothing to report"
detail: Containers.running > 0
? "Started for development, still up"
: "Start a stack from the Containers page"
value: ""
}
ActionRow {
label: "Containers"
detail: "Projects, what they expose, and what they cost"
action: "Open"
divider: false
onTriggered: ShellState.settingsPage = "containers"
}
}
}
// ── This machine ────────────────────────────────────────────────────────
SettingsCard {
title: "This machine"
subtitle: root.reassurance
TextRow {
visible: root.findings.length === 0
label: "Nothing needs your attention"
detail: "Anything worth acting on would appear here."
value: ""
divider: false
}
Repeater {
model: root.findings
delegate: SettingRow {
required property var modelData
required property int index
width: parent.width
label: String(modelData.label ?? "")
detail: String(modelData.detail ?? "")
controlWidth: 110
divider: index < root.findings.length - 1
SettingsButton {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
text: String(modelData.action ?? "Open")
onClicked: ShellState.settingsPage = String(modelData.page)
}
}
}
}
// ── Worth doing, when there is something ────────────────────────────────
SettingsCard {
visible: Updates.total > 0 || Containers.reclaimable > 0
title: "Do next"
subtitle: "Small things this machine is waiting on."
ActionRow {
visible: Updates.total > 0
label: "Install " + Updates.total + " update" + (Updates.total === 1 ? "" : "s")
detail: "Packages, applications and firmware, from wherever each comes from"
action: "Open"
divider: Containers.reclaimable > 0
onTriggered: ShellState.settingsPage = "updates"
}
ActionRow {
visible: Containers.reclaimable > 0
label: "Reclaim " + Containers.formatBytes(Containers.reclaimable)
detail: "Container images and volumes nothing references"
action: "Review"
divider: false
onTriggered: ShellState.settingsPage = "containers"
}
}
// ── Weather ─────────────────────────────────────────────────────────────
SettingsCard {
title: "Weather"
subtitle: "Shown above and in the date menu."
// The location is set roughly once a year, so the search that changes it
// is behind a press rather than permanently open.
PickerRow {
id: locationPicker
label: "Location"
detail: "Only the search term is sent; the name is a label kept on this machine"
value: Settings.weatherLocation !== "" ? Settings.weatherLocation : "Not set — choose one to see weather"
LocationPicker {
width: parent.width
onPicked: locationPicker.collapse()
}
}
ChoiceRow { setting: "temperatureUnit" }
SliderRow { setting: "weatherRefreshMinutes"; divider: false }
}
}