Harden Wine standard control support
This commit is contained in:
@@ -62,7 +62,7 @@ toolkit, OpenOffice/LibreOffice, Gecko, WebKitGtk, and KDE Qt toolkit.
|
|||||||
- **Simple protocol**: `echo "text" | socat - UNIX-CLIENT:"${XDG_RUNTIME_DIR}/cthulhu.sock"`
|
- **Simple protocol**: `echo "text" | socat - UNIX-CLIENT:"${XDG_RUNTIME_DIR}/cthulhu.sock"`
|
||||||
|
|
||||||
### Wine And Proton
|
### Wine And Proton
|
||||||
- **Standard controls**: Focus, name, value, role, state, and selection changes from MSAA controls
|
- **Standard controls**: MSAA events with Win32 class/message fallbacks for controls Wine leaves empty
|
||||||
- **Unmodified clients**: Implements the NVDA Controller RPC endpoint used by the official controller DLLs
|
- **Unmodified clients**: Implements the NVDA Controller RPC endpoint used by the official controller DLLs
|
||||||
- **Tolk compatibility**: Provides the NVDA detection window expected by the official `Tolk.dll`
|
- **Tolk compatibility**: Provides the NVDA detection window expected by the official `Tolk.dll`
|
||||||
- **Prefix management**: Runs one helper per active same-user Wine or Proton prefix and preserves Proton's loader
|
- **Prefix management**: Runs one helper per active same-user Wine or Proton prefix and preserves Proton's loader
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ The manager starts one helper per prefix, uses `WINEPREFIX` or
|
|||||||
`STEAM_COMPAT_DATA_PATH/pfx`, and passes the prefix's own loader through `WINELOADER`. Discovery
|
`STEAM_COMPAT_DATA_PATH/pfx`, and passes the prefix's own loader through `WINELOADER`. Discovery
|
||||||
requires an actual Wine loader process, so exported prefix variables and idle `wineserver`
|
requires an actual Wine loader process, so exported prefix variables and idle `wineserver`
|
||||||
processes do not keep helpers alive. A helper is stopped after its prefix has been idle for ten seconds. Repeated early exits use exponential
|
processes do not keep helpers alive. A helper is stopped after its prefix has been idle for ten seconds. Repeated early exits use exponential
|
||||||
restart backoff. If an actual NVDA window already exists in the prefix, the helper exits and yields
|
restart backoff. If an actual NVDA window already exists in the prefix, the helper yields the
|
||||||
to it.
|
controller endpoint to it while continuing to provide standard-dialog reading.
|
||||||
|
|
||||||
## Controller Compatibility
|
## Controller Compatibility
|
||||||
|
|
||||||
The helper registers the current `NvdaCtlr.<session>.<desktop>` local RPC endpoint, the legacy
|
The helper registers the current `NvdaCtlr.<session>.<desktop>` local RPC endpoint, the legacy
|
||||||
`NvdaCtlr` endpoint used by older official controller clients, and the Controller 1, 2, and 3
|
`NvdaCtlr` endpoint used by older official controller clients, and the Controller 1, 2, and 3
|
||||||
interface UUIDs. It also creates the `wxWindowClassNR` / `NVDA` window used
|
interface UUIDs. It also creates the `wxWindowClassNR` / `NVDA` window used
|
||||||
by official Tolk releases for detection. Therefore applications can keep the original
|
by official Tolk releases for detection. The compatibility window is explicitly hidden,
|
||||||
|
nonactivating, and excluded from ordinary window switching. Therefore applications can keep the original
|
||||||
`nvdaControllerClient32.dll`, `nvdaControllerClient64.dll`, and `Tolk.dll`; no DLL override is
|
`nvdaControllerClient32.dll`, `nvdaControllerClient64.dll`, and `Tolk.dll`; no DLL override is
|
||||||
required.
|
required.
|
||||||
|
|
||||||
@@ -33,10 +34,16 @@ The current Meson target builds the 64-bit helper. A 32-bit helper requires a 32
|
|||||||
|
|
||||||
## Standard Dialog Reading
|
## Standard Dialog Reading
|
||||||
|
|
||||||
The first reader stage listens for MSAA focus, name, value, state, and selection events. This makes
|
The first reader stage listens for MSAA focus, name, value, state, and selection events. When Wine's
|
||||||
ordinary Win32 dialogs and controls useful. It does not expose custom-drawn, DirectX, or
|
MSAA proxy has no useful data for a standard control, the helper falls back to the control's Win32
|
||||||
game-specific interfaces. UI Automation coverage and an AT-SPI object broker remain separate later
|
class and message protocol. Combo boxes, list boxes, edits, buttons, sliders, and static controls can
|
||||||
stages; the current helper presents normalized events directly through Cthulhu.
|
therefore expose useful names, roles, values, and states without replacing the MSAA path for controls
|
||||||
|
which already read correctly.
|
||||||
|
|
||||||
|
This class-based fallback is adapted from the LGPL `wine-a11y` reader published by Mudb0y.
|
||||||
|
|
||||||
|
The reader does not expose custom-drawn, DirectX, or game-specific interfaces. UI Automation coverage
|
||||||
|
and an AT-SPI object broker remain separate later stages.
|
||||||
|
|
||||||
## Prism
|
## Prism
|
||||||
|
|
||||||
|
|||||||
+279
-31
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "nvdaController.h"
|
#include "nvdaController.h"
|
||||||
|
|
||||||
|
#include <commctrl.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <oleacc.h>
|
#include <oleacc.h>
|
||||||
#include <rpc.h>
|
#include <rpc.h>
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cwchar>
|
#include <cwchar>
|
||||||
|
#include <iterator>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -19,6 +21,16 @@ constexpr auto INTERFACE_NAME = "org.stormux.Cthulhu1.Service";
|
|||||||
constexpr int DBUS_TIMEOUT_MS = 500;
|
constexpr int DBUS_TIMEOUT_MS = 500;
|
||||||
GDBusConnection *connection = nullptr;
|
GDBusConnection *connection = nullptr;
|
||||||
|
|
||||||
|
struct ControlDescription {
|
||||||
|
std::string name;
|
||||||
|
std::string role;
|
||||||
|
std::string value;
|
||||||
|
std::string states;
|
||||||
|
std::string description;
|
||||||
|
int position = 0;
|
||||||
|
int count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::string to_utf8(const wchar_t *text) {
|
std::string to_utf8(const wchar_t *text) {
|
||||||
if (text == nullptr)
|
if (text == nullptr)
|
||||||
return {};
|
return {};
|
||||||
@@ -94,6 +106,201 @@ std::string accessible_property(
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standard-control queries follow the LGPL wine-a11y reader published by
|
||||||
|
// Mudb0y, while presentation remains on Cthulhu's D-Bus interface.
|
||||||
|
void tidy_text(wchar_t *text, bool label = false) {
|
||||||
|
wchar_t *output = text;
|
||||||
|
for (wchar_t *input = text; *input != L'\0'; ++input) {
|
||||||
|
if (*input != L'&')
|
||||||
|
*output++ = *input;
|
||||||
|
}
|
||||||
|
*output = L'\0';
|
||||||
|
if (label && output > text && output[-1] == L':')
|
||||||
|
output[-1] = L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_control_text(HWND window, wchar_t *text, int capacity) {
|
||||||
|
DWORD_PTR result = 0;
|
||||||
|
text[0] = L'\0';
|
||||||
|
if (!SendMessageTimeoutW(window, WM_GETTEXT, capacity,
|
||||||
|
reinterpret_cast<LPARAM>(text), SMTO_ABORTIFHUNG,
|
||||||
|
200, &result))
|
||||||
|
return false;
|
||||||
|
tidy_text(text);
|
||||||
|
return text[0] != L'\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
void find_control_label(HWND window, wchar_t *text, int capacity) {
|
||||||
|
HWND previous = window;
|
||||||
|
wchar_t className[64] = {};
|
||||||
|
text[0] = L'\0';
|
||||||
|
for (int hop = 0; hop < 3; ++hop) {
|
||||||
|
previous = GetWindow(previous, GW_HWNDPREV);
|
||||||
|
if (previous == nullptr ||
|
||||||
|
!GetClassNameW(previous, className, std::size(className)))
|
||||||
|
return;
|
||||||
|
if (wcscmp(className, L"Static") != 0 && wcscmp(className, L"Button") != 0)
|
||||||
|
continue;
|
||||||
|
if (get_control_text(previous, text, capacity))
|
||||||
|
tidy_text(text, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string window_title(HWND window) {
|
||||||
|
wchar_t title[512] = {};
|
||||||
|
GetWindowTextW(GetAncestor(window, GA_ROOT), title, std::size(title));
|
||||||
|
tidy_text(title);
|
||||||
|
return to_utf8(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source_id(HWND window, const char *suffix) {
|
||||||
|
return std::to_string(reinterpret_cast<std::uintptr_t>(window)) + ":" +
|
||||||
|
suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool present_accessible_event(const std::string &sourceId,
|
||||||
|
const char *eventType,
|
||||||
|
const ControlDescription &control,
|
||||||
|
const std::string &windowTitle) {
|
||||||
|
return call_boolean("PresentAccessibleEvent",
|
||||||
|
g_variant_new(
|
||||||
|
"(sssssssiis)", sourceId.c_str(), eventType,
|
||||||
|
control.name.c_str(), control.role.c_str(),
|
||||||
|
control.value.c_str(), control.states.c_str(),
|
||||||
|
control.description.c_str(), control.position,
|
||||||
|
control.count, windowTitle.c_str())) == ERROR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlDescription describe_standard_control(HWND window) {
|
||||||
|
ControlDescription control;
|
||||||
|
wchar_t className[64] = {};
|
||||||
|
wchar_t text[512] = {};
|
||||||
|
wchar_t label[256] = {};
|
||||||
|
if (!GetClassNameW(window, className, std::size(className)))
|
||||||
|
return control;
|
||||||
|
get_control_text(window, text, std::size(text));
|
||||||
|
|
||||||
|
if (wcscmp(className, L"Button") == 0) {
|
||||||
|
control.name = to_utf8(text);
|
||||||
|
const LONG_PTR style = GetWindowLongPtrW(window, GWL_STYLE);
|
||||||
|
DWORD_PTR checked = 0;
|
||||||
|
switch (style & BS_TYPEMASK) {
|
||||||
|
case BS_CHECKBOX:
|
||||||
|
case BS_AUTOCHECKBOX:
|
||||||
|
case BS_3STATE:
|
||||||
|
case BS_AUTO3STATE:
|
||||||
|
control.role = "check box";
|
||||||
|
SendMessageTimeoutW(window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 200,
|
||||||
|
&checked);
|
||||||
|
control.description = checked == BST_CHECKED ? "checked"
|
||||||
|
: checked == BST_INDETERMINATE ? "half checked"
|
||||||
|
: "not checked";
|
||||||
|
break;
|
||||||
|
case BS_RADIOBUTTON:
|
||||||
|
case BS_AUTORADIOBUTTON:
|
||||||
|
control.role = "radio button";
|
||||||
|
SendMessageTimeoutW(window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 200,
|
||||||
|
&checked);
|
||||||
|
control.description = checked == BST_CHECKED ? "checked" : "not checked";
|
||||||
|
break;
|
||||||
|
case BS_GROUPBOX:
|
||||||
|
control.role = "grouping";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
control.role = "button";
|
||||||
|
}
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isEdit = wcscmp(className, L"Edit") == 0 ||
|
||||||
|
wcscmp(className, L"RICHEDIT50W") == 0 ||
|
||||||
|
wcscmp(className, L"RichEdit20W") == 0 ||
|
||||||
|
wcscmp(className, L"RichEdit20A") == 0;
|
||||||
|
if (isEdit) {
|
||||||
|
find_control_label(window, label, std::size(label));
|
||||||
|
control.name = to_utf8(label);
|
||||||
|
const LONG_PTR style = GetWindowLongPtrW(window, GWL_STYLE);
|
||||||
|
control.role = style & ES_READONLY ? "read only edit" : "edit";
|
||||||
|
if (style & ES_PASSWORD)
|
||||||
|
control.description = "password";
|
||||||
|
else
|
||||||
|
control.value = to_utf8(text);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcscmp(className, L"ComboBox") == 0 ||
|
||||||
|
wcscmp(className, L"ComboBoxEx32") == 0) {
|
||||||
|
find_control_label(window, label, std::size(label));
|
||||||
|
control.name = to_utf8(label);
|
||||||
|
control.role = "combo box";
|
||||||
|
control.value = to_utf8(text);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcscmp(className, L"ListBox") == 0 ||
|
||||||
|
wcscmp(className, L"ComboLBox") == 0) {
|
||||||
|
find_control_label(window, label, std::size(label));
|
||||||
|
control.name = to_utf8(label);
|
||||||
|
control.role = "list";
|
||||||
|
DWORD_PTR selected = static_cast<DWORD_PTR>(-1);
|
||||||
|
DWORD_PTR count = 0;
|
||||||
|
DWORD_PTR length = 0;
|
||||||
|
SendMessageTimeoutW(window, LB_GETCOUNT, 0, 0, SMTO_ABORTIFHUNG, 200,
|
||||||
|
&count);
|
||||||
|
SendMessageTimeoutW(window, LB_GETCURSEL, 0, 0, SMTO_ABORTIFHUNG, 200,
|
||||||
|
&selected);
|
||||||
|
const LONG_PTR style = GetWindowLongPtrW(window, GWL_STYLE);
|
||||||
|
const bool stringItems =
|
||||||
|
!(style & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE)) ||
|
||||||
|
(style & LBS_HASSTRINGS);
|
||||||
|
if (static_cast<LONG_PTR>(selected) >= 0) {
|
||||||
|
SendMessageTimeoutW(window, LB_GETTEXTLEN, selected, 0, SMTO_ABORTIFHUNG,
|
||||||
|
200, &length);
|
||||||
|
if (stringItems && length < std::size(text) &&
|
||||||
|
SendMessageTimeoutW(window, LB_GETTEXT, selected,
|
||||||
|
reinterpret_cast<LPARAM>(text), SMTO_ABORTIFHUNG,
|
||||||
|
200, &length)) {
|
||||||
|
tidy_text(text);
|
||||||
|
control.value = to_utf8(text);
|
||||||
|
}
|
||||||
|
control.position = static_cast<int>(selected) + 1;
|
||||||
|
}
|
||||||
|
if (static_cast<LONG_PTR>(count) >= 0)
|
||||||
|
control.count = static_cast<int>(count);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcscmp(className, L"msctls_trackbar32") == 0) {
|
||||||
|
find_control_label(window, label, std::size(label));
|
||||||
|
control.name = to_utf8(label);
|
||||||
|
control.role = "slider";
|
||||||
|
DWORD_PTR position = 0;
|
||||||
|
SendMessageTimeoutW(window, TBM_GETPOS, 0, 0, SMTO_ABORTIFHUNG, 200,
|
||||||
|
&position);
|
||||||
|
control.value = std::to_string(static_cast<LONG>(position));
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcscmp(className, L"Static") == 0) {
|
||||||
|
control.name = to_utf8(text);
|
||||||
|
control.role = "static text";
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
control.name = to_utf8(text);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool present_standard_focus(HWND window) {
|
||||||
|
const ControlDescription control = describe_standard_control(window);
|
||||||
|
if (control.name.empty() && control.role.empty() && control.value.empty() &&
|
||||||
|
control.description.empty())
|
||||||
|
return false;
|
||||||
|
return present_accessible_event(source_id(window, "standard"), "focus",
|
||||||
|
control, window_title(window));
|
||||||
|
}
|
||||||
|
|
||||||
void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||||
LONG objectId, LONG childId, DWORD, DWORD) {
|
LONG objectId, LONG childId, DWORD, DWORD) {
|
||||||
if (event != EVENT_OBJECT_FOCUS && event != EVENT_OBJECT_NAMECHANGE &&
|
if (event != EVENT_OBJECT_FOCUS && event != EVENT_OBJECT_NAMECHANGE &&
|
||||||
@@ -105,8 +312,11 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
|||||||
VariantInit(&child);
|
VariantInit(&child);
|
||||||
if (FAILED(AccessibleObjectFromEvent(window, objectId, childId, &accessible,
|
if (FAILED(AccessibleObjectFromEvent(window, objectId, childId, &accessible,
|
||||||
&child)) ||
|
&child)) ||
|
||||||
accessible == nullptr)
|
accessible == nullptr) {
|
||||||
|
if (event == EVENT_OBJECT_FOCUS)
|
||||||
|
present_standard_focus(window);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const std::string name =
|
const std::string name =
|
||||||
accessible_property(accessible, &IAccessible::get_accName, child);
|
accessible_property(accessible, &IAccessible::get_accName, child);
|
||||||
@@ -131,9 +341,12 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
|||||||
accessible->Release();
|
accessible->Release();
|
||||||
VariantClear(&child);
|
VariantClear(&child);
|
||||||
|
|
||||||
wchar_t title[512] = {};
|
if (event == EVENT_OBJECT_FOCUS && name.empty() && value.empty() &&
|
||||||
GetWindowTextW(GetAncestor(window, GA_ROOT), title, 512);
|
description.empty() && (roleText.empty() || roleText == "client") &&
|
||||||
const std::string windowTitle = to_utf8(title);
|
present_standard_focus(window))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const std::string windowTitle = window_title(window);
|
||||||
const std::string sourceId =
|
const std::string sourceId =
|
||||||
std::to_string(reinterpret_cast<std::uintptr_t>(window)) + ":" +
|
std::to_string(reinterpret_cast<std::uintptr_t>(window)) + ":" +
|
||||||
std::to_string(objectId) + ":" + std::to_string(childId);
|
std::to_string(objectId) + ":" + std::to_string(childId);
|
||||||
@@ -157,12 +370,19 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
|||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
call_boolean("PresentAccessibleEvent",
|
ControlDescription control;
|
||||||
g_variant_new("(sssssssiis)", sourceId.c_str(), eventType,
|
control.name = name;
|
||||||
name.c_str(), roleText.c_str(), value.c_str(),
|
control.role = roleText;
|
||||||
stateText.c_str(), description.c_str(), 0,
|
control.value = value;
|
||||||
static_cast<int>(childCount),
|
control.states = stateText;
|
||||||
windowTitle.c_str()));
|
control.description = description;
|
||||||
|
control.count = static_cast<int>(childCount);
|
||||||
|
present_accessible_event(sourceId, eventType, control, windowTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_rpc_server() {
|
||||||
|
RpcMgmtStopServerListening(nullptr);
|
||||||
|
RpcServerUnregisterIf(nullptr, nullptr, FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool register_rpc_server() {
|
bool register_rpc_server() {
|
||||||
@@ -173,29 +393,40 @@ bool register_rpc_server() {
|
|||||||
nullptr) != RPC_S_OK)
|
nullptr) != RPC_S_OK)
|
||||||
return false;
|
return false;
|
||||||
DWORD sessionId = 0;
|
DWORD sessionId = 0;
|
||||||
if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId))
|
if (!ProcessIdToSessionId(GetCurrentProcessId(), &sessionId)) {
|
||||||
|
reset_rpc_server();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
wchar_t desktopName[256] = {};
|
wchar_t desktopName[256] = {};
|
||||||
DWORD bytesNeeded = 0;
|
DWORD bytesNeeded = 0;
|
||||||
if (!GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()),
|
if (!GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()),
|
||||||
UOI_NAME, desktopName, sizeof(desktopName),
|
UOI_NAME, desktopName, sizeof(desktopName),
|
||||||
&bytesNeeded))
|
&bytesNeeded)) {
|
||||||
|
reset_rpc_server();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
wchar_t endpoint[512] = {};
|
wchar_t endpoint[512] = {};
|
||||||
wsprintfW(endpoint, L"NvdaCtlr.%lu.%s", sessionId, desktopName);
|
wsprintfW(endpoint, L"NvdaCtlr.%lu.%s", sessionId, desktopName);
|
||||||
if (RpcServerUseProtseqEpW(
|
if (RpcServerUseProtseqEpW(
|
||||||
reinterpret_cast<RPC_WSTR>(const_cast<wchar_t *>(L"ncalrpc")),
|
reinterpret_cast<RPC_WSTR>(const_cast<wchar_t *>(L"ncalrpc")),
|
||||||
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
|
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, reinterpret_cast<RPC_WSTR>(endpoint),
|
||||||
reinterpret_cast<RPC_WSTR>(endpoint),
|
nullptr) != RPC_S_OK) {
|
||||||
nullptr) != RPC_S_OK)
|
reset_rpc_server();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
for (RPC_IF_HANDLE spec :
|
for (RPC_IF_HANDLE spec :
|
||||||
{NvdaController_v1_0_s_ifspec, NvdaController2_v1_0_s_ifspec,
|
{NvdaController_v1_0_s_ifspec, NvdaController2_v1_0_s_ifspec,
|
||||||
NvdaController3_v1_0_s_ifspec}) {
|
NvdaController3_v1_0_s_ifspec}) {
|
||||||
if (RpcServerRegisterIf(spec, nullptr, nullptr) != RPC_S_OK)
|
if (RpcServerRegisterIf(spec, nullptr, nullptr) != RPC_S_OK) {
|
||||||
|
reset_rpc_server();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE) == RPC_S_OK;
|
if (RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, TRUE) == RPC_S_OK)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
reset_rpc_server();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -260,35 +491,51 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
|||||||
wcsstr(commandLine, L"--no-controller") == nullptr;
|
wcsstr(commandLine, L"--no-controller") == nullptr;
|
||||||
if (!dialogReaderEnabled && !controllerEnabled)
|
if (!dialogReaderEnabled && !controllerEnabled)
|
||||||
return 0;
|
return 0;
|
||||||
if (controllerEnabled && FindWindowW(L"wxWindowClassNR", L"NVDA") != nullptr)
|
|
||||||
|
HANDLE singleton =
|
||||||
|
CreateMutexW(nullptr, TRUE, L"Local\\cthulhu-wine-access-singleton");
|
||||||
|
if (singleton == nullptr)
|
||||||
|
return 1;
|
||||||
|
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||||
|
CloseHandle(singleton);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
GError *error = nullptr;
|
GError *error = nullptr;
|
||||||
connection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
|
connection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
|
||||||
if (error != nullptr)
|
if (error != nullptr)
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
if (connection == nullptr || !cthulhu_is_available())
|
if (connection == nullptr || !cthulhu_is_available()) {
|
||||||
|
CloseHandle(singleton);
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
HWND window = nullptr;
|
HWND window = nullptr;
|
||||||
if (controllerEnabled) {
|
bool controllerActive = false;
|
||||||
|
if (controllerEnabled &&
|
||||||
|
FindWindowW(L"wxWindowClassNR", L"NVDA") == nullptr) {
|
||||||
WNDCLASSW windowClass{};
|
WNDCLASSW windowClass{};
|
||||||
windowClass.lpfnWndProc = DefWindowProcW;
|
windowClass.lpfnWndProc = DefWindowProcW;
|
||||||
windowClass.hInstance = instance;
|
windowClass.hInstance = instance;
|
||||||
windowClass.lpszClassName = L"wxWindowClassNR";
|
windowClass.lpszClassName = L"wxWindowClassNR";
|
||||||
RegisterClassW(&windowClass);
|
RegisterClassW(&windowClass);
|
||||||
window = CreateWindowExW(WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW,
|
window = CreateWindowExW(WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW,
|
||||||
windowClass.lpszClassName, L"NVDA", 0, 0, 0, 0,
|
windowClass.lpszClassName, L"NVDA", 0, 0, 0, 0, 0,
|
||||||
0, nullptr, nullptr, instance, nullptr);
|
nullptr, nullptr, instance, nullptr);
|
||||||
|
if (window != nullptr) {
|
||||||
|
ShowWindow(window, SW_HIDE);
|
||||||
|
controllerActive = register_rpc_server();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((controllerEnabled && (window == nullptr || !register_rpc_server()))) {
|
if (!controllerActive && window != nullptr) {
|
||||||
g_object_unref(connection);
|
DestroyWindow(window);
|
||||||
return 1;
|
window = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
HWINEVENTHOOK hook = nullptr;
|
HWINEVENTHOOK hook = nullptr;
|
||||||
if (dialogReaderEnabled) {
|
if (dialogReaderEnabled)
|
||||||
hook = SetWinEventHook(EVENT_MIN, EVENT_MAX, nullptr, winevent_callback, 0,
|
hook = SetWinEventHook(EVENT_MIN, EVENT_MAX, nullptr, winevent_callback, 0,
|
||||||
0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
||||||
}
|
|
||||||
MSG message{};
|
MSG message{};
|
||||||
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
||||||
TranslateMessage(&message);
|
TranslateMessage(&message);
|
||||||
@@ -296,11 +543,12 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
|||||||
}
|
}
|
||||||
if (hook != nullptr)
|
if (hook != nullptr)
|
||||||
UnhookWinEvent(hook);
|
UnhookWinEvent(hook);
|
||||||
if (controllerEnabled) {
|
if (controllerActive) {
|
||||||
RpcMgmtStopServerListening(nullptr);
|
reset_rpc_server();
|
||||||
RpcServerUnregisterIf(nullptr, nullptr, FALSE);
|
|
||||||
DestroyWindow(window);
|
|
||||||
}
|
}
|
||||||
|
if (window != nullptr)
|
||||||
|
DestroyWindow(window);
|
||||||
g_object_unref(connection);
|
g_object_unref(connection);
|
||||||
|
CloseHandle(singleton);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user