Add built-in updater, bump to 1.2.0
- predatord checks the Gitea releases every 6 hours, downloads the newest pacman package, verifies its SHA-256 from the release notes and installs it with `pacman -U` in a separate systemd unit - Update notice and install button on the System page, in the sidebar and in the panel widget; `predatorctl update [--check] [--yes]` - Optional API token for private repositories - packaging/release.py builds and publishes releases in the format the updater expects Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,10 @@ import argparse
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from PySide6.QtCore import QObject, Qt, QTimer, Signal
|
||||
from PySide6.QtGui import QColor, QIcon
|
||||
from PySide6.QtGui import QColor, QIcon, QPalette
|
||||
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||
from PySide6.QtWidgets import (QApplication, QButtonGroup, QCheckBox, QComboBox, QFrame,
|
||||
QGridLayout, QHBoxLayout, QInputDialog, QLabel, QListWidget,
|
||||
@@ -673,6 +674,29 @@ class SystemPage(Page):
|
||||
self.fw_card.body.addWidget(_muted("Values are stored in the laptop firmware (EC/BIOS)."))
|
||||
self.lay.addWidget(self.fw_card)
|
||||
|
||||
upd = Card("Updates")
|
||||
self.upd_head = QLabel("")
|
||||
self.upd_head.setObjectName("big")
|
||||
upd.body.addWidget(self.upd_head)
|
||||
self.upd_text = _muted("")
|
||||
self.upd_text.setTextFormat(Qt.MarkdownText)
|
||||
self.upd_text.setOpenExternalLinks(True)
|
||||
upd.body.addWidget(self.upd_text)
|
||||
h = QHBoxLayout()
|
||||
self.upd_check = QPushButton("Check now")
|
||||
self.upd_check.clicked.connect(lambda: (self.upd_check.setEnabled(False), win.backend.run("check_update")))
|
||||
h.addWidget(self.upd_check)
|
||||
self.upd_install = QPushButton("Install update")
|
||||
self.upd_install.setObjectName("primary")
|
||||
self.upd_install.clicked.connect(self._install)
|
||||
h.addWidget(self.upd_install)
|
||||
self.upd_auto = QCheckBox("Check for updates automatically")
|
||||
self.upd_auto.clicked.connect(lambda v: win.backend.run("set_updates", auto_check=bool(v)))
|
||||
h.addWidget(self.upd_auto)
|
||||
h.addStretch(1)
|
||||
upd.body.addLayout(h)
|
||||
self.lay.addWidget(upd)
|
||||
|
||||
svc = Card("Status")
|
||||
self.status = _muted("")
|
||||
svc.body.addWidget(self.status)
|
||||
@@ -728,7 +752,37 @@ class SystemPage(Page):
|
||||
box.setChecked(bool(info.get("value")))
|
||||
box.setToolTip(info.get("error", "") or "")
|
||||
|
||||
def _install(self):
|
||||
u = self.win.state.get("update") or {}
|
||||
if QMessageBox.question(self, "Install update",
|
||||
"Install Predator Control %s now?\n\nThe background service restarts briefly; "
|
||||
"reopen the app afterwards." % u.get("latest")) == QMessageBox.Yes:
|
||||
self.upd_install.setEnabled(False)
|
||||
self.win.backend.run("install_update")
|
||||
|
||||
def update_state(self, st):
|
||||
u = st.get("update") or {}
|
||||
status = st.get("update_status")
|
||||
self.upd_check.setEnabled(True)
|
||||
if status == "running":
|
||||
self.upd_head.setText("Installing update…")
|
||||
self.upd_text.setText("pacman is installing the new version.")
|
||||
self.upd_install.hide()
|
||||
elif u.get("available"):
|
||||
self.upd_head.setText("Version %s available" % u["latest"])
|
||||
text = "Installed: %s · [Release page](%s)\n\n%s" % (u["current"], u.get("url"), u.get("notes", "").split("### Install")[0].strip())
|
||||
if not u.get("can_install"):
|
||||
text += "\n\n*Automatic install needs the pacman package – otherwise run `git pull && sudo ./install.sh`.*"
|
||||
self.upd_text.setText(text)
|
||||
self.upd_install.setVisible(bool(u.get("can_install")))
|
||||
else:
|
||||
self.upd_head.setText("Up to date" if u else "Not checked yet")
|
||||
when = time.strftime("%d.%m.%Y %H:%M", time.localtime(u["checked"])) if u.get("checked") else "–"
|
||||
extra = "\n\nLast update failed – see: journalctl -u predator-control-update" if status == "failed" else ""
|
||||
err = ("\n\n⚠ %s" % st["update_error"]) if st.get("update_error") else ""
|
||||
self.upd_text.setText("Installed version %s · last check %s%s%s" % (st.get("version"), when, err, extra))
|
||||
self.upd_install.hide()
|
||||
self.upd_auto.setChecked(st["config"].get("updates", {}).get("auto_check", True))
|
||||
a = st["available"]
|
||||
yes = lambda v: "✔" if v else "✘"
|
||||
lines = ["%s Service predatord %s" % (yes(True), st.get("version")),
|
||||
@@ -833,13 +887,17 @@ class MainWindow(QMainWindow):
|
||||
if self.stack.currentWidget() is not bat_page:
|
||||
bat_page.indicator.set_battery(st["battery"]) # keep the power history going
|
||||
s = st["sensors"]
|
||||
self.footer.setText("%s\nCPU %s °C · GPU %s" % (
|
||||
u = st.get("update") or {}
|
||||
badge = ("⬆ Update %s available\n" % u["latest"]) if u.get("available") else ""
|
||||
self.footer.setText(badge + "%s\nCPU %s °C · GPU %s" % (
|
||||
PROFILE_INFO.get(st["profile"], (st["profile"],))[0], s.get("cpu_temp"),
|
||||
("%s °C" % s["gpu_temp"]) if s.get("gpu_temp") else "off"))
|
||||
|
||||
def _done(self, cmd, result, err):
|
||||
if err:
|
||||
self.statusBar().showMessage("Error: %s" % err, 8000)
|
||||
elif cmd == "install_update":
|
||||
self.statusBar().showMessage("Update is being installed…", 8000)
|
||||
elif cmd == "get_firmware":
|
||||
self.pages["system"][0].firmware(result)
|
||||
self.pages["battery"][0].firmware(result)
|
||||
@@ -860,6 +918,9 @@ def main(argv=None):
|
||||
app.setDesktopFileName("predator-control")
|
||||
app.setStyle("Fusion")
|
||||
app.setStyleSheet(theme.QSS)
|
||||
pal = app.palette()
|
||||
pal.setColor(QPalette.Link, QColor(theme.ACCENT))
|
||||
app.setPalette(pal)
|
||||
|
||||
# single instance: forward the requested page to a running window
|
||||
name = "predator-control-%d" % os.getuid()
|
||||
|
||||
Reference in New Issue
Block a user