feat(globalmenu): update
This commit is contained in:
10 files changed
+652
-469
No files matched your search
@@ -20,6 +20,7 @@ from fabric.utils import (
|
||||
from window.globalmenu.dbusmenu import DBusMenuClient, DBusMenuItem, _get_bus
|
||||
from window.globalmenu.environment import setup_global_menu_environment
|
||||
from window.globalmenu.gtkmenu import ActionMenuClient, GtkMenuClient
|
||||
from window.globalmenu.registrar import Registrar
|
||||
|
||||
_NODE_NAME_RE = re.compile(r'<node name="([^"]+)"')
|
||||
|
||||
@@ -133,29 +134,6 @@ def _dbus_get_pid(bus, service: str, timeout: int) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
REGISTRAR_XML = """
|
||||
<node>
|
||||
<interface name="com.canonical.AppMenu.Registrar">
|
||||
<method name="RegisterWindow">
|
||||
<arg type="u" name="windowId" direction="in"/>
|
||||
<arg type="o" name="menuObjectPath" direction="in"/>
|
||||
</method>
|
||||
<method name="UnregisterWindow">
|
||||
<arg type="u" name="windowId" direction="in"/>
|
||||
</method>
|
||||
<method name="GetMenuForWindow">
|
||||
<arg type="u" name="windowId" direction="in"/>
|
||||
<arg type="s" name="service" direction="out"/>
|
||||
<arg type="o" name="menuObjectPath" direction="out"/>
|
||||
</method>
|
||||
<method name="GetMenus">
|
||||
<arg type="a(uso)" name="menus" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
"""
|
||||
|
||||
|
||||
class GlobalMenuService(Service):
|
||||
"""Service that extracts and provides application menus for the global menu bar."""
|
||||
|
||||
@@ -189,9 +167,7 @@ class GlobalMenuService(Service):
|
||||
self._registry_lock = threading.Lock()
|
||||
self._state_lock = threading.Lock()
|
||||
|
||||
self._registered_menus: dict[str, tuple[str, str]] = {}
|
||||
|
||||
self._setup_registrar()
|
||||
Registrar.get_instance().start()
|
||||
self._subscribe_name_owner_changed()
|
||||
threading.Thread(target=self._setup_environment, daemon=True).start()
|
||||
threading.Thread(target=self._scan_initial_services, daemon=True).start()
|
||||
@@ -269,7 +245,6 @@ class GlobalMenuService(Service):
|
||||
return
|
||||
if entry.pid:
|
||||
self._pid_to_service.pop(entry.pid, None)
|
||||
self._registered_menus.pop(service_name, None)
|
||||
if isinstance(entry.importer, DBusMenuClient):
|
||||
try:
|
||||
entry.importer.disconnect_signals()
|
||||
@@ -288,79 +263,9 @@ class GlobalMenuService(Service):
|
||||
f"[GlobalMenuService] Cleaned up disappeared service: {service_name}"
|
||||
)
|
||||
|
||||
def _setup_registrar(self):
|
||||
try:
|
||||
self._bus = _get_bus()
|
||||
self._node = Gio.DBusNodeInfo.new_for_xml(REGISTRAR_XML)
|
||||
self._bus.register_object(
|
||||
"/com/canonical/AppMenu/Registrar",
|
||||
self._node.interfaces[0],
|
||||
self._handle_registrar_method,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
Gio.bus_own_name_on_connection(
|
||||
self._bus,
|
||||
"com.canonical.AppMenu.Registrar",
|
||||
Gio.BusNameOwnerFlags.NONE,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
logger.info("[GlobalMenuService] AppMenu Registrar DBus service started")
|
||||
except Exception as e:
|
||||
logger.error(f"[GlobalMenuService] Failed to start Registrar: {e}")
|
||||
|
||||
def _setup_environment(self):
|
||||
setup_global_menu_environment()
|
||||
|
||||
def _handle_registrar_method(
|
||||
self,
|
||||
connection,
|
||||
sender,
|
||||
object_path,
|
||||
_interface_name,
|
||||
method_name,
|
||||
parameters,
|
||||
invocation,
|
||||
):
|
||||
try:
|
||||
if method_name == "RegisterWindow":
|
||||
window_id, menu_path = parameters.unpack()
|
||||
logger.debug(
|
||||
f"[GlobalMenuService] App {sender} registered menu {menu_path} for window {window_id}"
|
||||
)
|
||||
with self._registry_lock:
|
||||
self._registered_menus[sender] = (sender, menu_path)
|
||||
invocation.return_value(None)
|
||||
|
||||
elif method_name == "UnregisterWindow":
|
||||
with self._registry_lock:
|
||||
self._registered_menus.pop(sender, None)
|
||||
invocation.return_value(None)
|
||||
|
||||
elif method_name == "GetMenuForWindow":
|
||||
window_id = parameters.unpack()[0]
|
||||
with self._registry_lock:
|
||||
entry = self._registered_menus.get(sender)
|
||||
if entry:
|
||||
svc_name, svc_path = entry
|
||||
invocation.return_value(GLib.Variant("(so)", (svc_name, svc_path)))
|
||||
else:
|
||||
invocation.return_value(
|
||||
GLib.Variant("(so)", (sender, "/com/canonical/menu/0"))
|
||||
)
|
||||
|
||||
elif method_name == "GetMenus":
|
||||
with self._registry_lock:
|
||||
menus = [
|
||||
(0, svc, path)
|
||||
for sender, (svc, path) in self._registered_menus.items()
|
||||
]
|
||||
invocation.return_value(GLib.Variant("(a(uso))", (menus,)))
|
||||
except Exception as e:
|
||||
logger.error(f"[GlobalMenuService] Registrar error {method_name}: {e}")
|
||||
invocation.return_error_literal(Gio.DBusError.FAILED, "FAILED", str(e))
|
||||
|
||||
def _uid(self, wm_class: str, pid: int) -> str:
|
||||
return f"{wm_class}:{pid}"
|
||||
|
||||
@@ -657,9 +562,10 @@ class GlobalMenuService(Service):
|
||||
|
||||
def _find_registrar(self, wm_class: str, target_pid: int) -> Optional[_MenuClient]:
|
||||
bus = _get_bus()
|
||||
with self._registry_lock:
|
||||
registered = dict(self._registered_menus)
|
||||
for sender, (service, path) in registered.items():
|
||||
registrar = Registrar.get_instance()
|
||||
registered = registrar.get_all_registrations()
|
||||
for window_id, reg in registered.items():
|
||||
service, path = reg.bus_name, reg.object_path
|
||||
try:
|
||||
pid = _dbus_get_pid(bus, service, _DBUS_TIMEOUT_FAST)
|
||||
if pid == target_pid or self._is_same_app(pid, target_pid):
|
||||
@@ -678,8 +584,7 @@ class GlobalMenuService(Service):
|
||||
return client
|
||||
except Exception as e:
|
||||
if "NameHasNoOwner" in str(e):
|
||||
with self._registry_lock:
|
||||
self._registered_menus.pop(sender, None)
|
||||
registrar.unregister_window(window_id)
|
||||
return None
|
||||
|
||||
def _find_fallback_scan(
|
||||
@@ -719,14 +624,17 @@ class GlobalMenuService(Service):
|
||||
if not (pid == target_pid or self._is_same_app(pid, target_pid)):
|
||||
continue
|
||||
|
||||
with self._registry_lock:
|
||||
if reg_entry := self._registered_menus.get(svc):
|
||||
logger.info(
|
||||
f"[GlobalMenuService] Found Registrar path {reg_entry[1]} for {svc}"
|
||||
)
|
||||
client = DBusMenuClient(reg_entry[0], reg_entry[1])
|
||||
self._cache_discovery(svc, reg_entry[1], pid, wm_class, client)
|
||||
return client
|
||||
registrar = Registrar.get_instance()
|
||||
reg_entry = registrar.get_menu_for_bus_name(svc)
|
||||
if reg_entry:
|
||||
logger.info(
|
||||
f"[GlobalMenuService] Found Registrar path {reg_entry.object_path} for {svc}"
|
||||
)
|
||||
client = DBusMenuClient(reg_entry.bus_name, reg_entry.object_path)
|
||||
self._cache_discovery(
|
||||
svc, reg_entry.object_path, pid, wm_class, client
|
||||
)
|
||||
return client
|
||||
|
||||
_safe_cls = re.sub(r"[^A-Za-z0-9_]", "", wm_class) if wm_class else ""
|
||||
_safe_cap = _safe_cls.capitalize() if _safe_cls else ""
|
||||
@@ -1090,6 +998,34 @@ class GlobalMenuService(Service):
|
||||
)
|
||||
return False
|
||||
|
||||
def get_current_dbusmenu_info(self) -> tuple[Optional[str], Optional[str]]:
|
||||
"""Return (unique_bus_name, object_path) for the current app if it has a DBusMenu server."""
|
||||
entry, _pid = self._resolve_current_entry()
|
||||
if entry and entry.importer and isinstance(entry.importer, DBusMenuClient):
|
||||
unique_name = self._resolve_unique_name(entry.importer.service_name)
|
||||
return unique_name, entry.importer.object_path
|
||||
return None, None
|
||||
|
||||
def _resolve_unique_name(self, well_known_name: str) -> Optional[str]:
|
||||
"""Resolve a well-known D-Bus name to its unique name (e.g. :1.123)."""
|
||||
try:
|
||||
bus = _get_bus()
|
||||
res = bus.call_sync(
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus",
|
||||
"GetNameOwner",
|
||||
GLib.Variant("(s)", (well_known_name,)),
|
||||
GLib.VariantType("(s)"),
|
||||
Gio.DBusCallFlags.NONE,
|
||||
_DBUS_TIMEOUT_FAST,
|
||||
None,
|
||||
)
|
||||
return res.get_child_value(0).get_string() if res else well_known_name
|
||||
except Exception as e:
|
||||
logger.warning(f"[service] GetNameOwner failed for {well_known_name}: {e}")
|
||||
return well_known_name
|
||||
|
||||
def about_to_show(self, item_id: int) -> bool:
|
||||
entry, _pid = self._resolve_current_entry()
|
||||
if entry and entry.importer:
|
||||
|
||||
Reference in new issue
Block a user