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
@@ -0,0 +1,136 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#include "native_items.h"
|
||||
#include "control_class.h"
|
||||
#include <commctrl.h>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
||||
namespace {
|
||||
bool query(HWND window, UINT message, WPARAM wp, LPARAM lp, DWORD_PTR *result) {
|
||||
return SendMessageTimeoutW(window, message, wp, lp, SMTO_ABORTIFHUNG,
|
||||
100, result) != 0;
|
||||
}
|
||||
bool is_tab(HWND window) {
|
||||
wchar_t className[128] = {};
|
||||
return GetClassNameW(window, className, std::size(className)) &&
|
||||
wine_access_class_matches(className, L"SysTabControl32");
|
||||
}
|
||||
struct Tab32 { uint32_t mask, state, stateMask, text; int32_t capacity; };
|
||||
struct Tab64 { uint32_t mask, state, stateMask, padding; uint64_t text; int32_t capacity; };
|
||||
struct Tree32 {
|
||||
uint32_t mask, item, state, stateMask, text;
|
||||
int32_t capacity, image, selectedImage, children;
|
||||
};
|
||||
struct Tree64 {
|
||||
uint32_t mask, padding;
|
||||
uint64_t item;
|
||||
uint32_t state, stateMask;
|
||||
uint64_t text;
|
||||
int32_t capacity, image, selectedImage, children;
|
||||
};
|
||||
static_assert(offsetof(Tab32, text) == 12 && offsetof(Tab64, text) == 16);
|
||||
static_assert(offsetof(Tree32, text) == 16 && offsetof(Tree64, text) == 24);
|
||||
constexpr size_t textOffset = 128;
|
||||
constexpr int textCapacity = 1024;
|
||||
constexpr size_t bufferSize = textOffset + textCapacity * sizeof(wchar_t);
|
||||
}
|
||||
|
||||
bool is_native_item_control(HWND window) {
|
||||
wchar_t className[128] = {};
|
||||
return GetClassNameW(window, className, std::size(className)) &&
|
||||
(wine_access_class_matches(className, L"SysTabControl32") ||
|
||||
wine_access_class_matches(className, L"SysTreeView32"));
|
||||
}
|
||||
|
||||
NativeItemKey native_item_key(HWND window) {
|
||||
NativeItemKey key;
|
||||
DWORD_PTR count = 0, item = 0, state = 0;
|
||||
if (is_tab(window)) {
|
||||
if (!query(window, TCM_GETITEMCOUNT, 0, 0, &count) || count == 0) return key;
|
||||
// Keyboard focus can move before selection in button-style tab controls.
|
||||
if (!query(window, TCM_GETCURFOCUS, 0, 0, &item) ||
|
||||
static_cast<LONG_PTR>(item) < 0)
|
||||
if (!query(window, TCM_GETCURSEL, 0, 0, &item)) return key;
|
||||
if (item >= count) return key;
|
||||
key.item = item + 1; // Zero means no item for both control types.
|
||||
DWORD_PTR selected = 0;
|
||||
if (query(window, TCM_GETCURSEL, 0, 0, &selected) && selected == item)
|
||||
key.state = TVIS_SELECTED;
|
||||
} else {
|
||||
if (!query(window, TVM_GETCOUNT, 0, 0, &count) ||
|
||||
!query(window, TVM_GETNEXTITEM, TVGN_CARET, 0, &item)) return key;
|
||||
key.item = item;
|
||||
if (item && query(window, TVM_GETITEMSTATE, item,
|
||||
TVIS_SELECTED | TVIS_EXPANDED, &state)) key.state = state;
|
||||
}
|
||||
key.count = static_cast<int>(count);
|
||||
return key;
|
||||
}
|
||||
|
||||
NativeItem read_native_item(HWND window) {
|
||||
NativeItem item;
|
||||
const bool tab = is_tab(window);
|
||||
item.role = tab ? "page tab" : "tree";
|
||||
const auto key = native_item_key(window);
|
||||
item.key = key;
|
||||
if (!key.item) return item;
|
||||
DWORD processId = 0;
|
||||
GetWindowThreadProcessId(window, &processId);
|
||||
HANDLE process = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ |
|
||||
PROCESS_VM_WRITE | PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
FALSE, processId);
|
||||
if (!process) return item;
|
||||
BOOL wow64 = FALSE;
|
||||
if (!IsWow64Process(process, &wow64)) { CloseHandle(process); return item; }
|
||||
void *remote = VirtualAllocEx(process, nullptr, bufferSize,
|
||||
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remote || (wow64 && reinterpret_cast<uintptr_t>(remote) > UINT32_MAX)) {
|
||||
if (remote) VirtualFreeEx(process, remote, 0, MEM_RELEASE);
|
||||
CloseHandle(process);
|
||||
return item;
|
||||
}
|
||||
BYTE buffer[bufferSize] = {};
|
||||
const auto address = reinterpret_cast<uintptr_t>(remote) + textOffset;
|
||||
if (tab && wow64) {
|
||||
const Tab32 data{TCIF_TEXT, 0, 0, static_cast<uint32_t>(address), textCapacity};
|
||||
memcpy(buffer, &data, sizeof(data));
|
||||
} else if (tab) {
|
||||
const Tab64 data{TCIF_TEXT, 0, 0, 0, address, textCapacity};
|
||||
memcpy(buffer, &data, sizeof(data));
|
||||
} else if (wow64) {
|
||||
const Tree32 data{TVIF_TEXT | TVIF_CHILDREN, static_cast<uint32_t>(key.item),
|
||||
0, 0, static_cast<uint32_t>(address), textCapacity, 0, 0, 0};
|
||||
memcpy(buffer, &data, sizeof(data));
|
||||
} else {
|
||||
const Tree64 data{TVIF_TEXT | TVIF_CHILDREN, 0, key.item, 0, 0,
|
||||
address, textCapacity, 0, 0, 0};
|
||||
memcpy(buffer, &data, sizeof(data));
|
||||
}
|
||||
DWORD_PTR result = 0;
|
||||
if (WriteProcessMemory(process, remote, buffer, sizeof(buffer), nullptr) &&
|
||||
query(window, tab ? TCM_GETITEMW : TVM_GETITEMW, tab ? key.item - 1 : 0,
|
||||
reinterpret_cast<LPARAM>(remote), &result) && result &&
|
||||
ReadProcessMemory(process, remote, buffer, sizeof(buffer), nullptr)) {
|
||||
wchar_t text[textCapacity] = {};
|
||||
memcpy(text, buffer + textOffset, sizeof(text));
|
||||
text[textCapacity - 1] = L'\0';
|
||||
char utf8[textCapacity * 4] = {};
|
||||
if (WideCharToMultiByte(CP_UTF8, 0, text, -1, utf8, sizeof(utf8), nullptr, nullptr))
|
||||
item.text = utf8;
|
||||
if (key.state & TVIS_SELECTED) item.states = "selected";
|
||||
if (tab) { item.position = static_cast<int>(key.item); item.count = key.count; }
|
||||
else {
|
||||
item.role = "tree item";
|
||||
int32_t children = 0;
|
||||
memcpy(&children, buffer + (wow64 ? offsetof(Tree32, children) : offsetof(Tree64, children)), sizeof(children));
|
||||
if (children != 0) {
|
||||
if (!item.states.empty()) item.states += ", ";
|
||||
item.states += key.state & TVIS_EXPANDED ? "expanded" : "collapsed";
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualFreeEx(process, remote, 0, MEM_RELEASE);
|
||||
CloseHandle(process);
|
||||
return item;
|
||||
}
|
||||
Reference in new issue
Block a user