From 22ab5051dcfe53e7e1cb1036701d44fc15d1ae0c Mon Sep 17 00:00:00 2001 From: Robin Candau Date: Tue, 1 Oct 2024 03:47:34 +0200 Subject: [PATCH] feat: Show the number and the list of pending updates in systray's tooltip Show the number and the list of pending updates in the systray applet tooltip, shown when hovering the mouse over the systray icon. Closes https://github.com/Antiz96/arch-update/issues/154 --- po/arch-update.pot | 28 +++++++++++++++-- po/fr.po | 34 +++++++++++++++++++-- src/lib/full_upgrade.sh | 3 +- src/lib/tray.py | 67 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 123 insertions(+), 9 deletions(-) diff --git a/po/arch-update.pot b/po/arch-update.pot index 4de7dcd..dfeac15 100644 --- a/po/arch-update.pot +++ b/po/arch-update.pot @@ -588,11 +588,35 @@ msgstr "" msgid "No service requiring a post upgrade restart found\\n" msgstr "" -#: src/lib/tray.py:123 +#: src/lib/tray.py:124 +msgid "Arch-Update: 'updates' state file isn't found" +msgstr "" + +#: src/lib/tray.py:141 +msgid "Arch-Update: System is up to date" +msgstr "" + +#: src/lib/tray.py:144 +#, python-brace-format +msgid "" +"Arch-Update: 1 update available\n" +"\n" +"{update_list}" +msgstr "" + +#: src/lib/tray.py:149 +#, python-brace-format +msgid "" +"Arch-Update: {updates} updates available\n" +"\n" +"{update_list}" +msgstr "" + +#: src/lib/tray.py:182 msgid "Run Arch-Update" msgstr "" -#: src/lib/tray.py:124 +#: src/lib/tray.py:183 msgid "Exit" msgstr "" diff --git a/po/fr.po b/po/fr.po index 71dcba9..32688b3 100644 --- a/po/fr.po +++ b/po/fr.po @@ -658,11 +658,41 @@ msgstr "" msgid "No service requiring a post upgrade restart found\\n" msgstr "Aucun service nécessitant un redémarrage suite à la mise à jour n'a été trouvé\\n" -#: src/lib/tray.py:123 +#: src/lib/tray.py:124 +msgid "Arch-Update: 'updates' state file isn't found" +msgstr "Arch-Update : fichier d'état 'updates' non trouvé" + +#: src/lib/tray.py:141 +msgid "Arch-Update: System is up to date" +msgstr "Arch-Update : Le système à jour" + +#: src/lib/tray.py:144 +#, python-brace-format +msgid "" +"Arch-Update: 1 update available\n" +"\n" +"{update_list}" +msgstr "" +"Arch-Update : 1 mise à jour disponible\n" +"\n" +"{update_list}" + +#: src/lib/tray.py:149 +#, python-brace-format +msgid "" +"Arch-Update: {updates} updates available\n" +"\n" +"{update_list}" +msgstr "" +"Arch-Update : {updates} mises à jour disponibles\n" +"\n" +"{update_list}" + +#: src/lib/tray.py:182 msgid "Run Arch-Update" msgstr "Lancer Arch-Update" -#: src/lib/tray.py:124 +#: src/lib/tray.py:183 msgid "Exit" msgstr "Quitter" diff --git a/src/lib/full_upgrade.sh b/src/lib/full_upgrade.sh index 666b68e..bc515e0 100755 --- a/src/lib/full_upgrade.sh +++ b/src/lib/full_upgrade.sh @@ -18,8 +18,9 @@ if [ -n "${proceed_with_update}" ]; then # shellcheck source=src/lib/update.sh source "${libdir}/update.sh" - # Record the date of the last successful update (used by other stages) + # Record the date of the last successful update (used by other stages) and empty the 'updates' state file (which contains the list of pending updates) date +%Y-%m-%d > "${statedir}/last_update_run" + true > "${statedir}/last_updates_check" fi # Source the "orphan_packages" library which displays orphan packages and offers to remove them diff --git a/src/lib/tray.py b/src/lib/tray.py index dbad83f..6daad7a 100755 --- a/src/lib/tray.py +++ b/src/lib/tray.py @@ -10,6 +10,7 @@ import os import sys import subprocess +import re from PyQt6.QtGui import QIcon, QAction from PyQt6.QtWidgets import QApplication, QSystemTrayIcon, QMenu from PyQt6.QtCore import QFileSystemWatcher @@ -26,9 +27,20 @@ ICON_FILE = os.path.join( os.environ['HOME'], '.local', 'state', 'arch-update', 'tray_icon') if not os.path.isfile(ICON_FILE): - log.error("Statefile does not exist: %s", ICON_FILE) + log.error("State icon file does not exist: %s", ICON_FILE) sys.exit(1) +# Find Updates file +UPDATES_FILE = None +if 'XDG_STATE_HOME' in os.environ: + UPDATES_FILE = os.path.join( + os.environ['XDG_STATE_HOME'], 'arch-update', 'last_updates_check') +elif 'HOME' in os.environ: + UPDATES_FILE = os.path.join( + os.environ['HOME'], '.local', 'state', 'arch-update', 'last_updates_check') +if not os.path.isfile(UPDATES_FILE): + log.error("State updates file does not exist: %s", UPDATES_FILE) + # Find translations paths = [] if 'XDG_DATA_DIRS' in os.environ: @@ -79,21 +91,67 @@ class ArchUpdateQt6: """ System Tray using QT6 library """ def file_changed(self): - """ Called when icon file content changes """ + """ Update icon and tooltip on state file content changes """ + self.update_icon() + self.update_tooltip() - contents = "" + def update_icon(self): + """ Update the tray icon based on the icon state file content """ if self.watcher and not self.iconfile in self.watcher.files(): self.watcher.addPath(self.iconfile) + try: with open(self.iconfile, encoding="utf-8") as f: contents = f.readline().strip() except FileNotFoundError: log.error("Statefile Missing") sys.exit(1) + if contents.startswith("arch-update"): icon = QIcon.fromTheme(contents) self.tray.setIcon(icon) + def update_tooltip(self): + """ Update the tooltip with the number / list of pending updates """ + if self.watcher and not self.updatesfile in self.watcher.files(): + self.watcher.addPath(self.updatesfile) + + try: + with open(self.updatesfile, encoding="utf-8") as f: + updates_list = f.readlines() + except FileNotFoundError: + log.error("State updates file missing") + tooltip = _("Arch-Update: 'updates' state file isn't found") + self.tray.setToolTip(tooltip) + return + + # Define a regex pattern to match ANSI escape / color codes + ansi_escape_pattern = re.compile(r'\x1B\[[0-?9;]*[mK]') + + # Remove ANSI escape / color codes and any empty lines, then strip whitespaces + updates_list = [ + ansi_escape_pattern.sub('', update).strip() + for update in updates_list + if update.strip() + ] + + updates_count = len(updates_list) + + if updates_count == 0: + tooltip = _("Arch-Update: System is up to date") + elif updates_count == 1: + update_list = "".join(updates_list) + tooltip = _("Arch-Update: 1 update available\n\n{update_list}").format( + update_list=update_list + ) + else: + update_list = "\n".join(updates_list) + tooltip = _("Arch-Update: {updates} updates available\n\n{update_list}").format( + updates=updates_count, update_list=update_list + ) + + self.tray.setToolTip(tooltip) + def run(self): """ Start arch-update """ arch_update() @@ -106,6 +164,7 @@ def __init__(self, iconfile): """ Start Qt6 System Tray """ self.iconfile = iconfile + self.updatesfile = UPDATES_FILE self.watcher = None # Application @@ -131,7 +190,7 @@ def __init__(self, iconfile): self.tray.setContextMenu(menu) # File Watcher - self.watcher = QFileSystemWatcher([self.iconfile]) + self.watcher = QFileSystemWatcher([self.iconfile, self.updatesfile]) self.watcher.fileChanged.connect(self.file_changed) app.exec()