Make the process of adding an orca extension easier.
This commit is contained in:
1 parent
a8118c12cf
commit
417ee40dda
6 files changed
+199
-35
No files matched your search
@@ -123,6 +123,30 @@ WINEPREFIX=/path/to/prefix /path/to/cthulhu-wine-access.exe --reader orca
|
||||
| Cthulhu (default) | `org.stormux.Cthulhu1.Service` | `/org/stormux/Cthulhu1/Service` |
|
||||
| Orca | `org.gnome.Orca1.Service` | `/org/gnome/Orca1/Service` |
|
||||
|
||||
An Orca user extension can expose the Wine methods on its own object and
|
||||
interface, without adding them to Orca's core service. Override either field
|
||||
with `--reader-path` and `--reader-interface`; omitted fields retain the
|
||||
selected reader's defaults. For an extension class named `WineAccess`:
|
||||
|
||||
```sh
|
||||
WINEPREFIX=/path/to/prefix /path/to/cthulhu-wine-access.exe --reader orca \
|
||||
--reader-path /org/gnome/Orca1/Service/WineAccess \
|
||||
--reader-interface org.gnome.Orca1.WineAccess
|
||||
```
|
||||
|
||||
Both overrides accept validated D-Bus values and apply regardless of their
|
||||
position relative to `--reader`. The bus name stays that of the selected
|
||||
reader; overrides do not grant review access to another connection.
|
||||
|
||||
Before starting its hooks or controller server, the helper introspects the
|
||||
selected object and interface and checks the required method signatures for
|
||||
the enabled capabilities. A missing extension or incompatible interface
|
||||
causes startup to fail with a diagnostic. Controller support requires
|
||||
`SpeakText`, `CancelSpeech`, `BrailleMessage` and `SpeakSsml`; control reading
|
||||
requires `PresentAccessibleEvent`. Review methods remain optional. The
|
||||
interface must be published before launching the helper. This check runs at
|
||||
startup; later request failures still follow the ordinary error handling.
|
||||
|
||||
Selection applies to controller requests, accessibility events, review
|
||||
registration and review-capture authorization. Only one helper runs per Wine
|
||||
desktop/prefix, so stop the existing helper before changing readers. There is
|
||||
|
||||
@@ -88,18 +88,6 @@ HWND combo_event_control(HWND window, DWORD event) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Winegcc uses 16-bit wchar_t; Linux libc's wcs* functions expect 32-bit
|
||||
// wchar_t. Use Wine's Unicode APIs or explicit code-unit comparisons.
|
||||
bool wide_contains(const wchar_t *text, const wchar_t *needle) {
|
||||
for (; *text != L'\0'; ++text) {
|
||||
const wchar_t *left = text;
|
||||
const wchar_t *right = needle;
|
||||
while (*right != L'\0' && *left == *right) { ++left; ++right; }
|
||||
if (*right == L'\0') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ControlDescription {
|
||||
std::string name;
|
||||
std::string role;
|
||||
@@ -162,6 +150,59 @@ bool reader_is_available() {
|
||||
return owned == TRUE;
|
||||
}
|
||||
|
||||
bool reader_supports_protocol(bool controllerEnabled, bool dialogReaderEnabled) {
|
||||
GError *error = nullptr;
|
||||
GVariant *reply = g_dbus_connection_call_sync(
|
||||
connection, readerEndpoint.busName, readerEndpoint.objectPath,
|
||||
"org.freedesktop.DBus.Introspectable", "Introspect", nullptr,
|
||||
G_VARIANT_TYPE("(s)"), G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT_MS, nullptr,
|
||||
&error);
|
||||
if (error) g_error_free(error);
|
||||
if (!reply) {
|
||||
std::fprintf(stderr, "wine-access: reader object %s is unavailable\n",
|
||||
readerEndpoint.objectPath);
|
||||
return false;
|
||||
}
|
||||
const char *xml = nullptr;
|
||||
g_variant_get(reply, "(&s)", &xml);
|
||||
GDBusNodeInfo *node = g_dbus_node_info_new_for_xml(xml, nullptr);
|
||||
g_variant_unref(reply);
|
||||
GDBusInterfaceInfo *interfaceInfo = node ?
|
||||
g_dbus_node_info_lookup_interface(node, readerEndpoint.interfaceName) : nullptr;
|
||||
struct RequiredMethod {
|
||||
const char *name;
|
||||
const char *inputs;
|
||||
const char *outputs;
|
||||
bool required;
|
||||
};
|
||||
const RequiredMethod methods[] = {
|
||||
{"SpeakText", "sb", "b", controllerEnabled},
|
||||
{"CancelSpeech", "", "b", controllerEnabled},
|
||||
{"BrailleMessage", "s", "b", controllerEnabled},
|
||||
{"SpeakSsml", "ss", "i", controllerEnabled},
|
||||
{"PresentAccessibleEvent", "sssssssiis", "b", dialogReaderEnabled}};
|
||||
bool supported = interfaceInfo != nullptr;
|
||||
if (!interfaceInfo)
|
||||
std::fprintf(stderr, "wine-access: reader interface %s is unavailable\n",
|
||||
readerEndpoint.interfaceName);
|
||||
for (const auto &required : methods) {
|
||||
if (!interfaceInfo || !required.required) continue;
|
||||
GDBusMethodInfo *method = g_dbus_interface_info_lookup_method(interfaceInfo, required.name);
|
||||
std::string inputs, outputs;
|
||||
if (method) {
|
||||
for (auto args = method->in_args; args && *args; ++args) inputs += (*args)->signature;
|
||||
for (auto args = method->out_args; args && *args; ++args) outputs += (*args)->signature;
|
||||
}
|
||||
if (!method || inputs != required.inputs || outputs != required.outputs) {
|
||||
std::fprintf(stderr, "wine-access: reader method %s requires %s -> %s\n",
|
||||
required.name, required.inputs, required.outputs);
|
||||
supported = false;
|
||||
}
|
||||
}
|
||||
if (node) g_dbus_node_info_unref(node);
|
||||
return supported;
|
||||
}
|
||||
|
||||
// Standard-control queries follow the LGPL wine-a11y reader published by
|
||||
// Mudb0y, while presentation uses the selected reader's D-Bus interface.
|
||||
void strip_mnemonics(wchar_t *text) {
|
||||
@@ -1008,7 +1049,32 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
wchar_t **arguments = CommandLineToArgvW(commandLine, &argumentCount);
|
||||
if (arguments == nullptr) return 1;
|
||||
bool validReader = true;
|
||||
bool validEndpoint = true;
|
||||
std::string readerPath, readerInterface;
|
||||
bool dialogReaderEnabled = true, controllerEnabled = true;
|
||||
for (int index = 1; index < argumentCount; ++index) {
|
||||
if (lstrcmpW(arguments[index], L"--no-dialog-reader") == 0) {
|
||||
dialogReaderEnabled = false;
|
||||
continue;
|
||||
}
|
||||
if (lstrcmpW(arguments[index], L"--no-controller") == 0) {
|
||||
controllerEnabled = false;
|
||||
continue;
|
||||
}
|
||||
const bool pathOption = lstrcmpW(arguments[index], L"--reader-path") == 0;
|
||||
const bool interfaceOption = lstrcmpW(arguments[index], L"--reader-interface") == 0;
|
||||
if (pathOption || interfaceOption) {
|
||||
if (++index == argumentCount) { validEndpoint = false; break; }
|
||||
std::string value = to_utf8(arguments[index]);
|
||||
if (pathOption ? !g_variant_is_object_path(value.c_str()) :
|
||||
!g_dbus_is_interface_name(value.c_str())) {
|
||||
validEndpoint = false;
|
||||
break;
|
||||
}
|
||||
if (pathOption) readerPath = value;
|
||||
else readerInterface = value;
|
||||
continue;
|
||||
}
|
||||
if (lstrcmpW(arguments[index], L"--reader") != 0) continue;
|
||||
if (++index == argumentCount) { validReader = false; break; }
|
||||
if (lstrcmpW(arguments[index], L"cthulhu") == 0)
|
||||
@@ -1022,10 +1088,14 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
std::fprintf(stderr, "wine-access: --reader requires cthulhu or orca\n");
|
||||
return 1;
|
||||
}
|
||||
const bool dialogReaderEnabled =
|
||||
!wide_contains(commandLine, L"--no-dialog-reader");
|
||||
const bool controllerEnabled =
|
||||
!wide_contains(commandLine, L"--no-controller");
|
||||
if (!validEndpoint) {
|
||||
std::fprintf(stderr, "wine-access: --reader-path and --reader-interface require valid D-Bus values\n");
|
||||
return 1;
|
||||
}
|
||||
// Apply overrides after reader selection, independent of argument order.
|
||||
// These strings remain alive until the workers have stopped at shutdown.
|
||||
if (!readerPath.empty()) readerEndpoint.objectPath = readerPath.c_str();
|
||||
if (!readerInterface.empty()) readerEndpoint.interfaceName = readerInterface.c_str();
|
||||
if (!dialogReaderEnabled && !controllerEnabled)
|
||||
return 0;
|
||||
|
||||
@@ -1050,6 +1120,12 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!reader_supports_protocol(controllerEnabled, dialogReaderEnabled)) {
|
||||
g_object_unref(connection);
|
||||
CloseHandle(singleton);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Application-owned MSAA objects (including Windows Forms menus) are
|
||||
// marshaled across processes. Initialize their callback/message-loop thread;
|
||||
// Wine's built-in control proxies can otherwise hide this missing setup.
|
||||
|
||||
@@ -102,12 +102,29 @@ The controller test drives the helper through a native Windows NVDA RPC client.
|
||||
It verifies speech, cancellation, braille, SSML status forwarding, reader loss,
|
||||
invalid reader values, and startup refusal when only the unselected service
|
||||
exists. Both service names are owned while the selected reader handles requests.
|
||||
It also rejects invalid endpoint overrides, missing objects/interfaces,
|
||||
missing required methods and incorrect input/output signatures.
|
||||
The menu/editor and review tests also accept `--reader cthulhu`; omit the option
|
||||
to check existing default behavior. Review checks reject callers even when they
|
||||
own the unselected service, then replace the selected service owner and verify
|
||||
registration and capture access move to the new owner. Its provider object path
|
||||
and interface remain the same for both readers.
|
||||
|
||||
To check an Orca extension endpoint, add `--extension-endpoint`:
|
||||
|
||||
```sh
|
||||
xvfb-run -a python3 tests/controller_integration.py --build-dir build --reader orca --extension-endpoint
|
||||
xvfb-run -a python3 tests/menu_integration.py --build-dir build --reader orca --extension-endpoint
|
||||
xvfb-run -a python3 tests/menu_integration.py --build-dir build --reader orca --extension-endpoint --fixture edit
|
||||
xvfb-run -a python3 tests/review_integration.py --build-dir build --reader orca --extension-endpoint
|
||||
```
|
||||
|
||||
These publish `/org/gnome/Orca1/Service/WineAccess` with interface
|
||||
`org.gnome.Orca1.WineAccess`, while retaining the `org.gnome.Orca1.Service`
|
||||
bus name. Controller checks put overrides before reader selection; control
|
||||
and review checks put them after it. Review checks still reject unrelated
|
||||
callers and transfer capture access only when the selected bus owner changes.
|
||||
|
||||
These tests supply recording services implementing the Wine protocol; they do
|
||||
not run Orca or prove audible speech or physical braille. Stock Orca still needs
|
||||
the reader-side methods and helper lifecycle described in the
|
||||
|
||||
@@ -12,17 +12,21 @@ from gi.repository import Gio, GLib
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--build-dir", default="build")
|
||||
parser.add_argument("--reader", choices=("cthulhu", "orca"))
|
||||
parser.add_argument("--extension-endpoint", action="store_true")
|
||||
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),
|
||||
*(["--extension-endpoint"] if args.extension_endpoint else []),
|
||||
*(["--reader", args.reader] if args.reader else [])]))
|
||||
|
||||
interface = "org.gnome.Orca1.Service" if args.reader == "orca" else "org.stormux.Cthulhu1.Service"
|
||||
busName = "org.gnome.Orca1.Service" if args.reader == "orca" else "org.stormux.Cthulhu1.Service"
|
||||
interface = busName.rsplit(".", 1)[0] + ".WineAccess" if args.extension_endpoint else busName
|
||||
otherInterface = "org.stormux.Cthulhu1.Service" if args.reader == "orca" else "org.gnome.Orca1.Service"
|
||||
objectPath = "/" + interface.replace(".", "/")
|
||||
objectPath = "/" + busName.replace(".", "/") + ("/WineAccess" if args.extension_endpoint else "")
|
||||
endpointArguments = ["--reader-path", objectPath, "--reader-interface", interface] if args.extension_endpoint else []
|
||||
methods = {"SpeakText": ("sb", "b"), "CancelSpeech": ("", "b"),
|
||||
"BrailleMessage": ("s", "b"), "SpeakSsml": ("ss", "i")}
|
||||
xml = '<node><interface name="' + interface + '">'
|
||||
@@ -41,7 +45,7 @@ def record(connection, sender, path, iface, method, parameters, invocation):
|
||||
|
||||
|
||||
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
connection.register_object(objectPath, Gio.DBusNodeInfo.new_for_xml(xml).interfaces[0], record, None, None)
|
||||
registration = connection.register_object(objectPath, Gio.DBusNodeInfo.new_for_xml(xml).interfaces[0], record, None, None)
|
||||
|
||||
|
||||
def name_call(method, name):
|
||||
@@ -51,13 +55,13 @@ def name_call(method, name):
|
||||
Gio.DBusCallFlags.NONE, 1000, None).unpack()[0]
|
||||
|
||||
|
||||
def wait_for_client(client):
|
||||
def wait_for_client(client, expected=0):
|
||||
deadline = time.monotonic() + 10
|
||||
while client.poll() is None and time.monotonic() < deadline:
|
||||
while GLib.MainContext.default().iteration(False):
|
||||
pass
|
||||
time.sleep(.005)
|
||||
assert client.poll() == 0, f"controller client failed or timed out: {client.poll()}"
|
||||
assert client.poll() == expected, f"client failed or timed out: {client.poll()}"
|
||||
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="wine-access-controller-") as temporary:
|
||||
@@ -80,6 +84,7 @@ with tempfile.TemporaryDirectory(prefix="wine-access-controller-") as temporary:
|
||||
helper = None
|
||||
client = None
|
||||
helperCommand = [str(build / "cthulhu-wine-access.exe"), "--no-dialog-reader",
|
||||
*endpointArguments,
|
||||
*(["--reader", args.reader] if args.reader else [])]
|
||||
try:
|
||||
subprocess.run(["wineboot", "-u"], env=env, check=True, timeout=45)
|
||||
@@ -87,11 +92,43 @@ with tempfile.TemporaryDirectory(prefix="wine-access-controller-") as temporary:
|
||||
assert name_call("RequestName", otherInterface) == 1
|
||||
result = subprocess.run(helperCommand, env=env, timeout=5)
|
||||
assert result.returncode == 1, "helper accepted an unselected reader"
|
||||
for readerArgs in (["--reader"], ["--reader", "unknown"]):
|
||||
for readerArgs in (["--reader"], ["--reader", "unknown"],
|
||||
["--reader-path"], ["--reader-path", ""],
|
||||
["--reader-path", "relative/path"], ["--reader-path", "/bad-path"],
|
||||
["--reader-interface"], ["--reader-interface", ""],
|
||||
["--reader-interface", "WineAccess"],
|
||||
["--reader-interface", "org.example.Bad-Interface"]):
|
||||
result = subprocess.run([str(build / "cthulhu-wine-access.exe"), *readerArgs],
|
||||
env=env, timeout=5)
|
||||
assert result.returncode == 1, "invalid reader argument was accepted"
|
||||
assert name_call("RequestName", interface) == 1
|
||||
assert result.returncode == 1, "invalid endpoint argument was accepted"
|
||||
assert name_call("RequestName", busName) == 1
|
||||
# An owned reader name is insufficient: refuse missing objects,
|
||||
# interfaces, methods and incompatible signatures before starting RPC.
|
||||
def reject(command):
|
||||
rejected = subprocess.Popen(command, env=env, stderr=subprocess.PIPE)
|
||||
try:
|
||||
wait_for_client(rejected, 1)
|
||||
return rejected.communicate()[1].decode()
|
||||
finally:
|
||||
if rejected.poll() is None:
|
||||
rejected.kill()
|
||||
rejected.wait()
|
||||
diagnostic = reject([*helperCommand, "--reader-path", "/Missing"])
|
||||
# Gio can introspect an absent object as an empty node.
|
||||
assert "reader object" in diagnostic or "reader interface" in diagnostic, diagnostic
|
||||
assert "reader interface" in reject([*helperCommand, "--reader-interface", "org.example.Missing"])
|
||||
connection.unregister_object(registration)
|
||||
for brokenXml in (
|
||||
xml.replace('<method name="CancelSpeech"><arg type="b" direction="out"/></method>', ''),
|
||||
xml.replace('<arg type="i" direction="out"/>', '<arg type="b" direction="out"/>'),
|
||||
xml.replace('<arg type="b" direction="in"/>', '<arg type="s" direction="in"/>')):
|
||||
broken = connection.register_object(objectPath, Gio.DBusNodeInfo.new_for_xml(brokenXml).interfaces[0], record, None, None)
|
||||
assert "reader method" in reject(helperCommand)
|
||||
connection.unregister_object(broken)
|
||||
registration = connection.register_object(objectPath, Gio.DBusNodeInfo.new_for_xml(xml).interfaces[0], record, None, None)
|
||||
# Controller-only interfaces must not enable control reading.
|
||||
assert "PresentAccessibleEvent" in reject([part for part in helperCommand if part != "--no-dialog-reader"])
|
||||
print("PASS: endpoint validation and required method signatures")
|
||||
helper = subprocess.Popen(helperCommand, env=env)
|
||||
# The fixture waits for the RPC server to listen before making calls.
|
||||
client = subprocess.Popen(["wine", str(clientPath)], env=env)
|
||||
@@ -103,7 +140,7 @@ with tempfile.TemporaryDirectory(prefix="wine-access-controller-") as temporary:
|
||||
("BrailleMessage", ("Controller braille",)),
|
||||
("SpeakSsml", ("wine-nvda-controller", "<speak>Controller SSML</speak>")),
|
||||
], events
|
||||
assert name_call("ReleaseName", interface) == 1
|
||||
assert name_call("ReleaseName", busName) == 1
|
||||
client = subprocess.Popen(["wine", str(clientPath), "unavailable"], env=env)
|
||||
wait_for_client(client)
|
||||
print(f"PASS: {interface} controller routing, SSML status, service loss, and reader validation")
|
||||
|
||||
@@ -18,6 +18,7 @@ parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--build-dir", default="build")
|
||||
parser.add_argument("--session", action="store_true")
|
||||
parser.add_argument("--reader", choices=("cthulhu", "orca"))
|
||||
parser.add_argument("--extension-endpoint", action="store_true")
|
||||
parser.add_argument("--fixture", choices=("menu", "edit", "notepad", "list", "listbox", "delphi", "winforms", "controls", "scintilla", "goldwave"), default="menu")
|
||||
parser.add_argument("--notepadpp-exe", type=Path)
|
||||
parser.add_argument("--goldwave-exe", type=Path)
|
||||
@@ -38,6 +39,7 @@ if not args.session:
|
||||
sys.exit(subprocess.call(["dbus-run-session", "--", sys.executable,
|
||||
__file__, "--session", "--build-dir", str(build),
|
||||
"--fixture", args.fixture,
|
||||
*(["--extension-endpoint"] if args.extension_endpoint else []),
|
||||
*(["--reader", args.reader] if args.reader else []),
|
||||
*(["--notepadpp-exe", str(args.notepadpp_exe.resolve())] if args.notepadpp_exe else []),
|
||||
*(["--goldwave-exe", str(args.goldwave_exe.resolve())] if args.goldwave_exe else []),
|
||||
@@ -45,9 +47,12 @@ if not args.session:
|
||||
*(["--late-helper"] if args.late_helper else []),
|
||||
*(["--restart-helper"] if args.restart_helper else [])]))
|
||||
|
||||
interface = "org.gnome.Orca1.Service" if args.reader == "orca" else "org.stormux.Cthulhu1.Service"
|
||||
object_path = "/" + interface.replace(".", "/")
|
||||
busName = "org.gnome.Orca1.Service" if args.reader == "orca" else "org.stormux.Cthulhu1.Service"
|
||||
interface = busName.rsplit(".", 1)[0] + ".WineAccess" if args.extension_endpoint else busName
|
||||
object_path = "/" + busName.replace(".", "/") + ("/WineAccess" if args.extension_endpoint else "")
|
||||
readerArguments = ["--reader", args.reader] if args.reader else []
|
||||
if args.extension_endpoint:
|
||||
readerArguments += ["--reader-path", object_path, "--reader-interface", interface]
|
||||
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"/>'
|
||||
@@ -62,7 +67,7 @@ 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,
|
||||
GLib.Variant("(su)", (busName, 0)), None,
|
||||
Gio.DBusCallFlags.NONE, 1000, None)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
||||
|
||||
@@ -13,16 +13,21 @@ parser.add_argument('--build-dir', default='build')
|
||||
parser.add_argument('--32-bit', dest='is32', action='store_true')
|
||||
parser.add_argument('--session', action='store_true')
|
||||
parser.add_argument('--reader', choices=('cthulhu', 'orca'))
|
||||
parser.add_argument('--extension-endpoint', 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),
|
||||
*(['--extension-endpoint'] if args.extension_endpoint else []),
|
||||
*(['--reader', args.reader] if args.reader else []),
|
||||
*(['--32-bit'] if args.is32 else [])]))
|
||||
iface = 'org.gnome.Orca1.Service' if args.reader == 'orca' else 'org.stormux.Cthulhu1.Service'
|
||||
path = '/' + iface.replace('.', '/')
|
||||
busName = 'org.gnome.Orca1.Service' if args.reader == 'orca' else 'org.stormux.Cthulhu1.Service'
|
||||
iface = busName.rsplit('.', 1)[0] + '.WineAccess' if args.extension_endpoint else busName
|
||||
path = '/' + busName.replace('.', '/') + ('/WineAccess' if args.extension_endpoint else '')
|
||||
readerArguments = ['--reader', args.reader] if args.reader else []
|
||||
if args.extension_endpoint:
|
||||
readerArguments += ['--reader-path', path, '--reader-interface', iface]
|
||||
capture_iface = 'org.stormux.Cthulhu1.WineReview'
|
||||
capture_path = '/org/stormux/Cthulhu1/WineReview'
|
||||
|
||||
@@ -50,7 +55,7 @@ def record(conn, sender, object_path, interface, method, params, invocation):
|
||||
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
registration = connection.register_object(path, Gio.DBusNodeInfo.new_for_xml(interface_xml()).interfaces[0], record, None, None)
|
||||
connection.call_sync('org.freedesktop.DBus','/org/freedesktop/DBus','org.freedesktop.DBus','RequestName',
|
||||
GLib.Variant('(su)',(iface,0)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
GLib.Variant('(su)',(busName,0)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
|
||||
def pump_until(predicate, seconds=5):
|
||||
end = time.monotonic()+seconds
|
||||
@@ -127,9 +132,9 @@ with tempfile.TemporaryDirectory(prefix='wine-review-wire-') as temporary:
|
||||
other.register_object(path, Gio.DBusNodeInfo.new_for_xml(interface_xml()).interfaces[0],record,None,None)
|
||||
providers.clear(); focuses.clear()
|
||||
connection.call_sync('org.freedesktop.DBus','/org/freedesktop/DBus','org.freedesktop.DBus','ReleaseName',
|
||||
GLib.Variant('(s)',(iface,)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
GLib.Variant('(s)',(busName,)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
other.call_sync('org.freedesktop.DBus','/org/freedesktop/DBus','org.freedesktop.DBus','RequestName',
|
||||
GLib.Variant('(su)',(iface,0)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
GLib.Variant('(su)',(busName,0)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
pump_until(lambda:sender in providers and sender in focuses)
|
||||
focus=focuses[sender]
|
||||
wrong=request(connection,sender,focus); pump_until(lambda:bool(wrong),2)
|
||||
@@ -139,9 +144,9 @@ with tempfile.TemporaryDirectory(prefix='wine-review-wire-') as temporary:
|
||||
assert accepted[0][0].unpack()[0]['status']=='ok'
|
||||
providers.clear(); focuses.clear()
|
||||
other.call_sync('org.freedesktop.DBus','/org/freedesktop/DBus','org.freedesktop.DBus','ReleaseName',
|
||||
GLib.Variant('(s)',(iface,)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
GLib.Variant('(s)',(busName,)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
connection.call_sync('org.freedesktop.DBus','/org/freedesktop/DBus','org.freedesktop.DBus','RequestName',
|
||||
GLib.Variant('(su)',(iface,0)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
GLib.Variant('(su)',(busName,0)),None,Gio.DBusCallFlags.NONE,1000,None)
|
||||
pump_until(lambda:sender in providers and sender in focuses)
|
||||
other.close_sync(None)
|
||||
print('PASS: test_reader_owner_replacement')
|
||||
|
||||
Reference in new issue
Block a user