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() + } + } + } +}