Read Windows Forms login fields and associate their labels
This commit is contained in:
1 parent
d7ea005a1f
commit
25e38907d3
9 files changed
+149
-25
No files matched your search
+1
-1
@@ -16,7 +16,7 @@ add_custom_command(OUTPUT "${menu_hook}"
|
|||||||
COMMAND "${MINGW_C_COMPILER}" -std=c11 -O2 -Wall -Wextra -shared
|
COMMAND "${MINGW_C_COMPILER}" -std=c11 -O2 -Wall -Wextra -shared
|
||||||
-static-libgcc -o "${menu_hook}" "${CMAKE_CURRENT_SOURCE_DIR}/menu_hook.c"
|
-static-libgcc -o "${menu_hook}" "${CMAKE_CURRENT_SOURCE_DIR}/menu_hook.c"
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/edit_hook.c" -luser32 -lcomctl32
|
"${CMAKE_CURRENT_SOURCE_DIR}/edit_hook.c" -luser32 -lcomctl32
|
||||||
DEPENDS menu_hook.c menu_bridge.h edit_hook.c edit_hook.h
|
DEPENDS menu_hook.c menu_bridge.h edit_hook.c edit_hook.h control_class.h
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
add_custom_target(wine-access-menu ALL DEPENDS "${menu_hook}")
|
add_custom_target(wine-access-menu ALL DEPENDS "${menu_hook}")
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,11 @@ close. The hook applies to the helper's Wine desktop and prefix and is removed
|
|||||||
on normal shutdown. It is disabled by `--no-dialog-reader`.
|
on normal shutdown. It is disabled by `--no-dialog-reader`.
|
||||||
|
|
||||||
The same DLL subclasses standard `Edit` controls, including Wine Notepad's
|
The same DLL subclasses standard `Edit` controls, including Wine Notepad's
|
||||||
document area. It reports the current line on focus and menu return, characters
|
document area and `WindowsForms10.EDIT.app.*` controls. Windows Forms fields
|
||||||
|
use visible sibling labels immediately to their left, even when the labels
|
||||||
|
were created after the fields. Protected fields report their label and
|
||||||
|
password role without forwarding their contents.
|
||||||
|
It reports the current line on focus and menu return, characters
|
||||||
and words on horizontal navigation, lines on vertical/page/document-boundary
|
and words on horizontal navigation, lines on vertical/page/document-boundary
|
||||||
navigation, changed selections, and Backspace/Delete/Cut feedback. Password
|
navigation, changed selections, and Backspace/Delete/Cut feedback. Password
|
||||||
contents are excluded, and read-only edits do not report unsuccessful deletions.
|
contents are excluded, and read-only edits do not report unsuccessful deletions.
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
#pragma once
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// Windows Forms registers native-control subclasses with versioned names.
|
||||||
|
// Match the control token and its boundary; unrelated managed windows are
|
||||||
|
// not edits merely because their name contains EDIT somewhere.
|
||||||
|
static inline BOOL wine_access_class_matches(const WCHAR *className, const WCHAR *nativeName) {
|
||||||
|
if (lstrcmpiW(className, nativeName) == 0) return TRUE;
|
||||||
|
WCHAR expected[128] = L"WindowsForms10.";
|
||||||
|
if (lstrlenW(nativeName) > 80) return FALSE;
|
||||||
|
lstrcatW(expected, nativeName);
|
||||||
|
lstrcatW(expected, L".app.");
|
||||||
|
int length = lstrlenW(expected);
|
||||||
|
return lstrlenW(className) > length &&
|
||||||
|
CompareStringOrdinal(className, length, expected, length, TRUE) == CSTR_EQUAL;
|
||||||
|
}
|
||||||
+3
-1
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
#include "edit_hook.h"
|
#include "edit_hook.h"
|
||||||
#include "menu_bridge.h"
|
#include "menu_bridge.h"
|
||||||
|
#include "control_class.h"
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@@ -186,7 +187,8 @@ static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM l
|
|||||||
|
|
||||||
BOOL wine_access_watch_edit(HWND window) {
|
BOOL wine_access_watch_edit(HWND window) {
|
||||||
WCHAR class_name[64];
|
WCHAR class_name[64];
|
||||||
if (!window || !GetClassNameW(window, class_name, 64) || lstrcmpW(class_name, L"Edit")) return FALSE;
|
if (!window || !GetClassNameW(window, class_name, 64) ||
|
||||||
|
!wine_access_class_matches(class_name, L"Edit")) return FALSE;
|
||||||
// Wine's comctl32 v5 exports GetWindowSubclass only by ordinal. Our own
|
// Wine's comctl32 v5 exports GetWindowSubclass only by ordinal. Our own
|
||||||
// property also keeps attachment independent of the application's manifest.
|
// property also keeps attachment independent of the application's manifest.
|
||||||
if (GetPropW(window, EDIT_STATE_PROPERTY)) return FALSE;
|
if (GetPropW(window, EDIT_STATE_PROPERTY)) return FALSE;
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
#include "list_view.h"
|
#include "list_view.h"
|
||||||
|
#include "control_class.h"
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -38,7 +39,7 @@ constexpr size_t bufferSize = textOffset + textCapacity * sizeof(wchar_t);
|
|||||||
bool is_list_view(HWND window) {
|
bool is_list_view(HWND window) {
|
||||||
wchar_t className[64] = {};
|
wchar_t className[64] = {};
|
||||||
return GetClassNameW(window, className, std::size(className)) &&
|
return GetClassNameW(window, className, std::size(className)) &&
|
||||||
lstrcmpiW(className, L"SysListView32") == 0;
|
wine_access_class_matches(className, L"SysListView32");
|
||||||
}
|
}
|
||||||
|
|
||||||
int list_view_index(HWND window, int *count) {
|
int list_view_index(HWND window, int *count) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "edit_hook.h"
|
#include "edit_hook.h"
|
||||||
#include "msaa_states.h"
|
#include "msaa_states.h"
|
||||||
#include "list_view.h"
|
#include "list_view.h"
|
||||||
|
#include "control_class.h"
|
||||||
|
|
||||||
#include <commctrl.h>
|
#include <commctrl.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
@@ -162,12 +163,41 @@ void find_control_label(HWND window, wchar_t *text, int capacity) {
|
|||||||
HWND previous = window;
|
HWND previous = window;
|
||||||
wchar_t className[64] = {};
|
wchar_t className[64] = {};
|
||||||
text[0] = L'\0';
|
text[0] = L'\0';
|
||||||
|
// Managed 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")) {
|
||||||
|
RECT controlRect{};
|
||||||
|
if (GetWindowRect(window, &controlRect)) {
|
||||||
|
HWND label = nullptr;
|
||||||
|
LONG bestGap = 201;
|
||||||
|
HWND sibling = GetWindow(GetParent(window), GW_CHILD);
|
||||||
|
for (int hop = 0; sibling && hop < 128; ++hop, sibling = GetWindow(sibling, GW_HWNDNEXT)) {
|
||||||
|
RECT labelRect{};
|
||||||
|
if (sibling == window || !IsWindowVisible(sibling) ||
|
||||||
|
!GetClassNameW(sibling, className, std::size(className)) ||
|
||||||
|
!wine_access_class_matches(className, L"Static") || !GetWindowRect(sibling, &labelRect))
|
||||||
|
continue;
|
||||||
|
const LONG gap = controlRect.left - labelRect.right;
|
||||||
|
if (gap >= 0 && gap < bestGap && labelRect.top < controlRect.bottom &&
|
||||||
|
labelRect.bottom > controlRect.top) {
|
||||||
|
label = sibling;
|
||||||
|
bestGap = gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (label && get_control_text(label, text, capacity)) {
|
||||||
|
tidy_text(text, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
for (int hop = 0; hop < 3; ++hop) {
|
for (int hop = 0; hop < 3; ++hop) {
|
||||||
previous = GetWindow(previous, GW_HWNDPREV);
|
previous = GetWindow(previous, GW_HWNDPREV);
|
||||||
if (previous == nullptr ||
|
if (previous == nullptr ||
|
||||||
!GetClassNameW(previous, className, std::size(className)))
|
!GetClassNameW(previous, className, std::size(className)))
|
||||||
return;
|
return;
|
||||||
if (lstrcmpW(className, L"Static") != 0 && lstrcmpW(className, L"Button") != 0)
|
if (!wine_access_class_matches(className, L"Static") && !wine_access_class_matches(className, L"Button"))
|
||||||
continue;
|
continue;
|
||||||
if (get_control_text(previous, text, capacity))
|
if (get_control_text(previous, text, capacity))
|
||||||
tidy_text(text, true);
|
tidy_text(text, true);
|
||||||
@@ -211,14 +241,14 @@ ControlDescription describe_standard_control(HWND window) {
|
|||||||
if (!GetClassNameW(window, className, std::size(className)))
|
if (!GetClassNameW(window, className, std::size(className)))
|
||||||
return control;
|
return control;
|
||||||
const LONG_PTR windowStyle = GetWindowLongPtrW(window, GWL_STYLE);
|
const LONG_PTR windowStyle = GetWindowLongPtrW(window, GWL_STYLE);
|
||||||
const bool isEdit = lstrcmpW(className, L"Edit") == 0 ||
|
const bool isEdit = wine_access_class_matches(className, L"Edit") ||
|
||||||
lstrcmpW(className, L"RICHEDIT50W") == 0 ||
|
wine_access_class_matches(className, L"RICHEDIT50W") ||
|
||||||
lstrcmpW(className, L"RichEdit20W") == 0 ||
|
wine_access_class_matches(className, L"RichEdit20W") ||
|
||||||
lstrcmpW(className, L"RichEdit20A") == 0;
|
wine_access_class_matches(className, L"RichEdit20A");
|
||||||
if (!isEdit || !(windowStyle & ES_PASSWORD))
|
if (!isEdit || !(windowStyle & ES_PASSWORD))
|
||||||
get_control_text(window, text, std::size(text));
|
get_control_text(window, text, std::size(text));
|
||||||
|
|
||||||
if (lstrcmpW(className, L"Button") == 0) {
|
if (wine_access_class_matches(className, L"Button")) {
|
||||||
control.name = to_utf8(text);
|
control.name = to_utf8(text);
|
||||||
const LONG_PTR style = GetWindowLongPtrW(window, GWL_STYLE);
|
const LONG_PTR style = GetWindowLongPtrW(window, GWL_STYLE);
|
||||||
DWORD_PTR checked = 0;
|
DWORD_PTR checked = 0;
|
||||||
@@ -262,8 +292,8 @@ ControlDescription describe_standard_control(HWND window) {
|
|||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lstrcmpW(className, L"ComboBox") == 0 ||
|
if (wine_access_class_matches(className, L"ComboBox") ||
|
||||||
lstrcmpW(className, L"ComboBoxEx32") == 0) {
|
wine_access_class_matches(className, L"ComboBoxEx32")) {
|
||||||
find_control_label(window, label, std::size(label));
|
find_control_label(window, label, std::size(label));
|
||||||
control.name = to_utf8(label);
|
control.name = to_utf8(label);
|
||||||
control.role = "combo box";
|
control.role = "combo box";
|
||||||
@@ -287,8 +317,8 @@ ControlDescription describe_standard_control(HWND window) {
|
|||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lstrcmpW(className, L"ListBox") == 0 ||
|
if (wine_access_class_matches(className, L"ListBox") ||
|
||||||
lstrcmpW(className, L"ComboLBox") == 0) {
|
wine_access_class_matches(className, L"ComboLBox")) {
|
||||||
find_control_label(window, label, std::size(label));
|
find_control_label(window, label, std::size(label));
|
||||||
control.name = to_utf8(label);
|
control.name = to_utf8(label);
|
||||||
control.role = "list";
|
control.role = "list";
|
||||||
@@ -320,7 +350,7 @@ ControlDescription describe_standard_control(HWND window) {
|
|||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lstrcmpW(className, L"msctls_trackbar32") == 0) {
|
if (wine_access_class_matches(className, L"msctls_trackbar32")) {
|
||||||
find_control_label(window, label, std::size(label));
|
find_control_label(window, label, std::size(label));
|
||||||
control.name = to_utf8(label);
|
control.name = to_utf8(label);
|
||||||
control.role = "slider";
|
control.role = "slider";
|
||||||
@@ -331,7 +361,7 @@ ControlDescription describe_standard_control(HWND window) {
|
|||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lstrcmpW(className, L"Static") == 0) {
|
if (wine_access_class_matches(className, L"Static")) {
|
||||||
control.name = to_utf8(text);
|
control.name = to_utf8(text);
|
||||||
control.role = "static text";
|
control.role = "static text";
|
||||||
return control;
|
return control;
|
||||||
@@ -437,6 +467,9 @@ void present_text_event(const WineAccessTextEvent &event) {
|
|||||||
std::string text = to_utf8(event.text);
|
std::string text = to_utf8(event.text);
|
||||||
ControlDescription control;
|
ControlDescription control;
|
||||||
if (event.kind == WINE_ACCESS_TEXT_PASSWORD) {
|
if (event.kind == WINE_ACCESS_TEXT_PASSWORD) {
|
||||||
|
wchar_t label[256] = {};
|
||||||
|
find_control_label(window, label, std::size(label));
|
||||||
|
control.name = to_utf8(label);
|
||||||
control.role = "edit";
|
control.role = "edit";
|
||||||
control.description = "password";
|
control.description = "password";
|
||||||
} else if (event.kind == WINE_ACCESS_TEXT_FOCUS) {
|
} else if (event.kind == WINE_ACCESS_TEXT_FOCUS) {
|
||||||
|
|||||||
@@ -99,6 +99,37 @@ 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
|
native list, normal menus, editor navigation, switching to a native Linux
|
||||||
application, and reader shutdown. Verify speech and physical braille separately.
|
application, and reader shutdown. Verify speech and physical braille separately.
|
||||||
|
|
||||||
|
## Windows Forms login fields
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xvfb-run -a python3 tests/menu_integration.py --build-dir build --fixture winforms
|
||||||
|
xvfb-run -a python3 tests/menu_integration.py --build-dir build --fixture winforms --late-helper
|
||||||
|
```
|
||||||
|
|
||||||
|
This reuses the editing workflow with native controls registered under
|
||||||
|
Windows Forms class names and labels created after the fields. It verifies
|
||||||
|
`Email, edit` focus, `Password, edit, password` with no protected value,
|
||||||
|
navigation/selection/deletion and unrelated-label exclusion. It does not run
|
||||||
|
the .NET framework itself or establish live BARD speech/braille acceptance.
|
||||||
|
The ordinary and late-helper managed fixtures passed on October 10, together
|
||||||
|
with the existing native editor, Notepad, menu and 32/64-bit list regressions.
|
||||||
|
|
||||||
|
Read-only inspection of the BARD login on October 10 confirmed a 64-bit
|
||||||
|
Windows Forms application with `WindowsForms10.EDIT.app.*` email/password
|
||||||
|
controls, adjacent visible `Email:`/`Password:` labels, and the `ES_PASSWORD`
|
||||||
|
style on the password field. Its MSAA objects were unavailable to the probe;
|
||||||
|
the previous exact `Edit` class checks missed both fields. The helper now
|
||||||
|
recognizes the managed native-control class names in its standard reader and
|
||||||
|
editor hook. Existing native controls keep their original label lookup.
|
||||||
|
|
||||||
|
After installing the updated package, close BARD and restart Cthulhu so its
|
||||||
|
helper and the application load the new DLL together. Reopen BARD in its own
|
||||||
|
prefix. Tab and Shift+Tab between Email, Password, Sign in and Work Offline.
|
||||||
|
Expect labels and edit/password roles; navigate and select ordinary email
|
||||||
|
text, and confirm password contents stay excluded. Recheck native Notepad,
|
||||||
|
menus, the installer list and switching to Linux applications. Typing echo
|
||||||
|
and routable Wine review remain separate unfinished features.
|
||||||
|
|
||||||
## Live Notepad acceptance
|
## Live Notepad acceptance
|
||||||
|
|
||||||
Install all three helper files together and use the matching Cthulhu-Go source.
|
Install all three helper files together and use the matching Cthulhu-Go source.
|
||||||
|
|||||||
+32
-4
@@ -1,6 +1,21 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
|
#ifdef WINE_ACCESS_WINFORMS_FIXTURE
|
||||||
|
static const WCHAR *editClass = L"WindowsForms10.EDIT.app.0.fixture_r7_ad1";
|
||||||
|
static const WCHAR *staticClass = L"WindowsForms10.STATIC.app.0.fixture_r7_ad1";
|
||||||
|
static void register_control(HINSTANCE instance, const WCHAR *nativeName, const WCHAR *managedName) {
|
||||||
|
WNDCLASSEXW cls = {0};
|
||||||
|
cls.cbSize = sizeof(cls);
|
||||||
|
if (!GetClassInfoExW(NULL, nativeName, &cls)) ExitProcess(3);
|
||||||
|
cls.hInstance = instance;
|
||||||
|
cls.lpszClassName = managedName;
|
||||||
|
if (!RegisterClassExW(&cls)) ExitProcess(4);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static const WCHAR *editClass = L"Edit";
|
||||||
|
#endif
|
||||||
|
|
||||||
static HWND editor, password;
|
static HWND editor, password;
|
||||||
static int step;
|
static int step;
|
||||||
|
|
||||||
@@ -115,17 +130,30 @@ static LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wp, LPARAM
|
|||||||
|
|
||||||
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
|
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
|
||||||
(void)previous; (void)args; (void)show;
|
(void)previous; (void)args; (void)show;
|
||||||
|
#ifdef WINE_ACCESS_WINFORMS_FIXTURE
|
||||||
|
register_control(instance, L"Edit", editClass);
|
||||||
|
register_control(instance, L"Static", staticClass);
|
||||||
|
#endif
|
||||||
WNDCLASSW cls = {0};
|
WNDCLASSW cls = {0};
|
||||||
cls.hInstance = instance; cls.lpszClassName = L"WineAccessEditFixture";
|
cls.hInstance = instance; cls.lpszClassName = L"WineAccessEditFixture";
|
||||||
cls.lpfnWndProc = window_proc; RegisterClassW(&cls);
|
cls.lpfnWndProc = window_proc; RegisterClassW(&cls);
|
||||||
HWND window = CreateWindowW(cls.lpszClassName, L"Wine editor fixture", WS_OVERLAPPEDWINDOW,
|
HWND window = CreateWindowW(cls.lpszClassName, L"Wine editor fixture", WS_OVERLAPPEDWINDOW,
|
||||||
120, 120, 500, 300, NULL, NULL, instance, NULL);
|
120, 120, 500, 300, NULL, NULL, instance, NULL);
|
||||||
editor = CreateWindowW(L"Edit", L"alpha beta\r\nsecond line",
|
editor = CreateWindowW(editClass, L"alpha beta\r\nsecond line",
|
||||||
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
|
WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL,
|
||||||
10, 10, 450, 150, window, NULL, instance, NULL);
|
100, 10, 350, 150, window, NULL, instance, NULL);
|
||||||
password = CreateWindowW(L"Edit", L"private-password-fixture",
|
password = CreateWindowW(editClass, L"private-password-fixture",
|
||||||
WS_CHILD | WS_VISIBLE | ES_PASSWORD, 10, 180, 300, 30,
|
WS_CHILD | WS_VISIBLE | ES_PASSWORD, 100, 180, 300, 30,
|
||||||
window, NULL, instance, NULL);
|
window, NULL, instance, NULL);
|
||||||
|
#ifdef WINE_ACCESS_WINFORMS_FIXTURE
|
||||||
|
// Labels are created after the fields, like the observed BARD login.
|
||||||
|
CreateWindowW(staticClass, L"Password:", WS_CHILD | WS_VISIBLE, 10, 183, 85, 20,
|
||||||
|
window, NULL, instance, NULL);
|
||||||
|
CreateWindowW(staticClass, L"Email:", WS_CHILD | WS_VISIBLE, 10, 13, 60, 20,
|
||||||
|
window, NULL, instance, NULL);
|
||||||
|
CreateWindowW(staticClass, L"Unrelated:", WS_CHILD | WS_VISIBLE, 10, 220, 80, 20,
|
||||||
|
window, NULL, instance, NULL);
|
||||||
|
#endif
|
||||||
HMENU menu = CreateMenu();
|
HMENU menu = CreateMenu();
|
||||||
AppendMenuW(menu, MF_STRING, 1, L"Test menu");
|
AppendMenuW(menu, MF_STRING, 1, L"Test menu");
|
||||||
SetMenu(window, menu);
|
SetMenu(window, menu);
|
||||||
|
|||||||
@@ -16,15 +16,15 @@ from gi.repository import Gio, GLib
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--build-dir", default="build")
|
parser.add_argument("--build-dir", default="build")
|
||||||
parser.add_argument("--session", action="store_true")
|
parser.add_argument("--session", action="store_true")
|
||||||
parser.add_argument("--fixture", choices=("menu", "edit", "notepad", "list"), default="menu")
|
parser.add_argument("--fixture", choices=("menu", "edit", "notepad", "list", "winforms"), default="menu")
|
||||||
parser.add_argument("--32-bit", dest="is32Bit", action="store_true")
|
parser.add_argument("--32-bit", dest="is32Bit", action="store_true")
|
||||||
parser.add_argument("--late-helper", action="store_true")
|
parser.add_argument("--late-helper", action="store_true")
|
||||||
parser.add_argument("--restart-helper", action="store_true")
|
parser.add_argument("--restart-helper", action="store_true")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.is32Bit and args.fixture != "list":
|
if args.is32Bit and args.fixture != "list":
|
||||||
parser.error("32-bit checks currently require --fixture list")
|
parser.error("32-bit checks currently require --fixture list")
|
||||||
if (args.late_helper or args.restart_helper) and args.fixture != "edit":
|
if (args.late_helper or args.restart_helper) and args.fixture not in ("edit", "winforms"):
|
||||||
parser.error("helper lifecycle checks require --fixture edit")
|
parser.error("helper lifecycle checks require --fixture edit or --fixture winforms")
|
||||||
build = Path(args.build_dir).resolve()
|
build = Path(args.build_dir).resolve()
|
||||||
if not args.session:
|
if not args.session:
|
||||||
sys.exit(subprocess.call(["dbus-run-session", "--", sys.executable,
|
sys.exit(subprocess.call(["dbus-run-session", "--", sys.executable,
|
||||||
@@ -61,8 +61,10 @@ with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
|||||||
env["WINE_ACCESS_FIXTURE_READY"] = "Z:" + str(ready).replace("/", "\\")
|
env["WINE_ACCESS_FIXTURE_READY"] = "Z:" + str(ready).replace("/", "\\")
|
||||||
fixture = Path(temporary) / "menu-fixture.exe"
|
fixture = Path(temporary) / "menu-fixture.exe"
|
||||||
compiler = "i686-w64-mingw32-gcc" if args.is32Bit else "x86_64-w64-mingw32-gcc"
|
compiler = "i686-w64-mingw32-gcc" if args.is32Bit else "x86_64-w64-mingw32-gcc"
|
||||||
|
sourceName = "edit" if args.fixture == "winforms" else args.fixture
|
||||||
subprocess.run([compiler, "-Wall", "-Wextra", "-mwindows",
|
subprocess.run([compiler, "-Wall", "-Wextra", "-mwindows",
|
||||||
"-o", str(fixture), str(Path(__file__).with_name(args.fixture + "_fixture.c")),
|
*(["-DWINE_ACCESS_WINFORMS_FIXTURE"] if args.fixture == "winforms" else []),
|
||||||
|
"-o", str(fixture), str(Path(__file__).with_name(sourceName + "_fixture.c")),
|
||||||
"-luser32", "-lcomctl32"], check=True)
|
"-luser32", "-lcomctl32"], check=True)
|
||||||
helper = None
|
helper = None
|
||||||
app = None
|
app = None
|
||||||
@@ -142,7 +144,7 @@ with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
|||||||
assert any(e[3] == "edit" and e[4] == "otepad second line" for e in text_events), "Notepad menu return missing"
|
assert any(e[3] == "edit" and e[4] == "otepad second line" for e in text_events), "Notepad menu return missing"
|
||||||
print("PASS: actual Wine Notepad keyboard characters, words, lines, selection, deletion, and menu return")
|
print("PASS: actual Wine Notepad keyboard characters, words, lines, selection, deletion, and menu return")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
if args.fixture == "edit":
|
if args.fixture in ("edit", "winforms"):
|
||||||
text_events = [e for e in events if e[0].endswith(":text")]
|
text_events = [e for e in events if e[0].endswith(":text")]
|
||||||
for event in text_events:
|
for event in text_events:
|
||||||
print(event[2:7])
|
print(event[2:7])
|
||||||
@@ -165,6 +167,12 @@ with tempfile.TemporaryDirectory(prefix="wine-access-menus-") as temporary:
|
|||||||
assert sum(e[2] == "new line deleted" and "kind=4" in e[5] for e in text_events) == 1, "CRLF Delete feedback missing"
|
assert sum(e[2] == "new line deleted" and "kind=4" in e[5] for e in text_events) == 1, "CRLF Delete feedback missing"
|
||||||
assert any(e[4] == "x" * 1022 for e in text_events), "truncated surrogate discarded line"
|
assert any(e[4] == "x" * 1022 for e in text_events), "truncated surrogate discarded line"
|
||||||
assert "d" in spoken and "undo restored" in spoken, "Undo left stale text snapshot"
|
assert "d" in spoken and "undo restored" in spoken, "Undo left stale text snapshot"
|
||||||
|
if args.fixture == "winforms":
|
||||||
|
assert any(e[2] == "Email" and e[3] == "edit" and e[4] == "alpha beta"
|
||||||
|
for e in text_events), "managed email label/focus missing"
|
||||||
|
assert any(e[2] == "Password" and e[3] == "edit" and e[6] == "password"
|
||||||
|
and e[4] == "" for e in text_events), "managed password label/protection missing"
|
||||||
|
assert not any(e[2] == "Unrelated" for e in text_events), "unrelated label associated with field"
|
||||||
print("PASS: editor focus, characters, words, lines, selection, deletion, Unicode, passwords, and read-only controls")
|
print("PASS: editor focus, characters, words, lines, selection, deletion, Unicode, passwords, and read-only controls")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
menus = [event for event in events if ":menu:" in event[0]]
|
menus = [event for event in events if ":menu:" in event[0]]
|
||||||
|
|||||||
Reference in new issue
Block a user