quine-linux/patches/kickoff/0001-update-bar.patch
Philip Peterson a6b5a836b7
All checks were successful
Image, disk image and live ISO / iso (push) Successful in 32m17s
Kickoff fork: an update bar in the start menu
When a newer image is on the forge, Kickoff shows "A system update is
available" with Update Now (Aurora's System Update); once one is
downloaded, "Restart to finish updating" with Restart. Hidden otherwise.

- patches/kickoff/: Kickoff's QML plus UpdateBar.qml as its footer. The
  kickoff-builder stage rebuilds just that plugin from the image's own
  plasma-desktop version (KDE's source, via the forge's
  mirror-kde-plasma-desktop) and the final stage swaps it in.
- quine-update-check (root; bootc needs it): bootc upgrade --check, then
  writes /run/quine-linux/update-status.json for the bar. At boot, every
  4 hours, and after uupd's nightly update.

Tested in a VM from a disk image of this tree: up to date (hidden),
available and staged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:34:41 -09:00

132 lines
4.4 KiB
Diff

diff --git a/kickoff/CMakeLists.txt b/kickoff/CMakeLists.txt
index 70e361c..d860cb4 100644
--- a/kickoff/CMakeLists.txt
+++ b/kickoff/CMakeLists.txt
@@ -26,6 +26,7 @@ QML_SOURCES
ListOfGridsView.qml
ListOfGridsViewDelegate.qml
NormalPage.qml
+ UpdateBar.qml
PlacesPage.qml
SectionView.qml
VerticalStackView.qml
diff --git a/kickoff/FullRepresentation.qml b/kickoff/FullRepresentation.qml
index aa25d3c..7d9a6bb 100644
--- a/kickoff/FullRepresentation.qml
+++ b/kickoff/FullRepresentation.qml
@@ -77,6 +77,9 @@ EmptyPage {
}
}
+ // Quine Linux: system update status (UpdateBar.qml).
+ footer: UpdateBar {}
+
contentItem: VerticalStackView {
id: contentItemStackView
focus: true
diff --git a/kickoff/UpdateBar.qml b/kickoff/UpdateBar.qml
new file mode 100644
index 0000000..3ebe376
--- /dev/null
+++ b/kickoff/UpdateBar.qml
@@ -0,0 +1,100 @@
+/*
+ SPDX-FileCopyrightText: 2026 Quine Foundation
+ SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+// Quine Linux: a bar at the bottom of Kickoff when a newer system image is
+// available, or downloaded and waiting for a restart. It reads the status
+// that quine-update-check.service writes as root (quine-linux's overlay,
+// /usr/libexec/quine-linux/update-check), and is hidden otherwise.
+
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import org.kde.kirigami as Kirigami
+import org.kde.plasma.plasma5support as P5Support
+import org.kde.plasma.private.sessions as Sessions
+
+Kirigami.InlineMessage {
+ id: root
+
+ readonly property string statusFile: "/run/quine-linux/update-status.json"
+ readonly property string readCommand: "cat " + statusFile
+ // "current", "available" or "staged"; anything else hides the bar.
+ property string updateState: ""
+ property string newBuild: ""
+
+ position: Kirigami.InlineMessage.Position.Footer
+ // Hidden, InlineMessage's implicit height is 0, so the menu doesn't keep
+ // room for it (EmptyPage sizes by the footer's implicit height).
+ visible: updateState === "available" || updateState === "staged"
+ showCloseButton: false
+ type: updateState === "staged" ? Kirigami.MessageType.Positive : Kirigami.MessageType.Information
+ icon.name: updateState === "staged" ? "system-reboot-update" : "system-software-update"
+ text: updateState === "staged"
+ ? i18nc("@info", "An update is ready. Restart to finish updating.")
+ : i18nc("@info", "A system update is available%1.",
+ newBuild ? i18nc("@info build date", " (built %1)", newBuild) : "")
+
+ actions: [
+ Kirigami.Action {
+ visible: root.updateState === "available"
+ text: i18nc("@action:button", "Update Now")
+ icon.name: "system-software-update"
+ // Aurora's System Update: `ujust update` in a terminal.
+ onTriggered: {
+ executable.connectSource("kioclient exec /usr/share/applications/dev.getaurora.system-update.desktop")
+ kickoff.expanded = false
+ }
+ },
+ Kirigami.Action {
+ visible: root.updateState === "staged" && sessions.canReboot
+ text: i18nc("@action:button", "Restart")
+ icon.name: "system-reboot"
+ onTriggered: {
+ kickoff.expanded = false
+ sessions.requestReboot()
+ }
+ }
+ ]
+
+ Sessions.SessionManagement {
+ id: sessions
+ }
+
+ P5Support.DataSource {
+ id: executable
+ engine: "executable"
+ connectedSources: []
+ onNewData: (source, data) => {
+ disconnectSource(source)
+ if (source !== root.readCommand) {
+ return
+ }
+ let status = null
+ try {
+ status = JSON.parse(data["stdout"])
+ } catch (e) {
+ // No file yet (the first check hasn't run, or a live session).
+ }
+ root.updateState = status?.state ?? ""
+ const built = status?.new?.timestamp
+ root.newBuild = built ? Qt.formatDate(new Date(built), Qt.locale(), Locale.ShortFormat) : ""
+ }
+ }
+
+ function refresh() {
+ executable.connectSource(readCommand)
+ }
+
+ Component.onCompleted: refresh()
+
+ Connections {
+ target: kickoff
+ function onExpandedChanged() {
+ if (kickoff.expanded) {
+ root.refresh()
+ }
+ }
+ }
+}