Fix GTK 4 foreground matching and menu labels

This commit is contained in:
Chase Cichorz authored and Angus Kola committed 2026-11-07 13:40:23 -05:00
1 parent d0c977281d
commit 49eeae89f4
8 files changed
+254 -9

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Storm Dragon
# Authors: Storm Dragon, Angus Kola
# SPDX-License-Identifier: GPL-3.0-or-later
"""A GTK 4 popover menu, whose items expose their label only through a relation."""
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gio, GLib, Gtk
def activate(app):
window = Gtk.ApplicationWindow(application=app, title="GTK 4 menu label probe")
for name in ("compose", "add-account", "grouping"):
action = (Gio.SimpleAction.new_stateful(name, None, GLib.Variant.new_boolean(True))
if name == "grouping" else Gio.SimpleAction.new(name, None))
window.add_action(action)
app.set_accels_for_action("win.compose", ["<Control>n"])
window.add_action(Gio.SimpleAction.new_stateful("filter", GLib.VariantType.new("s"),
GLib.Variant.new_string("all")))
menu = Gio.Menu()
menu.append("_New Message", "win.compose")
menu.append("_Add Account…", "win.add-account")
menu.append("Group into _Conversations", "win.grouping")
menu.append("_All Messages", "win.filter::all")
button = Gtk.MenuButton(label="File", menu_model=menu)
window.set_child(button)
window.present()
GLib.timeout_add(300, lambda: (button.popup(), False)[1])
app = Gtk.Application(application_id="org.stormux.CthulhuGtk4MenuProbe")
app.connect("activate", activate)
app.run(None)
+24
View File
@@ -31,5 +31,29 @@ field("multiple", ["Account", "identifier"])
field("named", ["Other label"], name="Explicit name")
field("unlabelled", [])
field("protected", ["Password"], password=True)
# Exercise the role-invalid Text fallback used by GTK 4, and ensure that a
# password relation target cannot contribute either its name or its contents.
invalid_label = Gtk.Label(label="Fallback label")
invalid_label.get_accessible().set_role(Atk.Role.INVALID)
invalid_label.get_accessible().set_name("")
box.pack_start(invalid_label, False, False, 0)
invalid_entry = Gtk.Entry()
invalid_entry.get_accessible().set_name("")
invalid_entry.get_accessible().set_description("invalid-label")
invalid_entry.get_accessible().add_relationship(Atk.RelationType.LABELLED_BY,
invalid_label.get_accessible())
box.pack_start(invalid_entry, False, False, 0)
password_label = Gtk.Entry()
password_label.set_visibility(False)
password_label.set_text("secret relation value")
password_label.get_accessible().set_name("secret relation name")
box.pack_start(password_label, False, False, 0)
safe_entry = Gtk.Entry()
safe_entry.get_accessible().set_name("")
safe_entry.get_accessible().set_description("password-target")
safe_entry.get_accessible().add_relationship(Atk.RelationType.LABELLED_BY,
password_label.get_accessible())
box.pack_start(safe_entry, False, False, 0)
window.show_all()
Gtk.main()
+58 -3
View File
@@ -26,6 +26,9 @@ def run_inside():
if os.environ.get("CTHULHU_NATIVE_EDIT_LABELS") == "1":
run_edit_label_probe()
return
if os.environ.get("CTHULHU_NATIVE_GTK4_MENU_LABELS") == "1":
run_gtk4_menu_label_probe()
return
if os.environ.get("CTHULHU_FOREGROUND_PID") == "1":
if os.environ.get("CTHULHU_FOREGROUND_PROXY") == "1":
run_proxy_probe()
@@ -58,11 +61,20 @@ def run_inside():
def run_edit_label_probe():
fixture = subprocess.Popen([sys.executable, str(Path(__file__).with_name("gtk_edit_labels.py"))])
try:
import pyatspi
import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi
def find_window():
return any(window.name == "Edit label probe"
for app in pyatspi.Registry.getDesktop(0) for window in app)
desktop = Atspi.get_desktop(0)
for i in range(desktop.get_child_count()):
app = desktop.get_child_at_index(i)
for j in range(app.get_child_count() if app else 0):
window = app.get_child_at_index(j)
if window is not None and window.get_name() == "Edit label probe":
return True
return False
wait_for(find_window, "edit label probe missing")
environment = dict(os.environ, GOCACHE="/tmp/cthulhu-go-cache", GOPROXY="off")
@@ -76,6 +88,49 @@ def run_edit_label_probe():
fixture.wait(timeout=3)
def run_gtk4_menu_label_probe():
fixture = subprocess.Popen([sys.executable, str(Path(__file__).with_name("gtk4_menu_labels.py"))],
env=dict(os.environ, GTK_A11Y="atspi"))
try:
import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi
def showing_menu_item(node, depth=0):
if node is None or depth > 40:
return False
if node.get_role_name() in ("menu item", "check menu item", "radio menu item") and \
node.get_state_set().contains(Atspi.StateType.SHOWING):
return True
return any(showing_menu_item(node.get_child_at_index(i), depth + 1)
for i in range(node.get_child_count()))
def menu_open():
desktop = Atspi.get_desktop(0)
for i in range(desktop.get_child_count()):
app = desktop.get_child_at_index(i)
for j in range(app.get_child_count() if app else 0):
window = app.get_child_at_index(j)
if window is not None and window.get_name() == "GTK 4 menu label probe":
try:
return showing_menu_item(window)
except Exception: # noqa: BLE001 - tree still settling
return False
return False
wait_for(menu_open, "GTK 4 menu probe missing")
environment = dict(os.environ, GOCACHE="/tmp/cthulhu-go-cache", GOPROXY="off")
subprocess.run(
["go", "test", "./internal/atspi", "-run", "^TestNativeGTK4MenuItemLabels$",
"-count=1", "-timeout=25s", "-v"],
cwd=Path(__file__).resolve().parents[1], env=environment, check=True,
)
finally:
fixture.terminate()
fixture.wait(timeout=3)
def run_thunar_probe():
with tempfile.TemporaryDirectory(prefix="cthulhu-thunar-") as temporary:
home = Path(temporary)