More accessibility fixes using foobar2000 as a test.
This commit is contained in:
1 parent
22f50402c4
commit
5f78168c57
9 files changed
+407
-14
No files matched your search
@@ -6,6 +6,7 @@
|
||||
#include "msaa_states.h"
|
||||
#include "list_view.h"
|
||||
#include "control_class.h"
|
||||
#include "native_items.h"
|
||||
|
||||
#include <commctrl.h>
|
||||
#include <gio/gio.h>
|
||||
@@ -37,6 +38,8 @@ HWND lastListControl = nullptr;
|
||||
int lastListIndex = -1;
|
||||
int lastListCount = 0;
|
||||
bool lastListSelected = false;
|
||||
HWND lastNativeControl = nullptr;
|
||||
NativeItemKey lastNativeItem;
|
||||
|
||||
// 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.
|
||||
@@ -163,11 +166,16 @@ void find_control_label(HWND window, wchar_t *text, int capacity) {
|
||||
HWND previous = window;
|
||||
wchar_t className[64] = {};
|
||||
text[0] = L'\0';
|
||||
// Managed controls need not be created in label/control order. Prefer a
|
||||
const bool knownClass = GetClassNameW(window, className, std::size(className));
|
||||
const bool managedEdit = knownClass && lstrcmpiW(className, L"Edit") != 0 &&
|
||||
wine_access_class_matches(className, L"Edit");
|
||||
// Controls need not be created in label/control order. Prefer a
|
||||
// visible static label immediately to the left on the same row, among
|
||||
// siblings only. Keep the existing native-control ordering below.
|
||||
if (GetClassNameW(window, className, std::size(className)) &&
|
||||
lstrcmpiW(className, L"Edit") != 0 && wine_access_class_matches(className, L"Edit")) {
|
||||
if (knownClass &&
|
||||
(wine_access_class_matches(className, L"Edit") ||
|
||||
wine_access_class_matches(className, L"ComboBox") ||
|
||||
wine_access_class_matches(className, L"ComboBoxEx32"))) {
|
||||
RECT controlRect{};
|
||||
if (GetWindowRect(window, &controlRect)) {
|
||||
HWND label = nullptr;
|
||||
@@ -188,9 +196,11 @@ void find_control_label(HWND window, wchar_t *text, int capacity) {
|
||||
}
|
||||
if (label && get_control_text(label, text, capacity)) {
|
||||
tidy_text(text, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
// Retain ordered labels for controls without a same-row sibling.
|
||||
if (managedEdit) return;
|
||||
}
|
||||
for (int hop = 0; hop < 3; ++hop) {
|
||||
previous = GetWindow(previous, GW_HWNDPREV);
|
||||
@@ -317,6 +327,18 @@ ControlDescription describe_standard_control(HWND window) {
|
||||
return control;
|
||||
}
|
||||
|
||||
if (is_native_item_control(window)) {
|
||||
const auto item = read_native_item(window);
|
||||
control.name = item.text;
|
||||
control.role = item.role;
|
||||
control.states = item.states;
|
||||
control.position = item.position;
|
||||
control.count = item.count;
|
||||
lastNativeControl = window;
|
||||
lastNativeItem = item.key;
|
||||
return control;
|
||||
}
|
||||
|
||||
if (wine_access_class_matches(className, L"ListBox") ||
|
||||
wine_access_class_matches(className, L"ComboLBox")) {
|
||||
find_control_label(window, label, std::size(label));
|
||||
@@ -384,10 +406,28 @@ bool present_standard_focus(HWND window) {
|
||||
control, window_title(window));
|
||||
}
|
||||
|
||||
void poll_list_focus(bool refresh = false) {
|
||||
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)) {
|
||||
lastListControl = nullptr;
|
||||
lastListIndex = -1;
|
||||
lastListCount = 0;
|
||||
lastListSelected = false;
|
||||
const auto key = native_item_key(info.hwndFocus);
|
||||
if (info.hwndFocus != lastNativeControl) {
|
||||
present_standard_focus(info.hwndFocus);
|
||||
} else if (refresh || !(key == lastNativeItem)) {
|
||||
const auto control = describe_standard_control(info.hwndFocus);
|
||||
present_accessible_event(source_id(info.hwndFocus, "standard"), "selection",
|
||||
control, window_title(info.hwndFocus));
|
||||
}
|
||||
return;
|
||||
}
|
||||
lastNativeControl = nullptr;
|
||||
lastNativeItem = {};
|
||||
if (foreground && GetGUIThreadInfo(GetWindowThreadProcessId(foreground, nullptr), &info) &&
|
||||
!(info.flags & GUI_INMENUMODE) && is_list_view(info.hwndFocus)) {
|
||||
int count = 0;
|
||||
@@ -587,9 +627,9 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||
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)) {
|
||||
if (is_list_view(window) || is_native_item_control(window)) {
|
||||
if (event == EVENT_OBJECT_FOCUS) present_standard_focus(window);
|
||||
else poll_list_focus(event == EVENT_OBJECT_NAMECHANGE || event == EVENT_OBJECT_VALUECHANGE);
|
||||
else poll_control_focus(event == EVENT_OBJECT_NAMECHANGE || event == EVENT_OBJECT_VALUECHANGE);
|
||||
return;
|
||||
}
|
||||
IAccessible *accessible = nullptr;
|
||||
@@ -628,6 +668,8 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||
VariantClear(&state);
|
||||
LONG childCount = 0;
|
||||
accessible->get_accChildCount(&childCount);
|
||||
const bool nativeClient = child.vt == VT_I4 && child.lVal == CHILDID_SELF &&
|
||||
(roleText.empty() || roleId == ROLE_SYSTEM_CLIENT);
|
||||
accessible->Release();
|
||||
VariantClear(&child);
|
||||
|
||||
@@ -667,6 +709,19 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
|
||||
control.states = stateText;
|
||||
control.description = description;
|
||||
control.count = static_cast<int>(childCount);
|
||||
// Wine can return named generic clients even for standard buttons and
|
||||
// combo boxes. Their names alone must not hide the native role/value.
|
||||
// Application-owned list items and other meaningful MSAA roles stay intact.
|
||||
if (nativeClient) {
|
||||
const auto native = describe_standard_control(window);
|
||||
if (!native.role.empty()) {
|
||||
control = native;
|
||||
if (control.name.empty()) control.name = name;
|
||||
if (control.states.empty()) control.states = stateText;
|
||||
if (control.description.empty()) control.description = description;
|
||||
if (password) control.value.clear();
|
||||
}
|
||||
}
|
||||
present_accessible_event(sourceId, eventType, control, windowTitle);
|
||||
}
|
||||
|
||||
@@ -865,13 +920,13 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, char *, int) {
|
||||
static_cast<unsigned long>(GetLastError()));
|
||||
}
|
||||
MSG message{};
|
||||
// Native list views also omit row-selection WinEvents in Wine. Observe the
|
||||
// focused row with scalar queries; retrieve its text only when it changes.
|
||||
// Native lists, tabs and trees can omit selection WinEvents in Wine. Observe
|
||||
// the focused item with scalar queries; retrieve text only when it changes.
|
||||
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 &&
|
||||
message.wParam == listTimer) {
|
||||
poll_list_focus();
|
||||
poll_control_focus();
|
||||
continue;
|
||||
}
|
||||
TranslateMessage(&message);
|
||||
|
||||
Reference in new issue
Block a user