// Copyright (C) 2026 Danilo M. // // 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 } } } }