diff --git a/src/window/globalmenu/dbusmenu.py b/src/window/globalmenu/dbusmenu.py index 5194f96..0f24a3c 100644 --- a/src/window/globalmenu/dbusmenu.py +++ b/src/window/globalmenu/dbusmenu.py @@ -233,9 +233,27 @@ class DBusMenuClient: if item.has_submenu and not item.children: try: self.about_to_show(item.id) + child_params = GLib.Variant( + "(iias)", (item.id, self._revision, []) + ) + child_res = self._call("GetLayout", child_params) + if child_res: + child_layout = child_res.get_child_value(1) + while child_layout.is_of_type(GLib.VariantType("v")): + child_layout = child_layout.get_variant() + children_variant = child_layout.get_child_value(2) + for i in range(children_variant.n_children()): + c = children_variant.get_child_value(i) + if c.is_of_type(GLib.VariantType("v")): + c = c.get_variant() + item.children.append( + self._parse_dbusmenu( + c, _depth + 1, item.id + ) + ) except Exception as e: logger.warning( - f"[dbusmenu] self.about_to_show(item.id) failed: {e}" + f"[dbusmenu] about_to_show/fetch children failed for {item.id}: {e}" ) except Exception as e: @@ -443,6 +461,17 @@ class DBusMenuClient: logger.warning(f"[dbusmenu] bus.signal_unsubscribe(sid) failed: {e}") self._signal_ids.clear() + def _find_and_update_children(self, items, parent_id, new_children): + for item in items: + if item.id == parent_id: + item.children = new_children + return True + if item.children and self._find_and_update_children( + item.children, parent_id, new_children + ): + return True + return False + def update_layout( self, parent_id: int, revision: int ) -> Optional[List[DBusMenuItem]]: @@ -456,11 +485,14 @@ class DBusMenuClient: while layout.is_of_type(GLib.VariantType("v")): layout = layout.get_variant() parsed = self._parse_dbusmenu(layout).children - if parent_id == 0: - with self._lock: + with self._lock: + if parent_id == 0: self._hash_cache = self._hash(parsed) self._cache = parsed self._cache_valid = True + elif self._cache is not None: + self._find_and_update_children(self._cache, parent_id, parsed) + self._cache_valid = True return parsed except Exception as e: logger.error(f"[Menu] update_layout error: {e}") diff --git a/src/window/globalmenu/service.py b/src/window/globalmenu/service.py index 1f3fc44..a8770c8 100644 --- a/src/window/globalmenu/service.py +++ b/src/window/globalmenu/service.py @@ -438,14 +438,21 @@ class GlobalMenuService(Service): subtree = entry.importer.update_layout(parent_id, revision) if subtree is None: return - if parent_id == 0: - with self._state_lock: - if entry.wm_class == self._current_wm_class: + with self._state_lock: + if entry.wm_class == self._current_wm_class: + full_menu = entry.importer.get_layout() + if full_menu is not None: + self._current_menu = full_menu + elif parent_id == 0: self._current_menu = subtree - with self._seq_lock: - self._extraction_seq += 1 - seq = self._extraction_seq - idle_add(self._emit_menu_changed, subtree, seq) + with self._seq_lock: + self._extraction_seq += 1 + seq = self._extraction_seq + idle_add( + self._emit_menu_changed, + self._current_menu or subtree, + seq, + ) def _handle_items_updated( self, diff --git a/src/window/panel/components/globalmenu.py b/src/window/panel/components/globalmenu.py index d666e8a..8b4005b 100644 --- a/src/window/panel/components/globalmenu.py +++ b/src/window/panel/components/globalmenu.py @@ -95,7 +95,40 @@ def _clean_label(label): return label.replace("_", "") -def _build_gtk_menu_from_children(children, click_handler): +def _on_item_select(item, svc, child, click_handler): + if getattr(item, "_lazy_loaded", False): + return + item._lazy_loaded = True + try: + logger.info( + f"[GlobalMenu] load submenu id={child.id} label={getattr(child, 'label', '')}" + ) + svc.about_to_show(child.id) + entry, _ = svc._resolve_current_entry() + if entry and entry.importer and hasattr(entry.importer, "update_layout"): + new_children = entry.importer.update_layout( + child.id, getattr(entry, "revision", 0) + ) + if new_children: + logger.info( + f"[GlobalMenu] got {len(new_children)} children for id={child.id}: " + f"{[getattr(c, 'label', '?') for c in new_children[:5]]}" + ) + new_menu = _build_gtk_menu_from_children( + new_children, click_handler, svc + ) + new_menu.show_all() + item.set_submenu(new_menu) + else: + logger.info( + f"[GlobalMenu] no children for id={child.id} " + f"(update_layout returned {new_children})" + ) + except Exception as e: + logger.warning(f"[GlobalMenu] submenu load failed: {e}") + + +def _build_gtk_menu_from_children(children, click_handler, svc=None): menu = Gtk.Menu() for child in children: @@ -116,11 +149,18 @@ def _build_gtk_menu_from_children(children, click_handler): sub_children = child.children if child.children else [] has_sub = getattr(child, "has_submenu", False) or bool(sub_children) - if has_sub and sub_children: + if has_sub: item = Gtk.MenuItem.new_with_label(f"{cleaned} \u25b8") - sub_menu = _build_gtk_menu_from_children(sub_children, click_handler) + sub_menu = _build_gtk_menu_from_children( + sub_children, click_handler, svc + ) sub_menu.show_all() item.set_submenu(sub_menu) + + if svc and getattr(child, "id", None) is not None: + item.connect( + "select", _on_item_select, svc, child, click_handler + ) else: item = Gtk.MenuItem.new_with_label(cleaned) if not enabled: @@ -225,7 +265,6 @@ class GlobalMenuDropdowns: except Exception: logger.exception("[globalmenu] cleanup failed") - self._destroy_widgets(self._gtk_menu_buttons) self._gtk_menu_buttons.clear() self._destroy_widgets(self._gtk_menus) @@ -260,7 +299,7 @@ class GlobalMenuDropdowns: setup_cursor_hover(menu_btn, "pointer") gtk_menu = _build_gtk_menu_from_children( - children, self._make_click_handler + children, self._make_click_handler, self._global_menu_svc ) menu_btn.set_popup(gtk_menu) menu_btn.show_all()