Read Windows Forms login fields and associate their labels

This commit is contained in:
Storm Dragon committed 2026-10-10 02:55:42 -04:00
1 parent d7ea005a1f
commit 25e38907d3
9 files changed
+149 -25

No files matched your search

+31
View File
@@ -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
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
Install all three helper files together and use the matching Cthulhu-Go source.
+32 -4
View File
@@ -1,6 +1,21 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#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 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) {
(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};
cls.hInstance = instance; cls.lpszClassName = L"WineAccessEditFixture";
cls.lpfnWndProc = window_proc; RegisterClassW(&cls);
HWND window = CreateWindowW(cls.lpszClassName, L"Wine editor fixture", WS_OVERLAPPEDWINDOW,
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,
10, 10, 450, 150, window, NULL, instance, NULL);
password = CreateWindowW(L"Edit", L"private-password-fixture",
WS_CHILD | WS_VISIBLE | ES_PASSWORD, 10, 180, 300, 30,
100, 10, 350, 150, window, NULL, instance, NULL);
password = CreateWindowW(editClass, L"private-password-fixture",
WS_CHILD | WS_VISIBLE | ES_PASSWORD, 100, 180, 300, 30,
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();
AppendMenuW(menu, MF_STRING, 1, L"Test menu");
SetMenu(window, menu);
+13 -5
View File
@@ -16,15 +16,15 @@ 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", "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("--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")
if (args.late_helper or args.restart_helper) and args.fixture not in ("edit", "winforms"):
parser.error("helper lifecycle checks require --fixture edit or --fixture winforms")
build = Path(args.build_dir).resolve()
if not args.session:
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("/", "\\")
fixture = Path(temporary) / "menu-fixture.exe"
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",
"-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)
helper = 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"
print("PASS: actual Wine Notepad keyboard characters, words, lines, selection, deletion, and menu return")
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")]
for event in text_events:
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 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"
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")
sys.exit(0)
menus = [event for event in events if ":menu:" in event[0]]