gnome-shell-extension-appindicator icon indicating copy to clipboard operation
gnome-shell-extension-appindicator copied to clipboard

do LayoutUpdate for menus with no entries

Open lost-melody opened this issue 1 year ago • 7 comments

This PR (partially) fixes #494.

Edit:

This problem is caused by the "empty menus":

  • For a LayoutUpdate signal, class DBusClient only requests a layout update when this._active is true (indicating the menu is currently open), or it is delayed until this._active becomes true (when the menu is opened later).
  • this._active is only set to true when an open-state-changed signal is emitted, which is triggered by opening the popup menu.
  • But an empty menu will never open, so this._active keeps false, so the delayed LayoutUpdate handler never runs.

QQ is an IM app who provides no menu entries before user logged in, and updates it when logged in. But it probably does not emit a LayoutUpdated signal before updating menu so its tray icon menu can never open until appindicator restarts (by disabling the extension or ending gnome session). Here we try updating entries before opening an empty menu. Only primary click and secondary click will trigger an update.

Wondering if there are better solutions...

lost-melody avatar Sep 12 '24 06:09 lost-melody

This also fixes Dropbox's tray icon menu not opening.

AmionSky avatar Feb 14 '25 17:02 AmionSky

Mhmh, since _updateMenu is triggered by the menu update, I'm wondering if we can instead react to some signal happening in dbusMenu.js to actually do this...

I can't personally reproduce this with QQ, not sure if that happens after logging in or if I can test it before.

3v1n0 avatar May 06 '25 19:05 3v1n0

The system tray icon in Blueman exhibits a similar persistence issue - once the application launches, the context menu associated with the tray icon becomes static and does not refresh to reflect state changes.

After launching Blueman, performing Bluetooth enable/disable operations through the interface will not update the visual state of the tray icon

The icon state only refreshes after either:

  • User session restart (logout/login cycle)

  • Manual reload of the desktop extension (disabling and re-enabling the extension)

This behavioral pattern suggests a fundamental refresh mechanism limitation in the tray icon implementation. Similar behavior can be expected in analogous scenarios where system tray components require dynamic state updates without full application/desktop environment reloads.

https://github.com/ubuntu/gnome-shell-extension-appindicator/issues/494#issuecomment-2574247477

plumlis avatar May 07 '25 00:05 plumlis

A minimal example to reproduce the issue. The tray should have Logout item after clicking the Login button in the application. However, you could not observe expected behavior until you lock the screen (this somehow updates the tray menu).

import gi
from gi.repository import AyatanaAppIndicator3 as AppIndicator3
from gi.repository import Gtk

class IndicatorApp:
    def __init__(self):
        # Create the main window
        self.window = Gtk.Window(title="Login Example")
        self.window.set_default_size(200, 100)
        self.window.connect("destroy", self.on_quit)
        
        # Create a button
        self.button = Gtk.Button(label="Login")
        self.button.connect("clicked", self.on_login_clicked)
        self.window.add(self.button)
        
        # Create the app indicator
        self.indicator = AppIndicator3.Indicator.new(
            "indicator-example",
            "system-run-symbolic",  # Default icon
            AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
        self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
        
        # Initial empty menu
        self.menu = Gtk.Menu()
        self.indicator.set_menu(self.menu)
        
        # Show the window
        self.window.show_all()
        
        # Track login state
        self.logged_in = False

    def on_login_clicked(self, widget):
        if not self.logged_in:
            # Update button text
            self.button.set_label("Logout")
            
            # Update tray menu
            self.update_tray_menu()
            
            self.logged_in = True
        else:
            # Logout action
            self.button.set_label("Login")
            self.clear_tray_menu()
            self.logged_in = False

    def update_tray_menu(self):
        # Clear existing menu items
        self.clear_tray_menu()
        
        # Create new menu items
        menu = Gtk.Menu()
        
        # Add logout item
        item_logout = Gtk.MenuItem(label="Logout")
        item_logout.connect("activate", self.on_logout_clicked)
        menu.append(item_logout)
        
        # Add separator
        menu.append(Gtk.SeparatorMenuItem())
        
        # Add quit item
        item_quit = Gtk.MenuItem(label="Quit")
        item_quit.connect("activate", self.on_quit)
        menu.append(item_quit)
        
        # Show all menu items
        menu.show_all()
        
        # Set the new menu
        self.indicator.set_menu(menu)

    def clear_tray_menu(self):
        # Create an empty menu
        menu = Gtk.Menu()
        self.indicator.set_menu(menu)

    def on_logout_clicked(self, widget):
        # Reset to login state
        self.button.set_label("Login")
        self.clear_tray_menu()
        self.logged_in = False

    def on_quit(self, widget=None):
        Gtk.main_quit()

if __name__ == "__main__":
    app = IndicatorApp()
    Gtk.main()

DerryAlex avatar May 07 '25 00:05 DerryAlex

Mhmh, since _updateMenu is triggered by the menu update, I'm wondering if we can instead react to some signal happening in dbusMenu.js to actually do this...

Yes I believe that there should be better solutions to this.

I was unable to inspect what exactly happened during the communication between QQ and StatusNotifierHost but with Bustle I can see how it is going now.

I can't personally reproduce this with QQ, not sure if that happens after logging in or if I can test it before.

~Yes, this happens after logging in.~

It seems that QQ does not have the problem any more, probably because it does not provide an empty menu before login.

Thanks to DerryAlex who provided another minimal reproduce.

lost-melody avatar May 07 '25 02:05 lost-melody

The code changes in this PR are different since @AmionSky's comment that this fixes the Dropbox tray icon, so I just wanted to chime in and say that the latest revision still works. I applied it in a minimalist way to v43 in Debian 12 (Gnome 43.9):

diff --git a/dbusMenu.js.bak b/dbusMenu.js
index a30502a..f74899e 100644
--- a/dbusMenu.js.bak
+++ b/dbusMenu.js
@@ -470,14 +470,14 @@ var DBusClient = GObject.registerClass({
 
     _onSignal(_sender, signal, params) {
         if (signal === 'LayoutUpdated') {
-            if (!this._active) {
+            if (!this._active && this.getRoot()?.getChildren().length) {
                 this._flagLayoutUpdateRequired = true;
                 return;
             }
 
             this._requestLayoutUpdate();
         } else if (signal === 'ItemsPropertiesUpdated') {
-            if (!this._active) {
+            if (!this._active && this.getRoot()?.getChildren().length) {
                 this._flagItemsUpdateRequired = true;
                 return;
             }

Now, when I boot my system and log in, the Dropbox tray icon works when I click it. I used to have to lock the screen and then unlock it before the icon would work.

mdmower avatar Jun 08 '25 18:06 mdmower

@3v1n0 - any chance this could be re-reviewed/merged?

mdmower avatar Aug 16 '25 16:08 mdmower