From a6cdad5082e87c9ca8698ad03bdc8411c39fb04c Mon Sep 17 00:00:00 2001 From: Joachim Hummel Date: Sat, 16 May 2026 23:16:39 +0200 Subject: [PATCH] Fix battery tray icon vanishing after boot The wait loop polled QSystemTrayIcon.isSystemTrayAvailable(), but Qt5 caches that value from the moment QApplication is constructed. At login the autostart wins the race against wf-panel-pi's tray, so Qt caches "no tray" permanently, the loop never escapes, and the process hangs until the 120s timeout and exits. Add wait_for_tray(), which polls the session bus directly (dbus-send NameHasOwner for org.kde.StatusNotifierWatcher) before QApplication is constructed, so Qt reports the tray correctly. Co-Authored-By: Claude Opus 4.7 (1M context) --- monitor/batteryIcon.py | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/monitor/batteryIcon.py b/monitor/batteryIcon.py index 63d7f91..01b6a53 100755 --- a/monitor/batteryIcon.py +++ b/monitor/batteryIcon.py @@ -108,14 +108,48 @@ def make_battery_icon(percent, charging): return QIcon(pix) +def tray_host_ready(): + """True once a StatusNotifier tray host is registered on the session bus. + + Queried over D-Bus directly instead of via + QSystemTrayIcon.isSystemTrayAvailable(): Qt caches that result from the + moment QApplication is constructed. At login the icon's autostart usually + wins the race against wf-panel-pi, so Qt caches "no tray" forever and the + icon never appears even after the panel's tray comes up. Checking the bus + directly dodges that cache -- we build QApplication only once the host is + actually present. + """ + try: + result = subprocess.run( + ["dbus-send", "--session", "--print-reply", + "--dest=org.freedesktop.DBus", "/org/freedesktop/DBus", + "org.freedesktop.DBus.NameHasOwner", + "string:org.kde.StatusNotifierWatcher"], + capture_output=True, text=True, timeout=5) + except (subprocess.SubprocessError, OSError): + return False + return "boolean true" in result.stdout + + +def wait_for_tray(timeout=180): + """Block until a tray host appears, so autostart survives a cold boot.""" + deadline = time.monotonic() + timeout + while not tray_host_ready(): + if time.monotonic() > deadline: + print(f"Tray host not available after {timeout}s", file=sys.stderr) + sys.exit(1) + time.sleep(2) + + class BatteryTray: def __init__(self): self.app = QApplication(sys.argv) self.app.setQuitOnLastWindowClosed(False) - # At login the panel may not have started its system tray yet. - # Wait for it instead of giving up, so autostart works after a reboot. - deadline = time.monotonic() + 120 + # wait_for_tray() already confirmed a tray host on the bus, so Qt now + # reports the tray correctly. Keep a short re-check as a guard against + # the host still finishing its own registration. + deadline = time.monotonic() + 60 while not QSystemTrayIcon.isSystemTrayAvailable(): if time.monotonic() > deadline: print("System tray not available after 120s", file=sys.stderr) @@ -208,6 +242,7 @@ class BatteryTray: def main(): + wait_for_tray() bt = BatteryTray() sys.exit(bt.run())