diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-15 12:21:03 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-15 12:21:03 +0200 |
| commit | 44f23582c0fcee1c50dcd696d320bc7020237132 (patch) | |
| tree | 8d2bd49b423b218388e8d41069bac2fa9c073e8e /desktop/modules/updates/UpdatesModule.qml | |
| parent | 7db7405c80e8f00008bc5196abfda2c500071a98 (diff) | |
| download | quickshell-44f23582c0fcee1c50dcd696d320bc7020237132.tar.gz quickshell-44f23582c0fcee1c50dcd696d320bc7020237132.zip | |
feat(updates): the drawer tile and page
The tile counts what can be installed now (Slackware and the binary
repo) and what only needs a build, and goes accent when there is
something to install. It runs updatesctl while the drawer is open and
not otherwise; a result over 30 minutes old is followed by a refresh
that runs nvchecker, never after a refresh, so a failing nvchecker
cannot loop.
The page has one section per next action: Slackware upgrades, new and
removed first, then upgrade here, awaiting build, upstream newer, app
updates, the order the work is done in. Each row names the package, the
repo it comes from and the one version jump its action covers. A source
that could not be read says so under the section it empties instead of
reading as nothing pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/updates/UpdatesModule.qml')
| -rw-r--r-- | desktop/modules/updates/UpdatesModule.qml | 97 |
1 files changed, 97 insertions, 0 deletions
diff --git a/desktop/modules/updates/UpdatesModule.qml b/desktop/modules/updates/UpdatesModule.qml new file mode 100644 index 0000000..f7b1f39 --- /dev/null +++ b/desktop/modules/updates/UpdatesModule.qml @@ -0,0 +1,97 @@ +// Copyright (C) 2026 Danilo M. <danix@danix.xyz> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License version 2 as +// published by the Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import Quickshell +import Quickshell.Io +import QtQuick +import "../.." + +// Pending updates, sorted by updatesctl into the next action. Nothing runs +// while the drawer is closed: the tile's lifetime is the check's, as with +// KDE Connect. A result older than 30 minutes is followed by a refresh, +// which runs nvchecker. +Module { + id: mod + + name: "updates" + label: "Updates" + + // linux-slackware, present in Inconsolata Nerd Font. + icon: "\uf318" + + property var doc: null + property bool checking: false + + readonly property int toInstall: doc + ? doc.slackware.upgrade.length + doc.slackware.new.length + + doc.slackware.removed.length + doc.upgrade.length + : 0 + readonly property int toBuild: doc ? doc.build.length : 0 + + active: toInstall > 0 + + property bool polling: false + onPollingChanged: if (polling) run(false) + + function run(refresh) { + if (proc.running && mod.checking) return; // never cut nvchecker short + proc.command = ["/usr/bin/python3", + `${Quickshell.shellDir}/modules/updates/updatesctl`].concat(refresh ? ["--refresh"] : []); + proc.running = false; + // After the stop: a killed run can still finish its stream, and + // its handler clears `checking`. + mod.checking = refresh; + proc.running = true; + } + + property Process proc: Process { + stdout: StdioCollector { + onStreamFinished: { + const refreshed = mod.checking; + mod.checking = false; + try { mod.doc = JSON.parse(this.text); } catch (e) { return; } + // Only after a cached read: a refresh whose nvchecker failed + // leaves `checked` old and would otherwise loop. + if (!refreshed && mod.polling && Date.now() / 1000 - mod.doc.checked > 1800) + mod.run(true); + } + } + } + + tileContent: Component { + Text { + Component.onCompleted: mod.polling = true + Component.onDestruction: mod.polling = false + + width: parent ? parent.width : implicitWidth + elide: Text.ElideRight + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: mod.active ? Theme.text : Theme.subtext + text: { + if (!mod.doc) return "checking…"; + const parts = []; + if (mod.toInstall) parts.push(mod.toInstall + " to install"); + if (mod.toBuild) parts.push(mod.toBuild + " to build"); + return parts.join(" · ") || "up to date"; + } + } + } + + page: Component { + Page { + title: "Updates" + UpdatesPage { + width: parent.width + up: mod + } + } + } +} |
