Skip to content

SystemTrayIcon (Linux/ksni): context menu is empty when the tray host reads it before the menu is filled in #13624

Description

@holgertkey

Bug description

On Linux, a SystemTrayIcon with a Menu sometimes shows its icon, but right-clicking
it opens nothing. The menu stays missing for the rest of the process's lifetime.
Restarting the app fixes it.

It happens when the app starts slowly. The first launch of a freshly built binary (cold
disk cache) triggers it reliably. It never happens on warm launches.

The app side is correct the whole time. Querying the item's dbusmenu directly returns
the full menu:

$ busctl --user --json=short call org.kde.StatusNotifierItem-<pid>-1 /MenuBar \
    com.canonical.dbusmenu GetLayout iias 0 -- -1 0
{"type":"u(ia{sv}av)","data":[1,[0,{"children-display":...},[
  {... "label":"Show Tagent" ...}, {... "label":"Settings…" ...},
  {... "type":"separator" ...}, {... "label":"Quit" ...}]]]}

Cause (as far as I can tell)

In i-slint-core/items/system_tray/ksni.rs, PlatformTray::new calls spawn_tray(...)
with an empty menu (std::vec::Vec::new()). The real menu arrives slightly later:
SystemTrayIcon::init calls tray.rebuild_menu() after the handle exists, which runs
PlatformTray::rebuild_menu → handle.update(|tray| tray.menu = ...).

So between these two steps the StatusNotifierItem is already registered with the watcher
and exposes an empty /MenuBar layout. ksni does emit LayoutUpdated after the
update. But if the tray host fetched the layout inside that gap, GNOME's AppIndicator
extension evidently keeps the empty menu and never shows one on right-click. Normally
the gap is too short to matter; on a slow start it isn't.

A comment in spawn_tray says returning the handle synchronously "eliminates the
pending-menu race". That covers the menu being lost on the Slint side, but not the host
reading the menu before it's filled in.

Suggested fix

Build the menu before the item is registered. For example, pass the built menu into
spawn_tray on creation, the same way set_visible(true) already respawns the tray
with the cached self.menu. The alternative is to defer spawn_tray until
rebuild_menu has run once.

Reproducible code

export component TrayIcon inherits SystemTrayIcon {
    icon: @image-url("tray.png");
    tooltip: "Test";
    Menu {
        MenuItem { title: "Show"; }
        MenuSeparator {}
        MenuItem { title: "Quit"; activated => { /* quit */ } }
    }
}
fn main() -> Result<(), slint::PlatformError> {
    let _tray = TrayIcon::new()?;
    slint::run_event_loop_until_quit()
}

Steps to reproduce

  1. Build the app in release mode.
  2. Evict the binary from the page cache to make startup slow, as on the first launch
    after a build:
    python3 -c "import os,sys; fd=os.open(sys.argv[1],os.O_RDONLY); os.posix_fadvise(fd,0,0,os.POSIX_FADV_DONTNEED)" target/release/app
  3. Run the app.
  4. Right-click the tray icon.

Expected: the menu opens. Actual: nothing opens (the icon itself is shown, and
left-click/Activate works). Without step 2, the menu opens every time.

Environment details

  • Slint: 1.17.1 (ksni 0.3.6, zbus 5.18.0). Observed on 1.17.1. The same code path
    (PlatformTray::new spawning with an empty menu) is unchanged in v1.18.1 and on
    master, so I expect it to reproduce there too.
  • Platform/OS: Ubuntu 24.04.5 LTS, GNOME Shell 46.0, X11 session
  • Tray host: ubuntu-appindicators@ubuntu.com (gnome-shell-extension-appindicator 58-1ubuntu24.04.1)
  • Backend/renderer: winit (default)
  • Rust: 1.98.1

Additional notes

As a workaround, I tried re-registering the icon once after startup (toggling
visible false → true, so the respawned item carries the cached menu). If visible is
not bound to anything in the .slint file, hide() on the tray panics with
Constant property being changed (i-slint-core/properties.rs:788), because an unbound
built-in visible is compiled as a constant. That may be worth a look on its own, since
SystemTrayIcon's docs present show()/hide() as the way to toggle it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    need triagingIssue that the owner of the area still need to triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions