Updates for when prism doesn't behave.
This commit is contained in:
1 parent
0d9cc2651d
commit
6e92ad13d1
12 files changed
+415
-5
No files matched your search
@@ -0,0 +1,98 @@
|
||||
"""Route real UIA notifications through native hooks to a private reader."""
|
||||
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("--reader", choices=("cthulhu", "orca"), default="cthulhu")
|
||||
parser.add_argument("--32-bit", dest="is32Bit", action="store_true")
|
||||
parser.add_argument("--session", action="store_true")
|
||||
parser.add_argument("--prism-dll", type=Path)
|
||||
parser.add_argument("--prism-include", type=Path)
|
||||
args = parser.parse_args()
|
||||
if bool(args.prism_dll) != bool(args.prism_include) or (args.prism_dll and args.is32Bit):
|
||||
parser.error("Prism checks require both --prism-dll and --prism-include, and a 64-bit DLL")
|
||||
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), "--reader", args.reader,
|
||||
*(["--prism-dll", str(args.prism_dll.resolve()),
|
||||
"--prism-include", str(args.prism_include.resolve())]
|
||||
if args.prism_dll else []),
|
||||
*(["--32-bit"] if args.is32Bit else [])]))
|
||||
|
||||
interface = "org.gnome.Orca1.Service" if args.reader == "orca" else "org.stormux.Cthulhu1.Service"
|
||||
objectPath = "/" + interface.replace(".", "/")
|
||||
xml = '<node><interface name="' + interface + '">'
|
||||
for method, inputs in (("SpeakText", "sb"), ("CancelSpeech", ""),
|
||||
("PresentAccessibleEvent", "sssssssiis")):
|
||||
xml += '<method name="' + method + '">'
|
||||
xml += ''.join('<arg type="' + kind + '" direction="in"/>' for kind in inputs)
|
||||
xml += '<arg type="b" direction="out"/></method>'
|
||||
xml += '</interface></node>'
|
||||
events = []
|
||||
|
||||
|
||||
def record(connection, sender, path, iface, method, parameters, invocation):
|
||||
if method != "PresentAccessibleEvent":
|
||||
events.append((method, parameters.unpack()))
|
||||
invocation.return_value(GLib.Variant("(b)", (True,)))
|
||||
|
||||
|
||||
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
connection.register_object(objectPath, 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-notifications-") as temporary:
|
||||
tempPath = Path(temporary)
|
||||
env = os.environ.copy()
|
||||
env.update(WINEPREFIX=str(tempPath / "prefix"), WINEDEBUG="-all",
|
||||
WINEDLLOVERRIDES="mscoree,mshtml=", CTHULHU_WINE_ACCESS_MANAGED="1")
|
||||
fixture = tempPath / "notification-fixture.exe"
|
||||
compiler = "i686-w64-mingw32-gcc" if args.is32Bit else "x86_64-w64-mingw32-gcc"
|
||||
subprocess.run([compiler, "-Wall", "-Wextra", "-mwindows", "-o", str(fixture),
|
||||
*(["-DWINE_ACCESS_PRISM_FIXTURE", "-I" + str(args.prism_include.resolve())]
|
||||
if args.prism_dll else []),
|
||||
str(Path(__file__).with_name("notification_fixture.c")),
|
||||
"-luser32", "-lole32", "-loleaut32", "-luiautomationcore", "-lshell32"], check=True)
|
||||
helper = app = None
|
||||
try:
|
||||
subprocess.run(["wineboot", "-u"], env=env, check=True, timeout=45)
|
||||
helper = subprocess.Popen([str(build / "cthulhu-wine-access.exe"), "--no-controller",
|
||||
"--reader", args.reader], env=env)
|
||||
time.sleep(.5)
|
||||
app = subprocess.Popen(["wine", str(fixture),
|
||||
*(["Z:" + str(args.prism_dll.resolve()).replace("/", "\\")]
|
||||
if args.prism_dll else [])], env=env)
|
||||
deadline = time.monotonic() + 20
|
||||
while app.poll() is None and time.monotonic() < deadline:
|
||||
while GLib.MainContext.default().iteration(False):
|
||||
pass
|
||||
time.sleep(.005)
|
||||
while GLib.MainContext.default().iteration(False):
|
||||
pass
|
||||
assert app.poll() == 0, f"notification fixture failed: {app.poll()}"
|
||||
assert helper.poll() is None, "helper exited during notifications"
|
||||
assert events == [
|
||||
("SpeakText", ("Menu caf\u00e9 \U0001f600", False)),
|
||||
("SpeakText", ("Second item", True)),
|
||||
("CancelSpeech", ()),
|
||||
("SpeakText", ("x" * 4094, False)),
|
||||
], events
|
||||
print(f"PASS: {32 if args.is32Bit else 64}-bit UIA text, interruption, cancellation, "
|
||||
f"Unicode, bounded text, and background silence through {args.reader}")
|
||||
finally:
|
||||
for child in (app, helper):
|
||||
if child is not None and child.poll() is None:
|
||||
child.terminate()
|
||||
child.wait(timeout=5)
|
||||
subprocess.run(["wineserver", "-k"], env=env, timeout=5)
|
||||
Reference in new issue
Block a user