deedra b808c6686a Read command-history recalls in Wine edit fields
MUSHclient consumes queued Up/Down keys in PreTranslateMessage before the
Edit control receives WM_KEYDOWN. It recalls commands with SetWindowText
followed by SetSel at the end of the replacement. The editor subclass only
refreshed its snapshot for WM_SETTEXT, so the recalled command was silent
or followed by a stray character or blank from the caret update. Recalling
equal-length commands also leaves the final caret position unchanged.

Observe Up/Down for the focused standard Edit control in WH_GETMESSAGE,
excluding Ctrl-modified navigation. Associate the next successful
WM_SETTEXT with that retrieved key and announce the bounded recalled text
as an existing ordered text-read action. Report empty input as blank.
Consume the pending recall after the replacement and suppress the ensuing
EM_SETSEL feedback so each recalled command is announced once.

Reset recall tracking on subsequent retrieved messages, actual focus and
ordinary dispatched navigation. Preserve snapshot refreshes and normal
Left/Right reading after recalls. Keep password and foreground/focus
exclusion in place, leave unrelated programmatic text updates silent, and
check the control class before accessing the shared attachment property
so Scintilla's different state structure is untouched. Both native hook
architectures receive the fix through the existing build.

Extend the Edit fixture with application-side pretranslation of queued
history arrows. Assert the exact ordered feedback for Up/Down, equal-length
entries, rapid recalls, empty input and horizontal navigation afterward.
Require silence for unchanged history boundaries, unrelated programmatic
updates, protected fields and independently unprotected background fields.
Document history behavior and live MUSHclient acceptance steps.

Validation used a private Xorg dummy display, disposable Wine prefixes and
recording D-Bus services, with GUI checks run sequentially:

- cmake --build build --parallel
- ctest --test-dir build --output-on-failure: all four checks passed
- python3 tests/menu_integration.py --build-dir build --fixture edit
- The Edit check with --32-bit, --32-bit --late-helper, and
  --32-bit --restart-helper
- python3 tests/menu_integration.py --build-dir build --fixture winforms
  --32-bit
- python3 tests/menu_integration.py --build-dir build --fixture notepad
- python3 tests/menu_integration.py --build-dir build --fixture menu
  --32-bit
- git diff --check

The new 32-bit history regression failed against the original hook,
recording only blank actions, and passed with the fix. Final 32-bit,
64-bit and Windows Forms editor checks passed, including existing typing,
selection, deletion, Unicode, password and read-only assertions.

Built and installed the helper into the existing user-local installation,
backed up the previous five files and verified the installed artifacts.
The user subsequently confirmed that the installed fix works in MUSHclient.
Physical braille acceptance was not separately established.
2026-10-14 15:46:26 -07:00
2026-10-09 14:19:59 -04:00
2026-10-11 00:10:21 -04:00
2026-10-10 22:39:24 -04:00
2026-10-10 00:34:54 -04:00
2026-10-11 22:07:14 -07:00
2026-10-10 22:39:24 -04:00

Wine Access

wine-access is the standalone Wine-hosted accessibility helper for Cthulhu and readers that implement its Wine D-Bus protocol. An Orca endpoint is available for future Orca-side integration; stock Orca does not implement the required Wine methods.

It provides two independent capabilities:

  • an MSAA/WinEvent reader for standard Wine controls;
  • NVDA Controller 1, 2, and 3 plus Tolk compatibility for applications using nvdaControllerClient32.dll, nvdaControllerClient64.dll, or Tolk.dll.

The compatibility window is hidden and non-activating. It is expected to be invisible; the important endpoint is the local Wine RPC server registered by the helper. The helper forwards speech, braille, cancellation, and standard control events to the selected reader on the session D-Bus. The default is org.stormux.Cthulhu1.Service.

The executable is named cthulhu-wine-access.exe for compatibility with the Cthulhu-Go supervisor.

Requirements

