Skip to content

Commit

Permalink
feat: Show the number and the list of pending updates in systray's to…
Browse files Browse the repository at this point in the history
…oltip

Show the number and the list of pending updates in the systray applet tooltip, shown when hovering the mouse over the systray icon.

Closes #154
  • Loading branch information
Antiz96 committed Oct 1, 2024
1 parent 33f8bf1 commit cad2d9c
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 9 deletions.
32 changes: 30 additions & 2 deletions po/arch-update.pot
Original file line number Diff line number Diff line change
Expand Up @@ -588,11 +588,39 @@ 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:186
msgid "Run Arch-Update"
msgstr ""

#: src/lib/tray.py:124
#: src/lib/tray.py:187
msgid "Check for updates"
msgstr ""

#: src/lib/tray.py:188
msgid "Exit"
msgstr ""

Expand Down
38 changes: 36 additions & 2 deletions po/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -658,11 +658,45 @@ 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:186
msgid "Run Arch-Update"
msgstr "Lancer Arch-Update"

#: src/lib/tray.py:124
#: src/lib/tray.py:187
msgid "Check for updates"
msgstr "Vérifier les mises à jour"

#: src/lib/tray.py:188
msgid "Exit"
msgstr "Quitter"

Expand Down
3 changes: 2 additions & 1 deletion src/lib/full_upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 70 additions & 4 deletions src/lib/tray.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -79,25 +91,75 @@ 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()

def check(self):
""" Check for updates """
subprocess.run(["arch-update", "--check"], check=False)

def exit(self):
""" Close systray process """
sys.exit(0)
Expand All @@ -106,6 +168,7 @@ def __init__(self, iconfile):
""" Start Qt6 System Tray """

self.iconfile = iconfile
self.updatesfile = UPDATES_FILE
self.watcher = None

# Application
Expand All @@ -121,17 +184,20 @@ def __init__(self, iconfile):
# Menu
menu = QMenu()
menu_launch = QAction(_("Run Arch-Update"))
menu_check = QAction(_("Check for updates"))
menu_exit = QAction(_("Exit"))
menu.addAction(menu_launch)
menu.addAction(menu_check)
menu.addAction(menu_exit)

menu_launch.triggered.connect(self.run)
menu_check.triggered.connect(self.check)
menu_exit.triggered.connect(self.exit)

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()
Expand Down

0 comments on commit cad2d9c

Please sign in to comment.