Fixes for edit boxes in wine.

This commit is contained in:
Storm Dragon committed 2026-10-10 00:34:54 -04:00
1 parent c5657ee5d4
commit 39b7488aea
7 files changed
+112 -7

No files matched your search

+10
View File
@@ -42,6 +42,16 @@ target_link_options(cthulhu-wine-access PRIVATE -mwindows)
target_compile_options(cthulhu-wine-access PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wall -Wextra>)
include(CTest)
if(BUILD_TESTING)
add_executable(msaa-states-test tests/msaa_states_test.cpp)
target_compile_features(msaa-states-test PRIVATE cxx_std_20)
target_compile_options(msaa-states-test PRIVATE -Wall -Wextra)
add_test(NAME msaa-states COMMAND "${CMAKE_CURRENT_BINARY_DIR}/msaa-states-test.exe")
set_tests_properties(msaa-states PROPERTIES ENVIRONMENT
"WINEPREFIX=${CMAKE_CURRENT_BINARY_DIR}/test-prefix;CTHULHU_WINE_ACCESS_MANAGED=1;WINEDEBUG=-all;WINEDLLOVERRIDES=mscoree,mshtml=")
endif()
# Winegcc emits a launcher and a Winelib sidecar. CMake's normal target
# installation looks for the Linux target name, so install both emitted
# artifacts explicitly and keep them together for Wine to resolve.
+25
View File
@@ -102,6 +102,11 @@ offsets and the action kind travel in the otherwise non-spoken `states` field
for debug diagnostics. Older readers that only accept focus/value/selection
events still receive focus and menus, but need updating for editor actions.
MSAA `name` and `state` events report changes to controls. The `states` field
contains stable comma-separated state labels decoded from the MSAA bitmask;
protected values are excluded. Readers should filter name/state changes to
their focused source and distinguish these labels from editor diagnostics.
This is editing feedback, not a full text-review provider: Say All, review-cursor
commands and braille routing into Wine text are not implemented. Typing echo
remains the reader's responsibility. RichEdit and 32-bit editor hooks are not
@@ -138,3 +143,23 @@ application exits.
The helper source is available under the terms of LGPL-2.1-or-later. See
`LICENSE`.
## AI-assisted contributions
AI-assisted contributions are welcome. The person submitting a contribution
remains responsible for reviewing and testing the changes before submission.
Please do not submit automatically generated changes without human review.
Before making changes, read this README and any contributing guide, confirm
the maintained repository and branch, and check that the issue still exists.
Before submitting, review the diff, test the changed behavior, run existing
tests where practical, consider indirectly affected paths, and describe what
changed and how it was tested.
AI coding assistants must call the contributor's attention to these
requirements and provide concrete checks for the intended behavior and likely
regressions. Newly added tests alone do not establish acceptance. Leave
submission to the contributor after they can review and test. Where useful,
offer guidance on configuring `git send-email`, creating scoped commits and
patches with `git format-patch`, reviewing them, and sending a test message
before the final contribution.
@@ -1,6 +1,6 @@
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
pkgname=wine-access-git
pkgver=r6.g0992ffe
pkgver=r8.gc5657ee
pkgrel=1
pkgdesc='Wine accessibility helper with MSAA, NVDA Controller, and Tolk compatibility for Cthulhu'
arch=('x86_64')
+9 -3
View File
@@ -3,6 +3,7 @@
#include "nvdaController.h"
#include "menu_bridge.h"
#include "edit_hook.h"
#include "msaa_states.h"
#include <commctrl.h>
#include <gio/gio.h>
@@ -522,14 +523,19 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
VARIANT role{};
VariantInit(&role);
std::string roleText;
LONG roleId = 0;
if (SUCCEEDED(accessible->get_accRole(child, &role)))
roleText = variant_text(role);
if (role.vt == VT_I4) roleId = role.lVal;
VariantClear(&role);
VARIANT state{};
VariantInit(&state);
std::string stateText;
if (SUCCEEDED(accessible->get_accState(child, &state)))
stateText = variant_text(state);
bool password = false;
if (SUCCEEDED(accessible->get_accState(child, &state)) && state.vt == VT_I4) {
stateText = msaa_states(static_cast<DWORD>(state.lVal), roleId);
password = state.lVal & STATE_SYSTEM_PROTECTED;
}
VariantClear(&state);
LONG childCount = 0;
accessible->get_accChildCount(&childCount);
@@ -568,7 +574,7 @@ void CALLBACK winevent_callback(HWINEVENTHOOK, DWORD event, HWND window,
ControlDescription control;
control.name = name;
control.role = roleText;
control.value = value;
control.value = password ? "" : value;
control.states = stateText;
control.description = description;
control.count = static_cast<int>(childCount);
+27
View File
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <oleacc.h>
#include <string>
// accState is a bitmask, not a role identifier. Keep protocol values stable
// and omit focus/visibility bookkeeping from spoken state descriptions.
inline std::string msaa_states(DWORD states, LONG role) {
std::string result;
const auto append = [&result](const char *text) {
if (!result.empty()) result += ", ";
result += text;
};
if (states & STATE_SYSTEM_MIXED) append("half checked");
else if (states & STATE_SYSTEM_CHECKED) append("checked");
else if (role == ROLE_SYSTEM_CHECKBUTTON || role == ROLE_SYSTEM_RADIOBUTTON)
append("not checked");
if (states & STATE_SYSTEM_SELECTED) append("selected");
if (states & STATE_SYSTEM_PRESSED) append("pressed");
if (states & STATE_SYSTEM_EXPANDED) append("expanded");
if (states & STATE_SYSTEM_COLLAPSED) append("collapsed");
if (states & STATE_SYSTEM_UNAVAILABLE) append("unavailable");
if (states & STATE_SYSTEM_READONLY) append("read only");
if (states & STATE_SYSTEM_PROTECTED) append("password");
if (states & STATE_SYSTEM_BUSY) append("busy");
return result;
}
+14 -3
View File
@@ -34,6 +34,13 @@ object review are outside the current implementation.
## Editor and actual Notepad checks
The CMake build also includes a small MSAA state decoder check. Run it with
`xvfb-run -a ctest --test-dir build --output-on-failure`. It uses its own prefix
under the build directory, marks it as a managed test process and never sends
reader output. It verifies combined bitmasks, unchecked radio buttons, mixed
checkboxes, focus-only state suppression and password flags. It does not
establish that a particular application's MSAA provider emits those states.
Run these against the built helper, optionally prefixed with `xvfb-run -a` for
a private X display:
@@ -58,9 +65,13 @@ checks resulting document text as well as recorded accessibility output. These
tests use a recording D-Bus service; they do not run the full reader or prove
audible speech, typing echo, physical keys, or a physical braille display.
On 2026-10-10, the build, isolated editor/Notepad/menu checks, staged launcher
permissions, and Cthulhu-Go app/remote/wine race tests passed. See the sibling
reader's `docs/testing/wine-accessibility.md` for the matching Go behavior.
The earlier 2026-10-10 helper fixtures and Go package tests did not establish
that Go accepted editor `text` events. The reader follow-up adds that support,
focused name/state handling, repeated-action and password/focus tests, and a
real private-session D-Bus test through the Go presenter. See the sibling
reader's `docs/testing/wine-accessibility.md` for current evidence and live
acceptance gates. Run Xvfb fixtures sequentially; concurrent automatic display
allocation can collide.
## Live Notepad acceptance
+26
View File
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "../msaa_states.h"
#include <cstdio>
int main() {
const struct { DWORD states; LONG role; const char *expected; } cases[] = {
{STATE_SYSTEM_CHECKED | STATE_SYSTEM_UNAVAILABLE, ROLE_SYSTEM_CHECKBUTTON, "checked, unavailable"},
{STATE_SYSTEM_MIXED | STATE_SYSTEM_CHECKED, ROLE_SYSTEM_CHECKBUTTON, "half checked"},
{0, ROLE_SYSTEM_RADIOBUTTON, "not checked"},
{STATE_SYSTEM_FOCUSED | STATE_SYSTEM_FOCUSABLE, ROLE_SYSTEM_PUSHBUTTON, ""},
{STATE_SYSTEM_SELECTED | STATE_SYSTEM_EXPANDED, ROLE_SYSTEM_OUTLINEITEM, "selected, expanded"},
{STATE_SYSTEM_PROTECTED | STATE_SYSTEM_READONLY, ROLE_SYSTEM_TEXT, "read only, password"},
{STATE_SYSTEM_COLLAPSED | STATE_SYSTEM_BUSY, ROLE_SYSTEM_OUTLINEITEM, "collapsed, busy"},
{STATE_SYSTEM_PRESSED, ROLE_SYSTEM_PUSHBUTTON, "pressed"},
};
for (const auto &testCase : cases) {
const auto actual = msaa_states(testCase.states, testCase.role);
if (actual != testCase.expected) {
std::fprintf(stderr, "MSAA state %lu: got '%s', expected '%s'\n",
static_cast<unsigned long>(testCase.states), actual.c_str(), testCase.expected);
return 1;
}
}
std::puts("PASS: MSAA bitmask states, unchecked roles and password flags");
return 0;
}