More fixes for menus and combo boxes.
This commit is contained in:
1 parent
67ebc28d15
commit
872388a59b
11 files changed
+392
-35
No files matched your search
@@ -41,6 +41,30 @@ bool lastListSelected = false;
|
||||
int lastListChecked = -1;
|
||||
HWND lastNativeControl = nullptr;
|
||||
NativeItemKey lastNativeItem;
|
||||
HWND lastComboControl = nullptr;
|
||||
std::string lastComboValue;
|
||||
|
||||
bool is_combo_box(HWND window) {
|
||||
wchar_t className[128] = {};
|
||||
return GetClassNameW(window, className, std::size(className)) &&
|
||||
(wine_access_class_matches(className, L"ComboBox") ||
|
||||
wine_access_class_matches(className, L"ComboBoxEx32"));
|
||||
}
|
||||
|
||||
HWND combo_event_control(HWND window, DWORD event) {
|
||||
if (is_combo_box(window)) return window;
|
||||
wchar_t className[128] = {};
|
||||
const bool comboList = GetClassNameW(window, className, std::size(className)) &&
|
||||
wine_access_class_matches(className, L"ComboLBox");
|
||||
if (event != EVENT_OBJECT_FOCUS && !comboList) return nullptr;
|
||||
GUITHREADINFO info{};
|
||||
info.cbSize = sizeof(info);
|
||||
HWND foreground = GetForegroundWindow();
|
||||
if (foreground && GetGUIThreadInfo(GetWindowThreadProcessId(foreground, nullptr), &info) &&
|
||||
is_combo_box(info.hwndFocus))
|
||||
return info.hwndFocus;
|
||||
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.
|
||||
@@ -321,6 +345,8 @@ ControlDescription describe_standard_control(HWND window) {
|
||||
control.name = to_utf8(label);
|
||||
control.role = "combo box";
|
||||
control.value = to_utf8(text);
|
||||
lastComboControl = window;
|
||||
lastComboValue = control.value;
|
||||
return control;
|
||||
}
|
||||
|
||||
@@ -427,8 +453,14 @@ void poll_control_focus(bool refresh = false) {
|
||||
GUITHREADINFO info{};
|
||||
info.cbSize = sizeof(info);
|
||||
HWND foreground = GetForegroundWindow();
|
||||
if (foreground && GetGUIThreadInfo(GetWindowThreadProcessId(foreground, nullptr), &info) &&
|
||||
!(info.flags & GUI_INMENUMODE) && is_native_item_control(info.hwndFocus)) {
|
||||
const bool focused = foreground &&
|
||||
GetGUIThreadInfo(GetWindowThreadProcessId(foreground, nullptr), &info) &&
|
||||
!(info.flags & GUI_INMENUMODE);
|
||||
if (!focused || !is_combo_box(info.hwndFocus)) {
|
||||
lastComboControl = nullptr;
|
||||
lastComboValue.clear();
|
||||
}
|
||||
if (focused && is_native_item_control(info.hwndFocus)) {
|
||||
lastListControl = nullptr;
|
||||
lastListIndex = -1;
|
||||
lastListCount = 0;
|
||||
@@ -446,8 +478,7 @@ void poll_control_focus(bool refresh = false) {
|
||||
}
|
||||
lastNativeControl = nullptr;
|
||||
lastNativeItem = {};
|
||||
if (foreground && GetGUIThreadInfo(GetWindowThreadProcessId(foreground, nullptr), &info) &&
|
||||
!(info.flags & GUI_INMENUMODE) && is_list_view(info.hwndFocus)) {
|
||||
if (focused && is_list_view(info.hwndFocus)) {
|
||||
int count = 0;
|
||||
const int index = list_view_index(info.hwndFocus, &count);
|
||||
DWORD_PTR state = 0;
|
||||
@@ -471,6 +502,19 @@ void poll_control_focus(bool refresh = false) {
|
||||
lastListCount = 0;
|
||||
lastListSelected = false;
|
||||
lastListChecked = -1;
|
||||
if (focused && is_combo_box(info.hwndFocus)) {
|
||||
if (info.hwndFocus != lastComboControl) {
|
||||
present_standard_focus(info.hwndFocus);
|
||||
} else {
|
||||
wchar_t text[512] = {};
|
||||
get_control_text(info.hwndFocus, text, std::size(text));
|
||||
if (refresh || to_utf8(text) != lastComboValue) {
|
||||
const auto control = describe_standard_control(info.hwndFocus);
|
||||
present_accessible_event(source_id(info.hwndFocus, "standard"), "value",
|
||||
control, window_title(info.hwndFocus));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void present_menu_event(const WineAccessMenuEvent &event) {
|
||||
@@ -625,21 +669,43 @@ LRESULT CALLBACK menu_bridge_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
||||
return DefWindowProcW(window, message, wp, lp);
|
||||
}
|
||||
|
||||
HMODULE load_menu_hook() {
|
||||
wchar_t path[4096] = {};
|
||||
const DWORD length = GetModuleFileNameW(nullptr, path, std::size(path));
|
||||
if (length == 0 || length >= std::size(path))
|
||||
return nullptr;
|
||||
bool helper_file_path(wchar_t *path, size_t capacity, const wchar_t *name) {
|
||||
const DWORD length = GetModuleFileNameW(nullptr, path, capacity);
|
||||
if (length == 0 || length >= capacity)
|
||||
return false;
|
||||
wchar_t *separator = nullptr;
|
||||
for (wchar_t *cursor = path; *cursor; ++cursor)
|
||||
if (*cursor == L'\\' || *cursor == L'/') separator = cursor;
|
||||
if (separator == nullptr)
|
||||
return false;
|
||||
const auto remaining = capacity - (separator + 1 - path);
|
||||
if (remaining <= static_cast<size_t>(lstrlenW(name)))
|
||||
return false;
|
||||
lstrcpyW(separator + 1, name);
|
||||
return true;
|
||||
}
|
||||
|
||||
HMODULE load_menu_hook() {
|
||||
wchar_t path[4096] = {};
|
||||
return helper_file_path(path, std::size(path), L"wine-access-menu.dll")
|
||||
? LoadLibraryW(path) : nullptr;
|
||||
}
|
||||
|
||||
HANDLE start_menu_host32(HWND bridge) {
|
||||
wchar_t path[4096] = {};
|
||||
if (!helper_file_path(path, std::size(path), L"wine-access-menu-host32.exe"))
|
||||
return nullptr;
|
||||
const auto remaining = std::size(path) - (separator + 1 - path);
|
||||
if (remaining <= static_cast<size_t>(lstrlenW(L"wine-access-menu.dll")))
|
||||
wchar_t command[8192] = {};
|
||||
wsprintfW(command, L"\"%s\" %lu %lu", path, GetCurrentProcessId(),
|
||||
static_cast<DWORD>(reinterpret_cast<UINT_PTR>(bridge)));
|
||||
STARTUPINFOW startup{};
|
||||
startup.cb = sizeof(startup);
|
||||
PROCESS_INFORMATION process{};
|
||||
if (!CreateProcessW(path, command, nullptr, nullptr, FALSE, 0, nullptr,
|
||||
nullptr, &startup, &process))
|
||||
return nullptr;
|
||||
lstrcpyW(separator + 1, L"wine-access-menu.dll");
|
||||
return LoadLibraryW(path);
|
||||
CloseHandle(process.hThread);
|
||||
return process.hProcess;
|
||||
}
|
||||
|
||||
void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||
@@ -650,6 +716,21 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||
return;
|
||||
if (editReaderActive && GetPropW(window, EDIT_STATE_PROPERTY))
|
||||
return;
|
||||
if (HWND combo = combo_event_control(window, event)) {
|
||||
// The combo's internal list can emit focus events even while the combo
|
||||
// retains keyboard focus. Keep a single source for focus and value updates.
|
||||
GUITHREADINFO info{};
|
||||
info.cbSize = sizeof(info);
|
||||
HWND foreground = GetForegroundWindow();
|
||||
if (foreground && GetGUIThreadInfo(GetWindowThreadProcessId(foreground, nullptr), &info) &&
|
||||
!(info.flags & GUI_INMENUMODE) && info.hwndFocus == combo) {
|
||||
if (event == EVENT_OBJECT_FOCUS && window == combo)
|
||||
present_standard_focus(combo);
|
||||
else
|
||||
poll_control_focus(event == EVENT_OBJECT_NAMECHANGE || event == EVENT_OBJECT_VALUECHANGE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Wine's MSAA provider exposes native list views as named clients, without
|
||||
// their rows. Use the standard-control query even when that name is present.
|
||||
if (is_list_view(window) || is_native_item_control(window)) {
|
||||
@@ -920,6 +1001,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
HHOOK inputHook = nullptr;
|
||||
HMODULE menuModule = nullptr;
|
||||
HWND menuBridge = nullptr;
|
||||
HANDLE menuHost32 = nullptr;
|
||||
if (dialogReaderEnabled) {
|
||||
hook = SetWinEventHook(EVENT_MIN, EVENT_MAX, nullptr, winevent_callback, 0,
|
||||
0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
||||
@@ -943,10 +1025,16 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
if (menuHook == nullptr)
|
||||
std::fprintf(stderr, "wine-access: native menu hook unavailable (error %lu)\n",
|
||||
static_cast<unsigned long>(GetLastError()));
|
||||
if (menuBridge != nullptr) {
|
||||
menuHost32 = start_menu_host32(menuBridge);
|
||||
if (menuHost32 == nullptr)
|
||||
std::fprintf(stderr, "wine-access: 32-bit menu host unavailable (error %lu)\n",
|
||||
static_cast<unsigned long>(GetLastError()));
|
||||
}
|
||||
}
|
||||
MSG message{};
|
||||
// Native lists, tabs and trees can omit selection WinEvents in Wine. Observe
|
||||
// the focused item with scalar queries; retrieve text only when it changes.
|
||||
// Native lists, tabs, trees and combo boxes can omit change WinEvents in Wine.
|
||||
// Observe only the foreground control; never read background selections.
|
||||
const UINT_PTR listTimer = dialogReaderEnabled ? SetTimer(nullptr, 0, 100, nullptr) : 0;
|
||||
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
||||
if (listTimer && message.message == WM_TIMER && message.hwnd == nullptr &&
|
||||
@@ -966,6 +1054,15 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
UnhookWindowsHookEx(inputHook);
|
||||
if (menuBridge != nullptr)
|
||||
DestroyWindow(menuBridge);
|
||||
if (menuHost32 != nullptr) {
|
||||
// Destroying the bridge ends the host's loop. Parent-process death also
|
||||
// stops it, so a reader crash cannot leave an orphaned global menu hook.
|
||||
if (WaitForSingleObject(menuHost32, 1000) == WAIT_TIMEOUT) {
|
||||
TerminateProcess(menuHost32, 0);
|
||||
WaitForSingleObject(menuHost32, 1000);
|
||||
}
|
||||
CloseHandle(menuHost32);
|
||||
}
|
||||
if (menuModule != nullptr)
|
||||
FreeLibrary(menuModule);
|
||||
if (controllerActive) {
|
||||
|
||||
Reference in new issue
Block a user