Finish the wonderland: System told truthfully, in eight tabs instead of ten
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -1,13 +1,30 @@
|
||||
import QtQuick
|
||||
// About.
|
||||
//
|
||||
// The landing page of the System category, and the answer to "what am I
|
||||
// running". Every line here is read from the machine rather than written down:
|
||||
// the versions block used to carry a hardcoded Quickshell version that had been
|
||||
// wrong for two releases, and a Design principles card restating opinions the
|
||||
// manual argues properly.
|
||||
//
|
||||
// Rows come from MachineInfo, except graphics, which is joined from
|
||||
// GraphicsDevices rather than read a second time -- two readouts of the same
|
||||
// hardware are two things that can disagree. Facts the cards below do not claim
|
||||
// by name are still shown, in Software, so a row the helper learns to report
|
||||
// cannot go missing here.
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
SettingsPage {
|
||||
id: root
|
||||
|
||||
objectName: "about"
|
||||
|
||||
title: "About"
|
||||
lede: "A curated Hyprland desktop built around focus, speed, and good taste."
|
||||
lede: "This machine, plainly."
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!MachineInfo.scanned)
|
||||
@@ -16,22 +33,298 @@ SettingsPage {
|
||||
GraphicsDevices.refresh();
|
||||
}
|
||||
|
||||
// ── Reading the machine ──────────────────────────────────────────────────
|
||||
|
||||
function fact(label: string): string {
|
||||
const row = (MachineInfo.facts ?? []).find(entry => entry.label === label);
|
||||
return row ? String(row.value ?? "") : "";
|
||||
}
|
||||
|
||||
// Joined with a middle dot, skipping whatever is absent. Firmware and
|
||||
// Secure Boot are both absent-tolerant on the helper's side, so either half
|
||||
// of that row can be missing on a given machine.
|
||||
function joined(parts: var): string {
|
||||
return parts.filter(part => String(part ?? "") !== "").join(" · ");
|
||||
}
|
||||
|
||||
readonly property string hostname: root.fact("Hostname")
|
||||
// "Fedora Linux 44 (Workstation Edition)" says the edition twice for the
|
||||
// one line where brevity matters most.
|
||||
readonly property string operatingSystem:
|
||||
root.fact("Operating system").replace(/\s*\(.*\)\s*$/, "")
|
||||
readonly property string uptime: root.fact("Uptime").split(",")[0].trim()
|
||||
|
||||
readonly property string identity: root.joined([
|
||||
root.operatingSystem === "" ? "" : "Panama on " + root.operatingSystem,
|
||||
SystemSettings.hyprlandVersion === "" ? "" : "Hyprland " + SystemSettings.hyprlandVersion,
|
||||
root.uptime === "" ? "" : "up " + root.uptime
|
||||
])
|
||||
|
||||
readonly property var graphicsRows: {
|
||||
const gpus = GraphicsDevices.devices ?? [];
|
||||
return gpus.map((device, index) => ({
|
||||
label: gpus.length > 1 ? "Graphics " + (index + 1) : "Graphics",
|
||||
value: String(device.name ?? "")
|
||||
}));
|
||||
}
|
||||
|
||||
readonly property string displayValue: root.joined([
|
||||
SystemSettings.monitorName, root.fact("Resolution")
|
||||
])
|
||||
|
||||
// Everything the three cards below name explicitly. Whatever the helper
|
||||
// reports that is not in here lands in Software rather than nowhere.
|
||||
readonly property var claimedFacts: [
|
||||
"Hostname", "Operating system", "Uptime", "Panama", "Kernel",
|
||||
"Firmware", "Secure Boot", "Model", "Processor", "Memory", "Swap",
|
||||
"Disk", "Resolution"
|
||||
]
|
||||
|
||||
readonly property var otherFacts: (MachineInfo.facts ?? [])
|
||||
.filter(entry => root.claimedFacts.indexOf(String(entry.label ?? "")) < 0)
|
||||
|
||||
// ── Copying it ───────────────────────────────────────────────────────────
|
||||
|
||||
// The same facts, as plain text, for a bug report or a message to somebody
|
||||
// trying to help. Built from what is on screen so the two cannot disagree.
|
||||
function systemInfoText(): string {
|
||||
const lines = [];
|
||||
if (root.hostname !== "")
|
||||
lines.push(root.hostname);
|
||||
if (root.identity !== "")
|
||||
lines.push(root.identity);
|
||||
lines.push("");
|
||||
for (const row of root.versionRows.concat(root.hardwareRows, root.otherFacts)) {
|
||||
if (String(row.value ?? "") !== "")
|
||||
lines.push(row.label + ": " + row.value);
|
||||
}
|
||||
return lines.join("\n") + "\n";
|
||||
}
|
||||
|
||||
property string copyResult: ""
|
||||
|
||||
Process {
|
||||
id: copyRun
|
||||
|
||||
property string payload: ""
|
||||
|
||||
command: ["wl-copy"]
|
||||
stdinEnabled: true
|
||||
onStarted: {
|
||||
copyRun.write(copyRun.payload);
|
||||
// wl-copy reads stdin until EOF before it exits; leaving the
|
||||
// channel open would hang it forever waiting for more. Same
|
||||
// stdinEnabled close that Health.copyReport uses.
|
||||
copyRun.stdinEnabled = false;
|
||||
}
|
||||
onExited: exitCode => {
|
||||
root.copyResult = exitCode === 0
|
||||
? "Copied."
|
||||
: "Could not reach the clipboard.";
|
||||
copyRun.payload = "";
|
||||
}
|
||||
}
|
||||
|
||||
function copySystemInfo(): void {
|
||||
if (copyRun.running)
|
||||
return;
|
||||
root.copyResult = "";
|
||||
copyRun.payload = root.systemInfoText();
|
||||
copyRun.stdinEnabled = true;
|
||||
copyRun.running = true;
|
||||
}
|
||||
|
||||
// ── The hero ─────────────────────────────────────────────────────────────
|
||||
|
||||
readonly property var versionRows: [
|
||||
{ label: "Panama", value: root.fact("Panama") },
|
||||
{ label: "Quickshell", value: SystemSettings.quickshellVersion },
|
||||
{ label: "Kernel", value: root.fact("Kernel") },
|
||||
{ label: "Firmware", value: root.joined([
|
||||
root.fact("Firmware"),
|
||||
root.fact("Secure Boot") === "" ? "" : "Secure Boot " + root.fact("Secure Boot")
|
||||
]) }
|
||||
].filter(row => String(row.value ?? "") !== "")
|
||||
|
||||
SettingsCard {
|
||||
title: "Desktop"
|
||||
subtitle: "Tokyo Night Moon · Prism glass · native tiling"
|
||||
Item {
|
||||
width: parent.width
|
||||
implicitHeight: Math.max(96, heroCopy.implicitHeight + 16)
|
||||
|
||||
// The house tile: a rounded plate carrying the System glyph the
|
||||
// sidebar uses, in the accent, drawn rather than shipped as an
|
||||
// image so it follows the theme like everything else.
|
||||
Rectangle {
|
||||
id: heroArt
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 84
|
||||
height: 84
|
||||
radius: 24
|
||||
color: Theme.alpha(Theme.accent, 0.13)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accent, 0.3)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: "transparent"
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Vertical
|
||||
GradientStop { position: 0; color: Theme.alpha(Theme.accentSecondary, 0.14) }
|
||||
GradientStop { position: 1; color: Theme.alpha(Theme.accent, 0.02) }
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\u{F02FD}"
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 38
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: heroCopy
|
||||
|
||||
anchors.left: heroArt.right
|
||||
anchors.leftMargin: 20
|
||||
anchors.right: copyButton.left
|
||||
anchors.rightMargin: 18
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.hostname === "" ? "This machine" : root.hostname
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeTitle
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.identity !== ""
|
||||
text: root.identity
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.copyResult !== ""
|
||||
text: root.copyResult
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
id: copyButton
|
||||
|
||||
objectName: "about-copy-button"
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Copy system info"
|
||||
enabled: !copyRun.running
|
||||
activeFocusOnTab: enabled
|
||||
border.width: activeFocus ? 2 : 1
|
||||
border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08)
|
||||
onClicked: root.copySystemInfo()
|
||||
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: "Copy system info"
|
||||
Keys.onReturnPressed: root.copySystemInfo()
|
||||
Keys.onSpacePressed: root.copySystemInfo()
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.versionRows
|
||||
|
||||
TextRow {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
label: modelData.label
|
||||
value: modelData.value
|
||||
divider: index < root.versionRows.length - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hardware ─────────────────────────────────────────────────────────────
|
||||
|
||||
readonly property var hardwareRows: [
|
||||
{ label: "Model", value: root.fact("Model") },
|
||||
{ label: "Processor", value: root.fact("Processor") }
|
||||
].concat(root.graphicsRows, [
|
||||
{ label: "Memory", value: root.joined([
|
||||
root.fact("Memory"),
|
||||
root.fact("Swap") === "" ? "" : root.fact("Swap") + " swap"
|
||||
]) },
|
||||
{ label: "Disk", value: root.fact("Disk") },
|
||||
{ label: "Display", value: root.displayValue }
|
||||
]).filter(row => String(row.value ?? "") !== "")
|
||||
|
||||
SettingsCard {
|
||||
title: "Hardware"
|
||||
|
||||
Repeater {
|
||||
model: root.hardwareRows
|
||||
|
||||
TextRow {
|
||||
required property var modelData
|
||||
|
||||
label: modelData.label
|
||||
value: modelData.value
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
label: "Hyprland"
|
||||
value: SystemSettings.hyprlandVersion || "Detecting…"
|
||||
visible: MachineInfo.scanned && root.hardwareRows.length === 0
|
||||
label: "Hardware"
|
||||
detail: "The system did not report anything readable"
|
||||
value: "Unavailable"
|
||||
}
|
||||
TextRow {
|
||||
label: "Quickshell"
|
||||
value: SystemSettings.quickshellVersion
|
||||
|
||||
// The machine's name is a network-facing setting rather than a fact
|
||||
// about the hardware, and Sharing is the page that owns it. Saying so
|
||||
// here is cheaper than a second field that writes the same hostname.
|
||||
ActionRow {
|
||||
label: "Device name"
|
||||
detail: root.hostname === ""
|
||||
? "What this machine calls itself on the network"
|
||||
: root.hostname + " — what this machine calls itself on the network"
|
||||
action: "Open Sharing"
|
||||
divider: false
|
||||
onTriggered: ShellState.openSettings("sharing")
|
||||
}
|
||||
TextRow {
|
||||
label: "Display"
|
||||
value: SystemSettings.monitorName || "Detecting…"
|
||||
}
|
||||
|
||||
// ── Software ─────────────────────────────────────────────────────────────
|
||||
|
||||
SettingsCard {
|
||||
title: "Software"
|
||||
|
||||
Repeater {
|
||||
model: root.otherFacts
|
||||
|
||||
TextRow {
|
||||
required property var modelData
|
||||
|
||||
label: String(modelData.label ?? "")
|
||||
value: String(modelData.value ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
label: "Configuration"
|
||||
detail: Quickshell.shellDir
|
||||
@@ -40,74 +333,34 @@ SettingsPage {
|
||||
}
|
||||
}
|
||||
|
||||
// What GNOME's About panel answers and this page did not: what am I running
|
||||
// on. Rows come from MachineInfo, except graphics, which is joined from
|
||||
// GraphicsDevices rather than read a second time -- two readouts of the same
|
||||
// hardware are two things that can disagree.
|
||||
//
|
||||
// The join splices the GPU rows in directly after Processor rather than
|
||||
// appending them, so the card reads the way fastfetch does: what the system
|
||||
// is, then what is installed on it, then the hardware underneath. A GPU
|
||||
// listed after "Disk" reads as an afterthought.
|
||||
readonly property var machineRows: {
|
||||
const rows = (MachineInfo.facts ?? []).slice();
|
||||
const gpus = GraphicsDevices.devices ?? [];
|
||||
if (gpus.length === 0)
|
||||
return rows;
|
||||
// ── Manual ───────────────────────────────────────────────────────────────
|
||||
|
||||
const graphics = gpus.map((device, index) => ({
|
||||
label: gpus.length > 1 ? "Graphics " + (index + 1) : "Graphics",
|
||||
value: device.name
|
||||
}));
|
||||
|
||||
const after = rows.findIndex(row => row.label === "Processor");
|
||||
if (after < 0)
|
||||
return rows.concat(graphics);
|
||||
return rows.slice(0, after + 1).concat(graphics, rows.slice(after + 1));
|
||||
}
|
||||
ManualChapters { id: contents }
|
||||
|
||||
SettingsCard {
|
||||
title: "This machine"
|
||||
subtitle: "Hardware and system, as the kernel reports it."
|
||||
title: "Manual"
|
||||
subtitle: "How this desktop works, written for the person using it. Links inside a chapter open the settings page they name."
|
||||
|
||||
Repeater {
|
||||
model: root.machineRows
|
||||
id: chapterRows
|
||||
model: contents.titled
|
||||
|
||||
TextRow {
|
||||
id: machineRow
|
||||
ActionRow {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
label: machineRow.modelData.label
|
||||
value: machineRow.modelData.value
|
||||
divider: machineRow.index < root.machineRows.length - 1
|
||||
label: modelData.title
|
||||
action: "Read"
|
||||
onTriggered: ShellState.openSettingsSection("manual", modelData.file)
|
||||
}
|
||||
}
|
||||
|
||||
TextRow {
|
||||
visible: MachineInfo.scanned && root.machineRows.length === 0
|
||||
label: "Hardware"
|
||||
detail: "The system did not report anything readable"
|
||||
value: "Unavailable"
|
||||
divider: false
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
title: "Design principles"
|
||||
|
||||
TextRow {
|
||||
label: "Curated by default"
|
||||
detail: "Strong choices instead of an incoherent matrix of switches"
|
||||
}
|
||||
TextRow {
|
||||
label: "Quiet while idle"
|
||||
detail: "No continuous decorative repaint loops"
|
||||
}
|
||||
TextRow {
|
||||
label: "Real system boundaries"
|
||||
detail: "Every control either works or clearly hands off to its owner"
|
||||
ActionRow {
|
||||
label: "Open the manual"
|
||||
detail: "Starts at the beginning"
|
||||
action: "Open"
|
||||
divider: false
|
||||
onTriggered: ShellState.openSettings("manual")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user