Building requires an x86_64 Linux host with:

  • CMake 3.20 or newer;
  • Make and native GCC/G++ (the script uses CMake's default Makefile generator);
  • pkg-config and the GLib/GIO development files;
  • XCB development files for Wine-associated X11 window geometry;
  • Wine development tools: winegcc, wineg++, and widl;
  • x86_64-w64-mingw32-gcc and i686-w64-mingw32-gcc to build native menu hooks (mingw-w64-gcc on Arch Linux).

The D-Bus helper is a 64-bit Winelib executable. A small 32-bit Windows companion supplies menu and standard Edit hooks for 32-bit applications, without requiring 32-bit Linux GLib/GIO development files.

Build

On Arch Linux, the PKGBUILD provides package-managed installation and upgrades. Choose one installation method to avoid a local copy taking precedence over the system package.

The convenience script builds in ./build. Running it as a user installs to $HOME/.local/libexec/cthulhu/wine; running it as root installs system wide to /usr/libexec/cthulhu/wine:

./build.sh
sudo ./build.sh

Before building, the script checks required tools and GIO development files. If any are missing, it lists them with Arch Linux package names and an install command, then exits without building. Other distributions need equivalent development packages. Removal does not require these dependencies.

Use ./build.sh -r to remove the local installation, or sudo ./build.sh -r to remove the system installation. If a user has no local installation but a system installation exists, the script requests the latter command without running sudo itself. Removal deletes only the five helper files and leaves other directory contents untouched.

Alternative build and install locations may be supplied as positional arguments. The second argument is the installation prefix, not the final helper directory:

./build.sh /tmp/wine-access-build "$HOME/.local"
./build.sh -r /tmp/wine-access-build "$HOME/.local"

That installs the helper and its Winelib sidecar as:

$prefix/libexec/cthulhu/wine/cthulhu-wine-access.exe
$prefix/libexec/cthulhu/wine/cthulhu-wine-access.exe.so
$prefix/libexec/cthulhu/wine/wine-access-menu.dll
$prefix/libexec/cthulhu/wine/wine-access-menu32.dll
$prefix/libexec/cthulhu/wine/wine-access-menu-host32.exe

For Cthulhu-Go, either set CTHULHU_WINE_ACCESS_HELPER to the installed executable or place it at:

$HOME/.local/libexec/cthulhu/wine/cthulhu-wine-access.exe

The helper is started automatically when Cthulhu-Go detects an active Wine loader for the same user and prefix. It requires the selected reader service to be running; it exits if that service is unavailable.

Reader selection and Orca integration

The default invocation and --reader cthulhu use the existing Cthulhu endpoint. An Orca-side integration can launch the helper in the application's Wine prefix:

WINEPREFIX=/path/to/prefix /path/to/cthulhu-wine-access.exe --reader orca
Reader Session bus name and interface Object path
Cthulhu (default) org.stormux.Cthulhu1.Service /org/stormux/Cthulhu1/Service
Orca org.gnome.Orca1.Service /org/gnome/Orca1/Service

Selection applies to controller requests, accessibility events, review registration and review-capture authorization. Only one helper runs per Wine desktop/prefix, so stop the existing helper before changing readers. There is no automatic switching between services. Unknown or missing reader values are rejected. Orca must arrange helper startup and shutdown itself.

The Orca endpoint follows the versioned service in Orca's dbus_service.py. It uses the same Wine methods and signatures as Cthulhu, on the selected service interface:

Method Input signature Output signature
SpeakText sb (text, interrupt) b
CancelSpeech empty b
BrailleMessage s (text) b
SpeakSsml ss (source, SSML) i (Windows status code)
PresentAccessibleEvent sssssssiis b
RegisterWineReviewProvider (optional) ou (provider path, version) b
PresentWineReviewFocus (optional) ssstssb(iiii) b

Boolean replies indicate whether the reader accepted the request. The helper queues plain controller speech with interrupt=false; Tolk requests interruption separately through CancelSpeech. An unsupported SpeakSsml implementation should return ERROR_CALL_NOT_IMPLEMENTED (120). PresentAccessibleEvent arguments are source ID, event type, name, role, value, states, description, position, count and window title, in that order. The reader handles focus tracking, event filtering and presentation; preserve repeated editor actions and exclude password values as described below.

Orca's existing SpeakMessage and DisplayMessage methods have different names/signatures. Wine Access does not translate to those methods. An Orca contributor must implement the Wine methods above for the capabilities they want to enable. --no-controller and --no-dialog-reader can disable either capability while bringing up the integration.

Review retains the shared /org/stormux/Cthulhu1/WineReview object path and org.stormux.Cthulhu1.WineReview interface for both readers. Use the helper's unique D-Bus sender from RegisterWineReviewProvider as the destination for CaptureWindow. Only the current same-user owner of the selected reader service may capture; owning the other reader name grants no access. See the flat-review contract for focus arguments, capture replies and limits. Missing review methods leave ordinary control and controller forwarding available.

When a game closes inside a Wine virtual desktop, the desktop can regain X11 focus while Wine has no foreground application. The helper restores native focus when exactly one visible, enabled application remains. Both X11 keyboard focus and the window manager's active window must identify that same virtual desktop. Existing application focus, Linux windows, minimized/disabled windows, and ambiguous choices between multiple applications are left alone.

Protocol and behavior

nvdaController.idl contains the compatible local RPC interfaces. The helper registers the legacy NvdaCtlr endpoint and the session/desktop-specific endpoint used by newer clients. It intentionally preserves the standard controller behavior for plain speech and cancellation while returning the documented unsupported status for SSML speaking-state callbacks that Cthulhu cannot currently implement accurately.

The standard-control reader is deliberately limited to native Wine controls. Custom-drawn, DirectX, and game-specific accessibility APIs remain the responsibility of the application or its accessibility middleware.

Native menu reading uses accompanying 64-bit and 32-bit Windows hook DLLs. They observe WM_MENUSELECT inside Wine applications and sends menu labels, displayed shortcuts, submenu roles, and checked/unavailable states to the helper. The helper queues these messages before forwarding them over D-Bus, suppresses repeated selections, and restores standard-control focus reporting when menus close. The hooks apply to the helper's Wine desktop and prefix and are removed on normal shutdown. The 32-bit host attaches to the foreground 32-bit UI thread so Wine's global hook chain cannot interfere with 64-bit menus. It exits when the helper dies or its bridge window closes. Menu and editor hooks are disabled by --no-dialog-reader. The 32-bit companion attaches both hooks to the same foreground UI thread.

Focused combo boxes are checked every 100 ms to recover value changes that Wine does not report through WinEvents. The combo's internal list events use the same source identity as the focused combo, so the reader can accept the new value without a Tab/Shift+Tab round trip.

Both hook DLLs subclass standard Edit controls, including Wine Notepad's document area, MUSHclient's command field and read-only output notepad, 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 accepted typed characters, the current line on focus and menu return, characters and words on horizontal navigation, lines on vertical/page/document-boundary navigation, changed selections, and Backspace/Delete/Cut feedback. Password contents are excluded, and read-only edits do not report unsuccessful deletions. Command-history recalls in MUSHclient report the recalled command, or blank when returning to empty input. The input hook observes Up/Down before the application consumes them, so recalls also work when the caret position stays the same. Unrelated programmatic text changes remain silent. Undo refreshes the text snapshot used by subsequent navigation. It can attach to an already-focused editor and resume after the helper restarts.

The 64-bit DLL also subclasses Scintilla controls, including Notepad++'s document area. Up/Down and page navigation report the current document line; Left/Right report characters, and Ctrl+Left/Right report words. Focus and menu return read the caret's line. Ordinary selections, Backspace/Delete/Ctrl+X, Unicode characters, undo and read-only documents are supported. Text is read inside the application through Scintilla's native API, using its encoding and byte positions; no full document snapshot is required. Rectangular/multiple selection feedback and Ctrl+Backspace/Delete feedback are not implemented.

Backspace reports only the removed text, such as t. Delete and Cut retain their deletion feedback, such as t deleted.

Standard Edit typing sends each accepted character as an ordered editor action, including repeated letters, spaces, tabs, line breaks and Unicode surrogate pairs. Typing over selected text also reports the inserted character. Password fields, background controls and rejected input remain silent. Paste and programmatic text changes do not generate character echo. The reader presents these through the same feedback path as caret navigation and Backspace. Character case is preserved. Cthulhu-Go uses its configured uppercase voice and pitch for capital-letter feedback, then the normal character voice for lowercase letters. This requires the matching reader update; the helper does not set speech pitch itself.

Editor actions use PresentAccessibleEvent with event type text; actual focus uses focus. Cthulhu-Go must include the corresponding editor-event support so rapid actions and repeated characters bypass focus coalescing. Selection offsets and the action kind (8 for typed insertion) travel in the otherwise non-spoken states field for debug diagnostics. Standard edits use UTF-16 offsets; Scintilla marks its encoded byte offsets with offset_unit=byte. 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.

Application-owned MSAA queries run on one separate COM worker, with bounded pending requests and copied results. A stalled provider cannot block the helper's menu/editor event thread or normal shutdown. Focus/foreground changes retire previous results; event results expire after 500 ms. If focus queries do not finish, native control feedback uses the existing bounded fallback. Unrelated background events are rejected before querying providers; visible popups owned by the foreground window retain their accessible event path. A provider that never completes still occupies the one MSAA worker until helper exit; native menus, editing, focus fallback and controller requests remain available. No extra workers accumulate behind blocked providers.

Reader acknowledgments also run on a separate worker. Menu, editor and control events enter a bounded FIFO so a delayed D-Bus reply cannot block reception of the hooks' WM_COPYDATA messages. The event thread's success means queued; controller RPC requests still return the reader's actual status. At most 128 events wait behind the active call. Each reply has a 500 ms timeout, and normal shutdown cancels the call. Failed exchanges and queue overflow produce concise stderr diagnostics without event text. After a failed exchange, editor actions re-establish their focus dependency; timed-out text actions are never replayed. An unavailable reader can still cause feedback loss or queue overflow.

Standard SysListView32 controls now report the current row's text columns, selection and position, including 32-bit installers driven by the 64-bit helper. Wine can expose these controls only as a generic client named List1, so the helper queries the native control with target-architecture structures. When a populated list has no focused or selected row, focus reads its first row without changing selection. A bounded focus poll reports row navigation when Wine omits selection events. Up to eight columns and 1023 UTF-16 units per column are read. These queries do not add column navigation or routable object review.

Standard ListBox controls report the selected item's text and position when Up/Down changes the selection, including AudioQuake's map chooser. The same foreground poll detects changes when Wine omits selection events, with one source identity for focus and selection. Background lists remain silent.

Native tab controls report their focused caption and position; tree controls report the selected node and its expansion state. A bounded focus poll follows navigation when Wine omits selection events. These queries work with 32-bit and 64-bit controls, including standard controls registered with ATL: names. Named generic MSAA clients no longer hide known native button/combo-box roles and values. Edit and combo-box labels can be visible siblings to the left, even when the labels were created after the fields. Application-owned MSAA lists retain their own names, roles and item data.

Delphi VCL TListView, TButton, TBitBtn, and TStaticText subclasses use these same native queries. List views with standard checkboxes report checked/not-checked states and observe Space-key toggles even without a selection event. This covers GoldWave 5.70's Setup list and standard buttons; arbitrary custom Delphi controls remain outside this support.

Control values and window titles retain literal ampersands. Button and static label captions decode keyboard mnemonics and escaped &&; static labels with SS_NOPREFIX retain their literal text.

These hooks provide editing feedback. Independent review-cursor commands use the optional flat-review protocol described below; Say All and native braille routing remain unsupported. Word echo and typing echo in Scintilla remain outside this helper's current support. RichEdit and 32-bit Scintilla hooks are not covered. Reports are limited to 1023 UTF-16 units; documents over one million units retain typing and line feedback without full selection/deletion snapshots. Scintilla reads at most 4096 encoded bytes per report, then limits output to 1023 UTF-16 units without splitting surrogate pairs. Wrapped Scintilla lines report the underlying document line.

Keep all five installed files together. Native menus and standard Edit controls are supported in 32-bit and 64-bit applications; custom menus that do not send WM_MENUSELECT are not covered.

Menu integration check

After building, run this in a graphical session with Python's PyGObject available (python-gobject on Arch Linux):

python3 tests/menu_integration.py --build-dir build
python3 tests/menu_integration.py --build-dir build --fixture edit
python3 tests/menu_integration.py --build-dir build --fixture edit --32-bit
python3 tests/menu_integration.py --build-dir build --fixture notepad

The test opens a short-lived native menu window in a temporary Wine prefix, uses a private D-Bus recording service, and checks keyboard menu navigation, context menus, shortcuts, item states, and duplicate suppression. It does not send speech or braille to the running reader. Live listening with Cthulhu is a separate acceptance check.

See the test guide for helper lifecycle checks and the live Notepad speech/braille procedure. After updating the DLL, close and reopen the Wine application: existing control subclasses keep their DLL loaded until the application exits.

License

The helper source is available under the terms of LGPL-2.1-or-later. See LICENSE.

Independent Wine flat review

With the matching Go reader, existing keypad review commands read visible standard Edit text, button/static captions, native ListBox/ListView items, and usable MSAA captions/values in both 32-bit and 64-bit applications. Each command captures fresh text while preserving application focus, caret, selection, scrolling and clipboard. Password and hidden contents are excluded before text retrieval. Caption mnemonics are decoded; literal item ampersands are preserved. Review text uses the reader's normal speech and braille queues.

Custom painted game output still needs an accessible output provider, such as MUSHclient's output plugin. RichEdit/Scintilla capture, semantic object review, Say All, typing echo and native braille routing remain outside this protocol.

Protocol version 1 adds RegisterWineReviewProvider(ou) -> b and PresentWineReviewFocus(ssstssb(iiii)) -> b to the reader service. The helper exports /org/stormux/Cthulhu1/WineReview, interface org.stormux.Cthulhu1.WineReview, with CaptureWindow(sst) -> a{sv}. Only the current same-user owner of the selected reader service can capture. Replies contain exactly version:u, instance:s, window:s, focus:s, revision:t, status:s, bounds:(iiii), and nodes:aa{sv}. Nodes have id, parent, role, name strings; protected, visible, text_known, caret_known booleans; caret, caret_utf16 unsigned 64-bit values; bounds:(iiii); and ranges:a(tttts(iiii)) (rune start/end, UTF-16 start/end, text, bounds). Bounds are screen x/y/width/height. Caption text offsets remain unknown. Older readers retain ordinary focus/menu/editor feedback.

Foreground bounds come from the X11 window associated with the HWND by Wine's __wine_x11_whole_window property, using bounded XCB queries. This handles window-manager decoration differences without title-based window searches. Virtual-desktop child coordinates are translated to the screen; the physical root geometry is checked again before returning text.

Capture has a 500 ms total deadline, one active and one pending job, 4096 nodes, 64 hierarchy levels, 256 ranges per control, 4096 code points per displayed line, 65536 per control, one million native source code units, 1 MiB of text and 2 MiB of serialized data. Non-OK replies have no nodes; stale, busy, limited and unavailable captures never reuse cached text. Capture runs on a separate COM worker so blocked MSAA providers do not block menu/focus events. Scalar Edit queries are batched so applications with periodic message loops can return visible text within the capture deadline. Native container children are enumerated once, and a maximized MDI document excludes the covered documents. See tests/README.md for checks and live acceptance.

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.

S
Description
No description provided
Readme LGPL-2.1
1.5 MiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 46.8%
C 30.8%
Python 18.8%
CMake 2.1%
Shell 1.5%