First pass at handling menus in wine.
This commit is contained in:
1 parent
cc6523e6fd
commit
0992ffee0d
9 files changed
+520
-18
No files matched your search
@@ -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)
|
||||
Reference in new issue
Block a user