Files
Panama/config/dot/quickshell/modules/settings/UpdatesPage.qml
T
Gabriel Brown a15f019c17 Offer automatic package downloads, now that the machinery is installed
The page had a row for the case where dnf-automatic is absent and
nothing for the case where it is present, so installing it made the
setting disappear rather than appear.

The switch enables downloading only, which is the shipped default and
the right one to keep: a machine that installs packages unattended can
reboot into a kernel nobody chose. Downloading ahead of time is what
makes the install quick when someone does choose it, and the row says
exactly that rather than implying updates apply themselves.

Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
2026-08-19 18:24:30 -04:00

270 lines
10 KiB
QML

// Software updates.
//
// Three sources that fail independently -- packages, applications, firmware --
// so each is counted and applied on its own. Blending them into one number
// would hide the case that matters: a flatpak mirror being down says nothing
// about whether a security fix is waiting.
//
// Applying packages takes a snapshot first, named after what is about to
// happen, so the Snapshots page shows "before 32 package updates" rather than a
// timestamp. That is the thing neither macOS nor Windows does cleanly, and it
// is nearly free here.
import Quickshell
import QtQuick
import qs.config
import qs.services
SettingsPage {
id: root
objectName: "updates"
title: "Software Update"
lede: "Packages, applications, and firmware, each from the place it actually comes from."
property string expandedSource: ""
Component.onCompleted: {
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)
Updates.check();
}
TextRow {
visible: Updates.lastError !== ""
label: "Updates need attention"
detail: Updates.lastError
value: ""
divider: false
}
TextRow {
visible: Updates.lastApplied !== null
label: "Updated"
detail: Updates.lastApplied
? Updates.sourceLabel(String(Updates.lastApplied.source ?? ""))
+ (String(Updates.lastApplied.restorePoint ?? "") !== ""
? " · a snapshot was taken first, number "
+ String(Updates.lastApplied.restorePoint)
: "")
: ""
value: ""
divider: false
}
// ── The headline ─────────────────────────────────────────────────────────
SettingsCard {
Column {
width: parent.width
spacing: 10
Row {
width: parent.width
spacing: 12
Text {
text: Updates.checking ? "Checking…" : Updates.summary()
color: Theme.fg
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeTitle
font.weight: Font.DemiBold
}
Text {
anchors.verticalCenter: parent.verticalCenter
visible: Updates.securityCount > 0
text: Updates.securityCount + " carry a security advisory"
color: Theme.warn
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSize
}
}
Text {
text: Updates.lastCheckedText()
color: Theme.fgMuted
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
}
}
ActionRow {
label: "Check for updates"
detail: "Refreshes package metadata, application remotes, and firmware. Takes a few seconds."
action: Updates.checking ? "Checking…" : "Check now"
enabled: !Updates.busy
divider: Updates.rebootNeeded
onTriggered: Updates.check()
}
// The honest version of "restart required": the running kernel is not
// the newest installed one, so a reboot would change which kernel runs.
TextRow {
visible: Updates.rebootNeeded
label: "Restart to finish"
detail: "A newer kernel is installed than the one running. "
+ String(Updates.kernel?.running ?? "") + " → "
+ String(Updates.kernel?.newestInstalled ?? "")
value: "Restart needed"
divider: false
}
}
// ── One card per source ──────────────────────────────────────────────────
SettingsCard {
title: "System packages"
subtitle: Updates.dnf?.available === false
? "dnf is not available on this machine."
: (Number(Updates.dnf?.count ?? 0) === 0
? "Nothing waiting."
: Updates.dnf.count + " package"
+ (Updates.dnf.count === 1 ? "" : "s") + " ready to install"
+ (Updates.securityCount > 0
? ", " + Updates.securityCount + " carrying an advisory" : ""))
ActionRow {
visible: Number(Updates.dnf?.count ?? 0) > 0
label: "Install package updates"
detail: "Asks for your password, and takes a snapshot first so this can be undone"
action: Updates.applying ? "Working…" : "Install"
enabled: !Updates.busy
onTriggered: Updates.apply("dnf")
}
ActionRow {
visible: Number(Updates.dnf?.count ?? 0) > 0
label: "What would change"
detail: root.expandedSource === "dnf"
? "Every package that would be replaced"
: Updates.dnf.count + " packages"
action: root.expandedSource === "dnf" ? "Hide" : "Show"
divider: root.expandedSource === "dnf"
onTriggered: root.expandedSource = root.expandedSource === "dnf" ? "" : "dnf"
}
Repeater {
model: root.expandedSource === "dnf" ? (Updates.dnf?.packages ?? []) : []
delegate: TextRow {
required property var modelData
required property int index
width: parent.width
label: String(modelData.name ?? "")
detail: String(modelData.repository ?? "")
value: String(modelData.version ?? "")
divider: index < (Updates.dnf?.packages ?? []).length - 1
}
}
TextRow {
visible: Number(Updates.dnf?.count ?? 0) === 0 && Updates.everChecked
label: "Packages are current"
detail: "Nothing from the system repositories is waiting"
value: ""
divider: false
}
}
SettingsCard {
title: "Applications"
subtitle: Updates.flatpak?.available === false
? "Flatpak is not installed."
: (Number(Updates.flatpak?.count ?? 0) === 0
? "Nothing waiting."
: Updates.flatpak.count + " application"
+ (Updates.flatpak.count === 1 ? "" : "s") + " ready to update")
Repeater {
model: Updates.flatpak?.applications ?? []
delegate: TextRow {
required property var modelData
required property int index
width: parent.width
label: String(modelData.id ?? "")
detail: "Flatpak"
value: String(modelData.version ?? "")
divider: true
}
}
ActionRow {
visible: Number(Updates.flatpak?.count ?? 0) > 0
label: "Update applications"
detail: "Needs no password: these are installed for your account"
action: Updates.applying ? "Working…" : "Update"
enabled: !Updates.busy
onTriggered: Updates.apply("flatpak")
}
SwitchRow {
label: "Update applications automatically"
detail: "Once a day, in the background. Applications are not a security boundary the way packages are, so this is safe to leave on; packages still ask."
checked: Updates.automatic?.flatpakEnabled === true
enabled: !Updates.busy && Updates.automatic?.flatpakAvailable === true
divider: false
onToggled: value => Updates.setAutomaticFlatpak(value)
}
}
SettingsCard {
title: "Firmware"
subtitle: Updates.firmware?.available === false
? "Firmware updating is not available on this machine."
: (Number(Updates.firmware?.count ?? 0) === 0
? "No firmware updates are offered for this hardware."
: Updates.firmware.count + " device"
+ (Updates.firmware.count === 1 ? "" : "s") + " have firmware available")
Repeater {
model: Updates.firmware?.devices ?? []
delegate: TextRow {
required property var modelData
required property int index
width: parent.width
label: String(modelData.name ?? "")
detail: String(modelData.version ?? "") + " → " + String(modelData.target ?? "")
+ (modelData.needsReboot ? " · installs on restart" : "")
value: ""
divider: true
}
}
ActionRow {
visible: Number(Updates.firmware?.count ?? 0) > 0
label: "Install firmware"
detail: "Some devices only finish updating after a restart"
action: Updates.applying ? "Working…" : "Install"
enabled: !Updates.busy
divider: false
onTriggered: Updates.apply("firmware")
}
// Offered only when the machinery exists. When it does not, this says
// so rather than showing a switch that could not do anything --
// installing software is not a settings action.
SwitchRow {
visible: Updates.automatic?.dnfAutomaticAvailable === true
label: "Download package updates automatically"
detail: "Fetches them in the background each morning so installing is quick. It does not install them: a machine that updates packages unattended can reboot into a kernel nobody chose."
checked: Updates.automatic?.dnfAutomaticEnabled === true
enabled: !Updates.busy
divider: false
onToggled: value => Updates.setAutomaticDnf(value)
}
TextRow {
visible: Updates.automatic?.dnfAutomaticAvailable === false
label: "Automatic package updates"
detail: "Not set up. dnf-automatic is not installed, and Settings does not install software."
value: "Off"
divider: false
}
}
}