Skip to content

Add native StatusNotifier tray support on Linux using dbus - #1467

Open
IdelsTak wants to merge 142 commits into
qzind:masterfrom
IdelsTak:issue-1456-linux-dbus-tray
Open

Add native StatusNotifier tray support on Linux using dbus#1467
IdelsTak wants to merge 142 commits into
qzind:masterfrom
IdelsTak:issue-1456-linux-dbus-tray

Conversation

@IdelsTak

@IdelsTak IdelsTak commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

QZ Tray can now use the desktop panel tray when a supported StatusNotifier host is available. But, it also keeps the existing taskbar window as a safe fallback

The new tray recreates the existing menu structure from TrayManager, including Advanced and Diagnostic submenus, checkbox items, separators, reload, About, autostart, and Exit

The Linux StatusNotifier path now also sends native desktop notifications through org.freedesktop.Notifications, so the SNI tray path no longer loses messages that previously went through AWT TrayIcon.displayMessage(...)

Changes

  • Added a Linux environment probe for the session bus, active StatusNotifier watcher, desktop name, display state, and headless state
  • Added support for both org.kde and org.freedesktop StatusNotifier namespaces
  • Added a dedicated D-Bus connection and unique item service name based on the QZ process ID
  • Added watcher restart handling so the tray item registers again when the desktop panel restarts
  • Added a temporary hicolor icon theme with PNG and symbolic SVG assets
  • Added Cinnamon and LXQt handling through the generated absolute PNG path for SNI hosts that do not reliably resolve the temporary symbolic theme
  • Added a complete com.canonical.dbusmenu implementation for the existing QZ Tray menu
  • Routed menu actions back through TrayManager on the Swing event thread
  • Added live checkbox state updates through ItemsPropertiesUpdated
  • Kept D-Bus menu calls ordered so nested submenus are realized correctly on XFCE
  • Added cleanup for the D-Bus connection and watcher listener during shutdown
  • Added desktop-specific guidance when a StatusNotifier host is missing
  • Added an opt-in property for testing desktops before they join the production allowlist
  • Added a Freedesktop Notifications client for Linux SNI desktop notifications
  • Routed existing TrayManager.displayMessage(...) calls to the SNI tray when AWT tray is not active
  • Kept the existing tray.notifications info-message suppression rule unchanged
  • Kept notification body text unchanged while using QZ Tray as the app name and one-word native summaries: Update, Attention, and Problem
  • Used a generated PNG file:// URI for notification icons after XFCE, Budgie, KDE, and LXQt icon testing
  • Added notification-server activation handling for desktops such as MATE where org.freedesktop.Notifications may be activatable but not already owned

Crucial StatusNotifier property fix

I made the StatusNotifier properties to now use an explicit org.freedesktop.DBus.Properties implementation

This was needed because annotation-bound property getters returned raw values for Properties.Get instead of the required D-Bus variants

Some hosts tolerated those replies, while LXQt could not decode IconName, IconThemePath, or Menu

Returning proper variants fixed both the fallback gear icon and the missing tray menu on LXQt

And this protocol fix also allowed COSMIC to load the QZ icon and menu!

Native Linux notifications

The SNI path now sends desktop notifications directly over D-Bus using org.freedesktop.Notifications.Notify

This reuses the existing tray message flow instead of changing each notification call site

The notification payload is intentionally small:

  • appName: QZ Tray
  • appIcon: generated PNG file:// URI
  • summary: one-word level label
  • body: existing message text
  • urgency: mapped from TrayIcon.MessageType
  • expireTimeout: desktop default

The Java D-Bus method is named sendNotification(...) and mapped to Notify with @DBusMemberName("Notify") to avoid colliding with Object.notify()

If the notification service is missing or cannot be activated, QZ logs a low-noise warning and keeps the tray/menu path running

Smoke tests and manual DEs verifications

Desktop Result
Ubuntu GNOME Icon, menu, actions, AppIndicator integration, runtime smoke, and native notifications work
KDE Icon, menu, actions, nested menus, runtime smoke, and native notifications work
XFCE Icon, menu, actions, checkbox updates, nested menus, and native notifications work
LXQt on Lubuntu 26.04 Icon and menu work after the property variant fix; SNI icon now uses the generated PNG path because symbolic theme handling was flaky
Ubuntu Budgie 26.04 End-to-end tray behavior works; notification icons need the PNG file:// URI
Linux Mint 22.3 Cinnamon End-to-end tray behavior works with the generated absolute PNG icon path
Linux Mint 22.3 MATE End-to-end tray behavior works; notification daemon may need D-Bus activation before sending
COSMIC Icon and main menu now work, but the nested Diagnostic submenu does not open
Pantheon on elementary OS 8.1 No StatusNotifier watcher or panel host was available

Only desktops that passed the full QZ Tray flow are enabled in the production allowlist

COSMIC remains outside the allowlist until the nested Diagnostic submenu works

Notification icon findings

The notification icon path is separate from the SNI tray icon path

Notification daemons do not receive or resolve the StatusNotifier IconThemePath, so qz-tray-symbolic can show a missing-icon placeholder

SVG path and SVG file:// URI worked on some desktops, but KDE and LXQt did not handle notification SVG theming consistently enough for this PR

Budgie rendered placeholders with a bare PNG path but worked with the PNG file:// URI

For this branch, notification icons use the generated PNG file:// URI, while the SNI tray can still use symbolic SVGs except for Cinnamon and LXQt

Fallback behavior

QZ Tray keeps using the taskbar fallback when any of these conditions apply

  • The process is headless
  • The session bus cannot be reached
  • No StatusNotifier watcher is present
  • The desktop has not passed end-to-end verification
  • StatusNotifier startup fails

Unverified desktops can be tested with -Dqz.sni.allowUnverifiedDesktop=true without changing the production allowlist

Testing

  • Manual icon and menu checks across GNOME, KDE, XFCE, LXQt, Budgie, Cinnamon, MATE, COSMIC, and Pantheon
  • Manual checks for nested menus, checkbox updates, About, reload, autostart, diagnostics, and Exit
  • Manual D-Bus inspection with gdbus, busctl, and dbus-monitor
  • Direct Freedesktop notification smoke with test/manual/linux-notifications-smoke.sh
  • Notification icon matrix smoke with test/manual/linux-notification-icon-smoke.sh
  • Real qz.App runtime notification smoke with test/manual/qz-app-runtime-smoke.sh
  • SNI menu regression smoke with test/manual/qz-app-menu-smoke.sh

TL;DR

  • The Linux taskbar fallback is still present and remains the recovery path
  • Existing TrayManager behavior is reused instead of duplicating application actions inside the D-Bus layer
  • Existing notification call sites are reused instead of adding Linux-specific notification calls around the app
  • Icon lookup differs between desktop hosts, so the generic symbolic theme path is kept alongside PNG-path exceptions for Cinnamon and LXQt
  • Notification icon lookup differs from SNI icon lookup, so notifications use a generated PNG file:// URI
  • The item exports both StatusNotifier interface namespaces while registering against the watcher namespace detected at runtime
  • Menu is exported before watcher registration because some hosts inspect the item immediately
  • The D-Bus connection is intentionally not shared so tray ownership and shutdown remain isolated

@tresf
tresf deleted the branch qzind:master July 29, 2026 17:52
@tresf tresf closed this Jul 29, 2026
@tresf tresf reopened this Jul 29, 2026
@tresf
tresf changed the base branch from ivy to master July 29, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants