First pass at handling menus in wine.

This commit is contained in:
Storm Dragon committed 2026-10-09 15:45:18 -04:00
1 parent cc6523e6fd
commit 0992ffee0d
9 files changed
+520 -18

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
"""Exercise a real Wine menu hook against a private D-Bus recording service.
Requires a graphical session, PyGObject, Wine, and MinGW. No speech is sent to
the running reader. The fixture window closes automatically.
"""
import argparse
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import time
from gi.repository import Gio, GLib
parser = argparse.ArgumentParser()
parser.add_argument("--build-dir", default="build")
parser.add_argument("--session", action="store_true")
args = parser.parse_args()
build = Path(args.build_dir).resolve()
if not args.session:
sys.exit(subprocess.call(["dbus-run-session", "--", sys.executable,
__file__, "--session", "--build-dir", str(build)]))
interface = "org.stormux.Cthulhu1.Service"
object_path = "/org/stormux/Cthulhu1/Service"
xml = '<node><interface name="' + interface + '"><method name="PresentAccessibleEvent">'
for index, kind in enumerate(["s"] * 7 + ["i", "i", "s"]):
xml += f'<arg name="arg{index}" type="{kind}" direction="in"/>'
xml += '<arg type="b" direction="out"/></method></interface></node>'
events = []
def record(connection, sender, path, iface, method, parameters, invocation):
events.append(parameters.unpack())
invocation.return_value(GLib.Variant("(b)", (True,)))
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
connection.register_object(object_path, Gio.DBusNodeInfo.new_for_xml(xml).interfaces[0], record, None, None)
connection.call_sync("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", "RequestName",
GLib.Variant("(su)", (interface, 0)), None,
Gio.DBusCallFlags.NONE, 1000, None)
with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
env = os.environ.copy()
env.update(WINEPREFIX=str(Path(temporary) / "prefix"), WINEDEBUG="-all",
WINEDLLOVERRIDES="mscoree,mshtml=", CTHULHU_WINE_ACCESS_MANAGED="1")
fixture = Path(temporary) / "menu-fixture.exe"
subprocess.run(["x86_64-w64-mingw32-gcc", "-Wall", "-Wextra", "-mwindows",
"-o", str(fixture), str(Path(__file__).with_name("menu_fixture.c")),
"-luser32"], check=True)
helper = None
app = None
try:
subprocess.run(["wineboot", "-u"], env=env, check=True, timeout=45)
helper = subprocess.Popen([str(build / "cthulhu-wine-access.exe"), "--no-controller"], env=env)
# Allow the helper to register its bridge before opening the fixture.
deadline = time.monotonic() + 1
while time.monotonic() < deadline:
while GLib.MainContext.default().iteration(False):
pass
time.sleep(0.01)
assert helper.poll() is None, "helper exited before fixture startup"
app = subprocess.Popen(["wine", str(fixture)], env=env)
deadline = time.monotonic() + 15
while app.poll() is None and time.monotonic() < deadline:
while GLib.MainContext.default().iteration(False):
pass
time.sleep(0.01)
assert app.poll() == 0, "fixture failed or timed out"
for _ in range(20):
while GLib.MainContext.default().iteration(False):
pass
time.sleep(0.01)
menus = [event for event in events if ":menu:" in event[0]]
for event in menus:
print(event[2:7])
assert any(e[2] == "File" and e[3] == "submenu" for e in menus), "File menu missing"
assert any(e[2] == "Open" and e[4] == "Ctrl+O" for e in menus), "Open or shortcut missing"
assert any(e[2] == "Word wrap" and e[6] == "checked" for e in menus), "checked item missing"
assert any(e[2] == "Edit" and e[3] == "submenu" for e in menus), "Edit menu missing"
assert any(e[2] == "Cut" and e[4] == "Ctrl+X" for e in menus), "Edit command missing"
assert any(e[2] == "Context action" for e in menus), "context menu missing"
assert any(e[2] == "Favorites" and e[3] == "submenu" for e in menus), "nested submenu missing"
assert any(e[2] == "Nested action" for e in menus), "nested command missing"
saved = [e for e in menus if e[2] == "Pinned & offline"]
assert len(saved) == 2, f"duplicate suppression/menu reset failed: {len(saved)}"
assert all(e[4] == "Ctrl+P" and e[6] == "checked, unavailable" for e in saved)
assert any(e[2] == "Return to control" and e[3] == "button" for e in events), "control focus missing"
subprocess.run([str(build / "cthulhu-wine-access.exe"),
"--no-dialog-reader", "--no-controller"],
env=env, check=True, timeout=5)
print("PASS: native and nested menus, context menus, shortcuts, states, "
"duplicate suppression, reopening, control focus, and disable options")
finally:
# Only this test's newly created Wine prefix is affected.
subprocess.run(["wineserver", "-k"], env=env, timeout=10, check=False)
for child in (app, helper):
if child is not None:
child.wait(timeout=10)