feat(src/utils): add ActionMenuClient and depth limits for menu parsing

Add flat GTK Action handling and guard DBus menu depth to prevent recursion and overflow.
This commit is contained in:
sankalp committed 2026-06-23 14:28:47 +05:45
1 parent b4d011d158
commit 2afbe05229
2 files changed
+103 -19

No files matched your search

+23 -18
View File
@@ -171,7 +171,9 @@ class DBusMenuClient:
return str(hash("".join(flat)))
# DBUSMENU PARSER
def _parse_dbusmenu(self, node):
def _parse_dbusmenu(self, node, _depth: int = 0):
if _depth > 20:
return DBusMenuItem(id=0)
item = DBusMenuItem(id=0)
try:
@@ -214,10 +216,12 @@ class DBusMenuClient:
children = node.get_child_value(2)
for i in range(children.n_children()):
if _depth >= 20:
break
c = children.get_child_value(i)
if c.is_of_type(GLib.VariantType("v")):
c = c.get_variant()
item.children.append(self._parse_dbusmenu(c))
item.children.append(self._parse_dbusmenu(c, _depth + 1))
if item.has_submenu and not item.children:
try:
@@ -248,25 +252,26 @@ class DBusMenuClient:
except Exception:
return None
def _parse_gtk_menu(self, variant):
def _parse_gtk_menu(self, variant, _depth: int = 0):
if _depth > 10:
return []
items = []
try:
if not variant:
if not variant or not variant.is_container():
return []
if variant.is_container():
for i in range(variant.n_children()):
c = variant.get_child_value(i)
item = DBusMenuItem(id=i)
try:
if c.n_children() > 0:
item.label = c.get_child_value(0).get_string()
except Exception:
pass
if c.n_children() > 1:
sub = c.get_child_value(c.n_children() - 1)
item.children = self._parse_gtk_menu(sub)
item.has_submenu = bool(item.children)
items.append(item)
for i in range(variant.n_children()):
c = variant.get_child_value(i)
item = DBusMenuItem(id=i)
try:
if c.n_children() > 0:
item.label = c.get_child_value(0).get_string()
except Exception:
pass
if c.n_children() > 1:
sub = c.get_child_value(c.n_children() - 1)
item.children = self._parse_gtk_menu(sub, _depth + 1)
item.has_submenu = bool(item.children)
items.append(item)
except Exception:
pass
return items
+80 -1
View File
@@ -1,5 +1,5 @@
import re
from typing import List, Optional
from typing import Dict, List, Optional
from fabric.utils import Gio, GLib, logger
@@ -172,3 +172,82 @@ class GtkMenuClient:
continue
except Exception as e:
logger.error(f"[GtkMenuClient] click failed: {e}")
class ActionMenuClient:
"""Flat menu from org.gtk.Actions when org.gtk.Menus is unavailable."""
__slots__ = ("service_name", "object_path", "_cached_items", "_action_map")
def __init__(self, service_name: str, object_path: str):
self.service_name = service_name
self.object_path = object_path
self._cached_items: List[DBusMenuItem] = []
self._action_map: Dict[int, str] = {}
def _call(self, interface, method, params, reply_type=None):
try:
return _get_bus().call_sync(
self.service_name,
self.object_path,
interface,
method,
params,
reply_type,
Gio.DBusCallFlags.NONE,
3000,
None,
)
except Exception:
return None
def get_layout(self, parent_id: int = 0) -> List[DBusMenuItem]:
if parent_id != 0:
return []
res = self._call(
"org.gtk.Actions",
"DescribeAll",
None,
GLib.VariantType("(a{s(bgav)})"),
)
if not res:
return self._cached_items
try:
data = res.get_child_value(0).unpack()
items: List[DBusMenuItem] = []
self._action_map = {}
for i, (name, (enabled, param_type, state)) in enumerate(data.items()):
label = name.replace("-", " ").replace("_", " ").strip().title()
item = DBusMenuItem(id=hash((0, i)))
item.action_name = name
# Hide actions that need parameters — they can't work in a flat menu
item.visible = not bool(param_type)
item.enabled = enabled if not param_type else False
item.label = label
self._action_map[item.id] = name
items.append(item)
self._cached_items = items
return items
except Exception as e:
logger.error(f"[ActionMenuClient] parse error: {e}")
return self._cached_items
def about_to_show(self, item_id: int) -> bool:
return False
def click_item(
self, item_id: int, action_name: str = "", action_target=None
) -> None:
if not action_name:
action_name = self._action_map.get(item_id, "")
if not action_name:
return
try:
self._call(
"org.gtk.Actions",
"Activate",
GLib.Variant("(sava{sv})", (action_name, [], {})),
)
except Exception as e:
logger.error(f"[ActionMenuClient] click failed: {e}")