Read native Wine list-view rows in 32-bit and 64-bit applications
This commit is contained in:
1 parent
9aad6748e0
commit
d7ea005a1f
8 files changed
+356
-4
No files matched your search
@@ -73,6 +73,32 @@ reader's `docs/testing/wine-accessibility.md` for current evidence and live
|
||||
acceptance gates. Run Xvfb fixtures sequentially; concurrent automatic display
|
||||
allocation can collide.
|
||||
|
||||
## List-view checks
|
||||
|
||||
```sh
|
||||
xvfb-run -a python3 tests/menu_integration.py --build-dir build --fixture list
|
||||
xvfb-run -a python3 tests/menu_integration.py --build-dir build --fixture list --32-bit
|
||||
```
|
||||
|
||||
The second command requires `i686-w64-mingw32-gcc`. Both use a private prefix
|
||||
and recording D-Bus service. They verify first-row reading without selecting
|
||||
it, two columns, Unicode and literal ampersands, Down/Up, button/list focus
|
||||
return, changed row text on a value event, and deletion of all rows without
|
||||
stale output. Run them sequentially. Both architecture fixtures passed on
|
||||
October 10, as did the existing menu, editor, actual Notepad and CTest MSAA
|
||||
state checks. The reader's focused Wine race checks also passed.
|
||||
|
||||
The BARD Express bootstrapper was inspected in place on October 10. Its
|
||||
32-bit `SysListView32` has one row: `Pending, Microsoft .NET Framework 4.7.2 Full`.
|
||||
The new query code retrieved that row without changing the installer. This
|
||||
does not establish audible output or successful prerequisite installation.
|
||||
|
||||
After installing the helper and restarting Cthulhu, Tab to the prerequisite
|
||||
list: expect the instructions, list role, pending component and `1 of 1`.
|
||||
Check arrows and Tab/Shift+Tab back to Install/Cancel. Also check a multirow
|
||||
native list, normal menus, editor navigation, switching to a native Linux
|
||||
application, and reader shutdown. Verify speech and physical braille separately.
|
||||
|
||||
## Live Notepad acceptance
|
||||
|
||||
Install all three helper files together and use the matching Cthulhu-Go source.
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#define UNICODE
|
||||
#define _UNICODE
|
||||
#include <windows.h>
|
||||
#include <commctrl.h>
|
||||
|
||||
static HWND list, button;
|
||||
static int step;
|
||||
|
||||
static LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
|
||||
if (message == WM_TIMER) {
|
||||
switch (++step) {
|
||||
case 1: case 2:
|
||||
// Exercise keyboard processing, not synthetic accessibility events.
|
||||
SendMessageW(list, WM_KEYDOWN, VK_DOWN, 1);
|
||||
break;
|
||||
case 3:
|
||||
SendMessageW(list, WM_KEYDOWN, VK_UP, 1);
|
||||
break;
|
||||
case 4: SetFocus(button); break;
|
||||
case 5: SetFocus(list); break;
|
||||
case 6: {
|
||||
LVITEMW item = {0};
|
||||
item.pszText = L"Ready";
|
||||
SendMessageW(list, LVM_SETITEMTEXTW, 0, (LPARAM)&item);
|
||||
NotifyWinEvent(EVENT_OBJECT_VALUECHANGE, list, OBJID_CLIENT, CHILDID_SELF);
|
||||
break;
|
||||
}
|
||||
case 7: SendMessageW(list, LVM_DELETEALLITEMS, 0, 0); break;
|
||||
default: DestroyWindow(window);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (message == WM_DESTROY) { PostQuitMessage(0); return 0; }
|
||||
return DefWindowProcW(window, message, wp, lp);
|
||||
}
|
||||
|
||||
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
|
||||
(void)previous; (void)args; (void)show;
|
||||
INITCOMMONCONTROLSEX controls = {sizeof(controls), ICC_LISTVIEW_CLASSES};
|
||||
InitCommonControlsEx(&controls);
|
||||
WNDCLASSW cls = {0};
|
||||
cls.hInstance = instance;
|
||||
cls.lpszClassName = L"WineAccessListFixture";
|
||||
cls.lpfnWndProc = window_proc;
|
||||
RegisterClassW(&cls);
|
||||
HWND window = CreateWindowW(cls.lpszClassName, L"Wine list fixture", WS_OVERLAPPEDWINDOW,
|
||||
120, 120, 600, 300, NULL, NULL, instance, NULL);
|
||||
button = CreateWindowW(L"Button", L"Return to control", WS_CHILD | WS_VISIBLE | WS_TABSTOP,
|
||||
10, 240, 200, 30, window, NULL, instance, NULL);
|
||||
CreateWindowW(L"Static", L"Required components", WS_CHILD | WS_VISIBLE,
|
||||
10, 10, 550, 30, window, NULL, instance, NULL);
|
||||
list = CreateWindowW(WC_LISTVIEWW, L"List1", WS_CHILD | WS_VISIBLE | WS_TABSTOP | LVS_REPORT,
|
||||
10, 50, 550, 180, window, NULL, instance, NULL);
|
||||
LVCOLUMNW column = {0};
|
||||
column.mask = LVCF_TEXT | LVCF_WIDTH;
|
||||
column.cx = 150;
|
||||
column.pszText = L"Status";
|
||||
SendMessageW(list, LVM_INSERTCOLUMNW, 0, (LPARAM)&column);
|
||||
column.cx = 350;
|
||||
column.pszText = L"Component";
|
||||
SendMessageW(list, LVM_INSERTCOLUMNW, 1, (LPARAM)&column);
|
||||
LVITEMW item = {0};
|
||||
item.mask = LVIF_TEXT;
|
||||
item.pszText = L"Pending";
|
||||
SendMessageW(list, LVM_INSERTITEMW, 0, (LPARAM)&item);
|
||||
item.iSubItem = 1;
|
||||
item.pszText = L"Runtime & tools β";
|
||||
SendMessageW(list, LVM_SETITEMTEXTW, 0, (LPARAM)&item);
|
||||
item.iItem = 1;
|
||||
item.iSubItem = 0;
|
||||
item.pszText = L"Installed";
|
||||
SendMessageW(list, LVM_INSERTITEMW, 0, (LPARAM)&item);
|
||||
item.iSubItem = 1;
|
||||
item.pszText = L"Second component";
|
||||
SendMessageW(list, LVM_SETITEMTEXTW, 1, (LPARAM)&item);
|
||||
ShowWindow(window, SW_SHOW);
|
||||
SetForegroundWindow(window);
|
||||
// Match the installer: initially no row is selected or focused.
|
||||
SetFocus(list);
|
||||
SetTimer(window, 1, 500, NULL);
|
||||
MSG message;
|
||||
while (GetMessageW(&message, NULL, 0, 0) > 0) {
|
||||
TranslateMessage(&message); DispatchMessageW(&message);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -16,10 +16,13 @@ from gi.repository import Gio, GLib
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--build-dir", default="build")
|
||||
parser.add_argument("--session", action="store_true")
|
||||
parser.add_argument("--fixture", choices=("menu", "edit", "notepad"), default="menu")
|
||||
parser.add_argument("--fixture", choices=("menu", "edit", "notepad", "list"), default="menu")
|
||||
parser.add_argument("--32-bit", dest="is32Bit", action="store_true")
|
||||
parser.add_argument("--late-helper", action="store_true")
|
||||
parser.add_argument("--restart-helper", action="store_true")
|
||||
args = parser.parse_args()
|
||||
if args.is32Bit and args.fixture != "list":
|
||||
parser.error("32-bit checks currently require --fixture list")
|
||||
if (args.late_helper or args.restart_helper) and args.fixture != "edit":
|
||||
parser.error("helper lifecycle checks require --fixture edit")
|
||||
build = Path(args.build_dir).resolve()
|
||||
@@ -27,6 +30,7 @@ if not args.session:
|
||||
sys.exit(subprocess.call(["dbus-run-session", "--", sys.executable,
|
||||
__file__, "--session", "--build-dir", str(build),
|
||||
"--fixture", args.fixture,
|
||||
*(["--32-bit"] if args.is32Bit else []),
|
||||
*(["--late-helper"] if args.late_helper else []),
|
||||
*(["--restart-helper"] if args.restart_helper else [])]))
|
||||
|
||||
@@ -56,9 +60,10 @@ with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
||||
ready = Path(temporary) / "ready"
|
||||
env["WINE_ACCESS_FIXTURE_READY"] = "Z:" + str(ready).replace("/", "\\")
|
||||
fixture = Path(temporary) / "menu-fixture.exe"
|
||||
subprocess.run(["x86_64-w64-mingw32-gcc", "-Wall", "-Wextra", "-mwindows",
|
||||
compiler = "i686-w64-mingw32-gcc" if args.is32Bit else "x86_64-w64-mingw32-gcc"
|
||||
subprocess.run([compiler, "-Wall", "-Wextra", "-mwindows",
|
||||
"-o", str(fixture), str(Path(__file__).with_name(args.fixture + "_fixture.c")),
|
||||
"-luser32"], check=True)
|
||||
"-luser32", "-lcomctl32"], check=True)
|
||||
helper = None
|
||||
app = None
|
||||
try:
|
||||
@@ -104,6 +109,25 @@ with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
||||
focused = event[0]
|
||||
elif event[1] == "text":
|
||||
assert event[0] == focused, f"text sent without establishing focus: {event!r}"
|
||||
if args.fixture == "list":
|
||||
listEvents = [e for e in events if e[3] == "list"]
|
||||
for event in listEvents:
|
||||
print(event[1:9])
|
||||
assert any(e[1] == "focus" and e[2] == "Required components" and
|
||||
e[4] == "Pending, Runtime & tools β" and e[7:9] == (1, 2)
|
||||
and e[5] == "" for e in listEvents), "unselected initial row or label missing"
|
||||
assert any(e[1] == "selection" and e[4] == "Installed, Second component" and
|
||||
e[7:9] == (2, 2) for e in listEvents), "Down lost second row/columns"
|
||||
assert any(e[1] == "selection" and e[4] == "Pending, Runtime & tools β" and
|
||||
e[7:9] == (1, 2) for e in listEvents), "Up lost first row"
|
||||
assert any(e[1] == "focus" and e[4] == "Pending, Runtime & tools β" and
|
||||
e[5] == "selected" for e in listEvents), "return to list lost selection"
|
||||
assert any(e[4] == "Ready, Runtime & tools β" for e in listEvents), "value event lost changed row text"
|
||||
assert any(e[4] == "" and e[7:9] == (0, 0) for e in listEvents), "empty list retained stale row"
|
||||
assert any(e[2] == "Return to control" for e in events), "button focus missing"
|
||||
assert not any(e[2] == "List1" for e in events), "generic MSAA client leaked"
|
||||
print("PASS: native list focus, columns, Unicode, navigation, return, and empty list")
|
||||
sys.exit(0)
|
||||
if args.fixture == "notepad":
|
||||
dialogEvents = [e for e in events if e[2] in ("Yes", "No", "Cancel")]
|
||||
for event in dialogEvents:
|
||||
|
||||
Reference in new issue
Block a user