TITLE:
libayatana-appindicator activates XEmbed fallback when xapp-sn-watcher hasn't claimed its bus name at boot — duplicate icon in systray + xapp-status applets
────────────────────────────────────────────────────────────────────────────────
BODY:
Summary
AppIndicator apps show two tray icons at boot — one in xapp-status@cinnamon.org
(small, tooltip works) and one in systray@cinnamon.org (larger, no tooltip).
Single process, registered once on the StatusNotifierWatcher bus, but visible
in two applets simultaneously.
Observed on two independent apps: Diodon 1.13.0 and a local
AyatanaAppIndicator3 Python app. Neither app registers more than once.
This is the same symptom reported and never code-fixed in
cinnamon#8426, cjs#74, and xapp#157. The 2022 hypothesis from @mtwebster in
#157 — "They're probably not showing up in the xapp applet twice, but once
there and once in the old system tray applet" — is correct. I have controlled
reproduction and a confirmed fix.
Environment
- OS: Linux Mint 22.3 Cinnamon 6.6.7
- Kernel: 6.17.0-22-generic
- xapps-common: 3.2.2+zena
- cinnamon-common: 6.6.7+zena
- libayatana-appindicator3-1: 0.5.93-1build3
Visual identification — two applets, not one
The duplicate icons render differently, which identifies which applet each
belongs to:
| Property |
Small icon |
Large icon |
| Size |
Panel-native (xapp-status sizing) |
Larger (systray sizing) |
| Hover tooltip |
✓ appears (AppIndicator title prop) |
✗ absent (XEmbed has no equivalent) |
| Menu on click |
App's own menu |
App's own menu (same underlying object) |
| Applet |
xapp-status@cinnamon.org |
systray@cinnamon.org |
Confirmed by checking gdbus — the indicator appears once on the
StatusNotifierWatcher bus:
$ gdbus call --session --dest org.kde.StatusNotifierWatcher \
--object-path /StatusNotifierWatcher \
--method org.freedesktop.DBus.Properties.Get \
org.kde.StatusNotifierWatcher RegisteredStatusNotifierItems
(<[':1.72/org/ayatana/NotificationItem/Diodon'
':1.76/org/ayatana/NotificationItem/battery_limit'
...]>,)
The duplication is downstream of the watcher — between the watcher and the
panel applets.
Root cause: boot-time race between xapp-sn-watcher and AppIndicator clients
Both apps and xapp-sn-watcher autostart in the same second at session login:
14:52:48 — xapp-sn-watcher PID 1997
14:52:48 — battery-limit-tray PID 2025
14:52:48 — diodon PID 2038
libayatana-appindicator checks at set_status(ACTIVE) whether
org.kde.StatusNotifierWatcher is owned on the session bus. If the watcher
process is alive but has not yet claimed that name (a race window of a few
hundred milliseconds at cold boot), the library activates a legacy
Gtk.StatusIcon XEmbed fallback. When the watcher comes up moments later,
the library also completes the StatusNotifier registration. Both paths remain
active: one icon in xapp-status, one in systray.
Controlled reproduction confirming the mechanism
I patched my local AppIndicator app to wait until org.kde.StatusNotifierWatcher
is actually owned before calling set_status(ACTIVE):
def wait_for_status_notifier_watcher(timeout_s=30):
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
try:
res = bus.call_sync(
"org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", "NameHasOwner",
GLib.Variant("(s)", ("org.kde.StatusNotifierWatcher",)),
GLib.VariantType("(b)"), Gio.DBusCallFlags.NONE, 1000, None,
)
if res.unpack()[0]:
return True
except GLib.Error:
pass
time.sleep(0.2)
return False
# called immediately before set_status(ACTIVE)
wait_for_status_notifier_watcher()
self.indicator.set_status(AyatanaAppIndicator3.IndicatorStatus.ACTIVE)
I then ran 5 cold reboots, alternating between the patched and unpatched
versions:
With watcher-wait patch (tested separately before revert):
battery_limit: 1 icon across multiple boots — no duplicate.
After reverting the patch (5 consecutive cold reboots):
| Reboot |
Result |
| 1 |
battery_limit: 1 icon (race did not trigger) |
| 2 |
battery_limit: 2 icons (large systray + small xapp-status) |
| 3 |
Diodon: 2 icons (large systray + small xapp-status); battery_limit: 1 |
| 4 |
Diodon: 2 icons |
| 5 |
1 icon each (race did not trigger) |
Two things are clear from this data:
- The watcher-wait fix directly prevents the bug — with it in place, no
duplicate appeared across any boot. After reverting, the duplicate appeared
on 3 of 5 reboots across two different apps.
- The bug is non-deterministic — whether the race triggers depends on
scheduler timing at each boot. Reboots 1 and 5 show no duplicate even
without the fix. This explains why all three previous issues were closed
without a code fix: if a maintainer tested on a fast machine where the
watcher claims its name in under 100ms, they would never see the bug.
Diodon exhibits the identical symptom but I cannot patch it; it demonstrates
that the bug affects any AppIndicator app that starts in the same boot-time
window as xapp-sn-watcher.
Why the fix belongs in xapp-sn-watcher, not per-app
The app-side fix works but is the wrong place for it. Users cannot patch
apps they don't maintain (Diodon, Transmission, etc.). The clean fix is to
ensure xapp-sn-watcher claims org.kde.StatusNotifierWatcher on the session
bus before any user-session AppIndicator client could race it.
The simplest approach: add an autostart phase hint to
xapp-sn-watcher.desktop so it starts in the initialization phase rather
than the applications phase. Alternatively, libayatana-appindicator could
be patched to de-activate its XEmbed fallback if the watcher claims its name
shortly after activation — but that crosses package boundaries.
This was all compiled with Claude Code.
Happy to test patches or provide D-Bus traces.
TITLE:
libayatana-appindicator activates XEmbed fallback when xapp-sn-watcher hasn't claimed its bus name at boot — duplicate icon in systray + xapp-status applets
────────────────────────────────────────────────────────────────────────────────
BODY:
Summary
AppIndicator apps show two tray icons at boot — one in
xapp-status@cinnamon.org(small, tooltip works) and one in
systray@cinnamon.org(larger, no tooltip).Single process, registered once on the StatusNotifierWatcher bus, but visible
in two applets simultaneously.
Observed on two independent apps: Diodon 1.13.0 and a local
AyatanaAppIndicator3 Python app. Neither app registers more than once.
This is the same symptom reported and never code-fixed in
cinnamon#8426, cjs#74, and xapp#157. The 2022 hypothesis from @mtwebster in
#157 — "They're probably not showing up in the xapp applet twice, but once
there and once in the old system tray applet" — is correct. I have controlled
reproduction and a confirmed fix.
Environment
Visual identification — two applets, not one
The duplicate icons render differently, which identifies which applet each
belongs to:
titleprop)xapp-status@cinnamon.orgsystray@cinnamon.orgConfirmed by checking
gdbus— the indicator appears once on theStatusNotifierWatcher bus:
The duplication is downstream of the watcher — between the watcher and the
panel applets.
Root cause: boot-time race between xapp-sn-watcher and AppIndicator clients
Both apps and
xapp-sn-watcherautostart in the same second at session login:libayatana-appindicatorchecks atset_status(ACTIVE)whetherorg.kde.StatusNotifierWatcheris owned on the session bus. If the watcherprocess is alive but has not yet claimed that name (a race window of a few
hundred milliseconds at cold boot), the library activates a legacy
Gtk.StatusIconXEmbed fallback. When the watcher comes up moments later,the library also completes the StatusNotifier registration. Both paths remain
active: one icon in
xapp-status, one insystray.Controlled reproduction confirming the mechanism
I patched my local AppIndicator app to wait until
org.kde.StatusNotifierWatcheris actually owned before calling
set_status(ACTIVE):I then ran 5 cold reboots, alternating between the patched and unpatched
versions:
With watcher-wait patch (tested separately before revert):
battery_limit: 1 icon across multiple boots — no duplicate.
After reverting the patch (5 consecutive cold reboots):
Two things are clear from this data:
duplicate appeared across any boot. After reverting, the duplicate appeared
on 3 of 5 reboots across two different apps.
scheduler timing at each boot. Reboots 1 and 5 show no duplicate even
without the fix. This explains why all three previous issues were closed
without a code fix: if a maintainer tested on a fast machine where the
watcher claims its name in under 100ms, they would never see the bug.
Diodon exhibits the identical symptom but I cannot patch it; it demonstrates
that the bug affects any AppIndicator app that starts in the same boot-time
window as
xapp-sn-watcher.Why the fix belongs in xapp-sn-watcher, not per-app
The app-side fix works but is the wrong place for it. Users cannot patch
apps they don't maintain (Diodon, Transmission, etc.). The clean fix is to
ensure
xapp-sn-watcherclaimsorg.kde.StatusNotifierWatcheron the sessionbus before any user-session AppIndicator client could race it.
The simplest approach: add an autostart phase hint to
xapp-sn-watcher.desktopso it starts in the initialization phase ratherthan the applications phase. Alternatively,
libayatana-appindicatorcouldbe patched to de-activate its XEmbed fallback if the watcher claims its name
shortly after activation — but that crosses package boundaries.
This was all compiled with Claude Code.
Happy to test patches or provide D-Bus traces.