Removed unused directory.

This commit is contained in:
Storm Dragon committed 2026-10-12 18:10:15 -04:00
1 parent d9aa40e16c
commit 4f2f72e945
2 files changed
-415

No files matched your search

@@ -1,182 +0,0 @@
# Wine Flat Review Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking. Native execution is recommended for these closely connected interfaces.
**Goal:** Make Cthulhu's existing flat-review keys read exposed visible Wine text without moving the application's caret, selection, or focus.
**Architecture:** wine-access supplies owned window snapshots through an optional D-Bus capture endpoint. A Go provider registry and capturer composition feed those snapshots to the existing review worker and flat cursor. Event-loop foreground ownership and provider lifetime checks reject stale results.
**Tech Stack:** C++20 Winelib, Wine Unicode/Win32/MSAA APIs, existing GLib/GIO and XCB; Go 1.24+, existing godbus and AT-SPI snapshot/review code; Python/PyGObject and XTest private-display fixtures. Delivery testing required an explicit libxcb link/development dependency for actual Wine-associated X11 geometry; XCB was already installed on the tested system.
**Spec:** [Approved Wine flat-review design](../specs/2026-10-11-wine-flat-review-design.md).
## Global Constraints
* Preserve existing uncommitted 32-bit Edit/menu changes and `cthulhu.log`.
* Keep desktop/laptop review bindings, speech/spelling/phonetics, braille queues, and silent movement boundaries.
* Never move native focus/caret/selection, scroll, copy to the clipboard, or inject application navigation keys to implement review.
* Password text is excluded before copying, serialization, presentation, and debug logging.
* Capture deadline: 500 ms total; one active native capture and one pending capture; command FIFO remains 16.
* Limits: 4096 nodes, depth 64, 256 ranges per control, 4096 code points per visual line, 65536 captured code points per control, 1 MiB text per window, 2 MiB serialized snapshot, one million UTF-16 units of full Edit source reads per window.
* Captions: 4096 UTF-8 bytes; identities: 256 UTF-8 bytes. Reject duplicate IDs, cycles, malformed UTF-8, offsets, bounds, and versions.
* Return complete snapshots or an explicit non-ok status; at most one stale recapture under the original deadline.
* Preserve `PresentAccessibleEvent`, NVDA/Tolk, optional-helper behavior, Linux review, and XTerm/Fenrir handoff.
* Core input/event loops never perform native capture or synchronous D-Bus capture calls.
* No native pointers/COM objects cross the bus. Keep each native object's release on its COM STA.
* Xorg correctness is required; private displays run sequentially. Distinguish recorded output, installed-binary checks, and live speech/braille acceptance.
* Exposed standard Edit/native/MSAA text is supported in both architectures. Custom painted text, RichEdit/Scintilla text providers, semantic object navigation, Say All, and native braille routing are outside this change.
* Use tool approval before writing reader source or installed files outside writable roots. Read applicable AGENTS.md before edits. Leave submission and human acceptance to the contributor.
## Review Focus
1. A blank command field must anchor review of other visible text; test in Tasks 1 and 6.
2. Same-length text replacements during capture must not produce mixed old/new text; test in Tasks 1 and 2.
3. Two applications with identical titles must not share review ownership; test in Tasks 4 and 6.
4. Negative monitor coordinates and Wine virtual-desktop child focus must match current foreground geometry; test in Tasks 1, 6, and 7.
5. A stalled MSAA call must leave menus, input, and shutdown responsive; test in Tasks 2 and 7.
## File ownership and interface map
Paths prefixed `reader/` mean `/home/deedra/git/cthulhu-go`; other paths belong to this standalone helper repository. These describe source responsibilities, not new packages for every file.
* Helper `review_capture.h/.cpp`: owned capture records, offsets, visibility, native lifetime identity, and window/control capture.
* Helper `review_service.h/.cpp`: D-Bus serialization, authorization, registration, bounded STA capture worker, and cancellation/lifetime handling.
* Helper `main.cpp`: start/stop service, post focus/lifecycle notices; retain existing event/menu policy.
* Helper `tests/review_fixture.c`, `tests/review_integration.py`, `tests/review_capture_test.cpp`: native behavior and transport tests.
* Reader `internal/wine/review_protocol.go`, `review_snapshot.go`: wire types, limits, and conversion to owned trees.
* Reader `internal/wine/review_registry.go`, `review_client.go`: connection ownership, registered helper state, focus mailbox, capture calls, and provider disappearance.
* Reader `internal/remote/wine_review.go`: optional authorized registration/focus methods and introspection.
* Reader `internal/app/wine_review_capture.go`, `wine_review_focus.go`: capturer composition and foreground/focus/result policy respectively.
* Reader `internal/review/flat_worker.go`: optional fresh-capture policy; preserve the existing flat navigation algorithm.
* Reader runtime/event files: dependency wiring and event ordering only.
## Task 1: Read-only native visible-window capture
**Files:** Create `review_capture.h`, `review_capture.cpp`, `tests/review_fixture.c`, `tests/review_capture_test.cpp`; modify `CMakeLists.txt`.
**Interfaces produced:**
* `ReviewFocus { std::string instance, window, focus, title, role; uint64_t revision; bool protected_text; RECT bounds; }`.
* `ReviewRange { uint64_t start, end, utf16_start, utf16_end; std::string text; RECT bounds; }`.
* `ReviewNode { std::string id, parent, role, name; bool protected_text, visible, text_known, caret_known; uint64_t caret, caret_utf16; RECT bounds; std::vector<ReviewRange> ranges; }`.
* `ReviewCapture { ReviewFocus source; std::string status; std::vector<ReviewNode> nodes; }`.
* `class ReviewIdentityStore`: `ReviewFocus observe_foreground()`, `void destroyed(HWND, LONG object, LONG child)`, `void reordered(HWND, LONG object)`, `void retire()`; synchronize lifetime registry access between native event and capture threads. Revisions change on focus/lifetime changes, not text changes.
* `ReviewCapture capture_review_window(ReviewIdentityStore&, const ReviewFocus&, uint64_t deadline_tick)`; deadline uses `GetTickCount64()` and all source/size limits above.
- [x] Write `tests/review_capture_test.cpp` and fixture cases named `visible_unicode`, `wrapped_and_blank`, `empty_anchor`, `password`, `clipped_hidden`, `same_length_update`, `native_controls`, `source_limit`, and `negative_coordinates`. Assert exact exposed text/rune offsets, unchanged focus/selection, and `limited` with zero nodes at limits. Include a surrogate before the first visible range so absolute rune offsets cannot be computed from visible text alone.
- [x] Add CTest entries `review-capture64` and `review-capture32`. Compile fixture binaries `build/review-fixture64.exe` and `build/review-fixture32.exe` with the existing MinGW compilers; `review-capture-test.exe --fixture <path>` launches and checks each fixture under its isolated prefix. Run `ctest --test-dir build -R review-capture --output-on-failure` on a private display before production capture exists; confirm the failure names the missing capability rather than fixture/setup failure.
- [x] Implement identities and capture. Query native Edit text/positions, visible line/wrap ranges and caret without edits; traverse bounded MSAA/native controls, omit protected contents, strip caption mnemonics only, and preserve empty focus as a node. Map each absolute UTF-16 position through the bounded source read. Verify source identity and reread collected ranges before returning, including same-length updates.
- [x] Build with `cmake --build build --parallel`; run the focused capture test on a private display with both fixture architectures. PASS means expected text and positions, no password token anywhere, and unchanged native state. Run `git diff --check` and review this task's diff.
Exact offset assertion for an offscreen prefix `😀alpha\n` followed by visible `beta`:
```cpp
assert(range.start == 7 && range.end == 11);
assert(range.utf16_start == 8 && range.utf16_end == 12);
assert(range.text == "beta");
```
## Task 2: Optional bounded helper capture service
**Files:** Create `review_service.h`, `review_service.cpp`, `tests/review_integration.py`; modify `main.cpp`, `CMakeLists.txt`, `tests/review_fixture.c`.
**Consumes:** Task 1 records, identity store, and capture function.
**Produces:** `class ReviewService` with `bool start(GDBusConnection*, ReviewIdentityStore&)`, `void focus_changed(const ReviewFocus&)`, and `void stop()`; exact version-1 methods/dictionary in the spec. No extra installed artifact.
- [x] Write private-session tests `test_capture_wire_contract`, `test_wrong_caller`, `test_old_reader`, `test_busy_capture`, `test_capture_deadline`, `test_same_length_capture_race`, and `test_stop_during_capture`. Assert an unavailable optional reader does not stop ordinary helper speech; stale/busy/limited replies have zero nodes; a blocked capture does not delay existing menu/focus events; shutdown remains bounded.
- [x] Run `python3 tests/review_integration.py --build-dir build --32-bit` on a private display and confirm missing endpoint/registration is the red failure.
- [x] Implement native records-to-`a{sv}` conversion and exact dictionary field types. Register `/org/stormux/Cthulhu1/WineReview` on the existing unique connection; authorize the current same-user Cthulhu service owner. Own a dedicated GLib main context/dispatcher thread because the current helper's Win32 message loop does not dispatch a GLib main context. Dispatch native work to a separate COM STA; the dispatcher must reject excess work even when capture blocks. Reply once per invocation, expire pending work within the original 500 ms, and release native references on their STA. No forced thread termination.
- [x] Wire start/stop and focus/destruction/reorder notices from the existing event loop. Poll recovers missed focus events without registering a new provider per notice. Retry registration when a compatible reader owner appears. Continue existing speech if methods are unavailable.
- [x] Run both `python3 tests/review_integration.py --build-dir build` and its `--32-bit` variant sequentially; PASS verifies typed schema, authorization, native state, bounded busy behavior, and older-reader compatibility. Re-run existing menu/editor fixtures because startup and shutdown changed. Review the diff.
## Task 3: Validate snapshots and build Go review trees
**Files:** Create `reader/internal/wine/review_protocol.go`, `review_snapshot.go`, `review_snapshot_test.go`.
**Interfaces produced:** `ReviewFocusNotice` mirrors the spec focus fields and includes actual sender/path copied from registration; `ReviewSnapshot`/`ReviewNode`/`ReviewRange` mirror wire records with `uint64` offsets and `atspi.Rectangle` bounds. `DecodeReviewSnapshot(fields map[string]dbus.Variant) (ReviewSnapshot, error)` validates types/limits; `ApplyReviewSnapshot(tree *atspi.AccessibleTree, snapshot ReviewSnapshot, sender string) (*atspi.AccessibleTree, atspi.ObjectID, error)` creates or replaces a stable owned tree. `IsReviewID(atspi.ObjectID) bool` checks the exact reserved namespace. Define sentinel errors `ErrReviewUnavailable`, `ErrReviewStale`, `ErrReviewBusy`, `ErrReviewLimited`, and `ErrReviewInvalid` for status and validation handling.
- [x] Write `TestReviewSnapshotUnicodeAndStableTree`, `TestReviewSnapshotEmptyAnchor`, `TestReviewSnapshotProtectedContent`, and table-driven `TestReviewSnapshotRejectsInvalidPayload`. Assert code-point counts, valid `/wine/` identities, unchanged tree pointer/live generations after text updates, new generations after removal/reuse, and rejection of every spec bound plus cycles, duplicates, malformed nested types, offset overflow, and clipped-range violations.
- [x] Run `GOCACHE=/tmp/cthulhu-go-cache go test ./internal/wine -run ReviewSnapshot -count=1` from the reader checkout; confirm tests fail for the missing protocol/conversion.
- [x] Implement exact dictionary decoding and conversion. Opaque IDs map to safe object-path components; root has window/dialog role; protected controls become `password text` with no ranges/value. Use existing tree validation/Replace rather than a parallel tree model. Never convert `uint64` to `int` before range checking.
- [x] Re-run the focused tests and `go test -race ./internal/wine -count=1`; PASS includes the existing manager tests. Review the diff.
The invalid-payload table must assert rejection rather than acceptance of partial data:
```go
snapshot, err := DecodeReviewSnapshot(fields)
if err == nil || len(snapshot.Nodes) != 0 {
t.Fatalf("invalid payload accepted: nodes=%d err=%v", len(snapshot.Nodes), err)
}
```
## Task 4: Provider registration, lifetime, and bounded Go capture
**Files:** Create `reader/internal/wine/review_registry.go`, `review_client.go`, `review_registry_test.go`, `review_client_test.go`, `reader/internal/remote/wine_review.go`, `wine_review_test.go`; modify `reader/internal/remote/service.go`, `introspection.go`.
**Consumes:** Task 3 wire records, errors, and tree conversion.
**Produces:** `NewReviewRegistry(logger *slog.Logger) *ReviewRegistry`; registry methods `Start(context.Context) error`, `Stop(context.Context) error`, `RegisterProvider(sender string, path dbus.ObjectPath, version uint32) bool`, `PresentFocus(sender string, notice ReviewFocusNotice) bool`, `Changes() <-chan struct{}`, `Focuses() []ReviewFocusNotice`, `Capture(context.Context, atspi.ObjectID) (*atspi.AccessibleTree, error)`, and `Current(atspi.ObjectID) bool`. `Changes` is a capacity-one notification; registry retains latest state per sender, not one lossy notice for all prefixes. Remote optional `WineReviewHandler` has the RegisterProvider/PresentFocus signatures; expose spec method names through authorized wrappers using the actual `dbus.Sender`.
- [x] Write `TestReviewRegistryTwoProviders`, `TestReviewRegistrySenderAssociation`, `TestReviewRegistryProviderDisappears`, `TestReviewCaptureDeadlineAndStaleReply`, and real private-bus `TestNativeSessionBusWineReview`. Assert unique connections/instances do not collide even for matching HWND/title; owner disappearance removes focus/tree; re-registration retires the old instance; focus notices from an unregistered or unauthorized sender are rejected; capture returns by 500 ms and Stop cancels work.
- [x] Run `go test ./internal/wine ./internal/remote -run 'ReviewRegistry|ReviewCapture|WineReview' -count=1`; confirm the red result is the missing registry/endpoint behavior.
- [x] Implement same-user service wrappers and introspection without changing existing Handler implementations. Registry owns a separate session-bus connection for captures/disconnection monitoring, unregisters its match rules on Stop, and targets only registered unique senders. Apply one original deadline, one stale retry, and exact expected identity/revision checks to capture; cache stable trees per provider/window lifetime, never cached text as a substitute for a successful current capture.
- [x] Run unit/race checks, then `dbus-run-session -- env CTHULHU_NATIVE_DBUS_TEST=1 GOCACHE=/tmp/cthulhu-go-cache go test -race ./internal/wine ./internal/remote -run NativeSessionBusWineReview -count=1`. PASS proves actual wire registration, capture, and disappearance. Review the diff.
## Task 5: Feed fresh Wine snapshots to the existing review worker
**Files:** Create `reader/internal/app/wine_review_capture.go`, `wine_review_capture_test.go`; modify `reader/internal/review/worker_contract.go`, `flat_worker.go`, `flat_worker_test.go`, and `reader/internal/app/runtime_review.go`.
**Consumes:** `ReviewRegistry.Capture` and `IsReviewID`.
**Produces:** `type review.FreshReviewWindowCapturer interface { FreshReviewWindow(atspi.ObjectID) bool }`. App `reviewCaptureRouter` composes the existing `review.TreeCapturer` and registry, implements its four capture methods plus `FreshReviewWindow`. Wine review uses registry capture; document capture rejects Wine IDs; native IDs delegate exactly as before.
- [x] Write `TestFlatWorkerFreshProviderPreservesCursor`, `TestFlatWorkerFreshProviderFailureDoesNotSpeakCachedText`, `TestReviewCaptureRouterNativeDelegation`, and `TestReviewCaptureRouterWineIDsNeverReachATSPINativeCalls`. Read `alpha beta`, move independently, replace the same live control's text, then assert the next command sees new text at the preserved offset. A native-provider spy must see no Wine object paths.
- [x] Run `go test ./internal/review ./internal/app -run 'FreshProvider|ReviewCaptureRouter' -count=1`; confirm the missing fresh-provider policy is the failure.
- [x] In `readFlat`, detect the optional policy and capture once per Wine command before cursor refresh. Keep capture on the review worker, apply the 500 ms Wine timeout, preserve the same tree through Replace, and prohibit repeated capture retries beyond the one allowed under the original deadline. Leave the existing AT-SPI stale-capture path and navigation algorithm intact.
- [x] Map `wine.ErrReviewLimited` to `review.ErrLimitedReview` in the router so the established limit message is used. Inject the composed capturer through runtime review construction. Run focused tests and existing flat-worker/cursor tests with race checks; PASS includes all review units and queued spelling styles. Review the diff.
## Task 6: Own Wine review focus and output in the event loop
**Files:** Create `reader/internal/app/wine_review_focus.go`, `wine_review_focus_test.go`; modify `app.go`, `runtime_remote.go`, `live_settings.go`, `event_loop.go`, `event_review_results.go` and the focused lifecycle/focus tests only as needed.
**Consumes:** registry focus snapshots/change notifications, `Current`, router from Task 5, existing foreground/title source and review worker.
**Produces:** `wineReviewFocusController` with `changed()`, `foregroundChanged()`, `retire()`, `owns(atspi.ObjectID) bool`, and `resultCurrent(review.Result) bool`. Event-loop-owned active state stores provider, window/focus lifetime, native source revision, foreground epoch, and root bounds. Registry connection/capture state stays outside the event loop.
- [x] Write `TestWineReviewEmptyFocusCanReachOutput`, `TestWineReviewIdenticalTitlesDoNotClaimForeground`, `TestWineReviewForegroundAndVirtualDesktop`, `TestWineReviewDropsOldResultsAndRestarts`, and `TestWineReviewSpeechBrailleAndSpelling`. Assert matching geometry/epoch ownership, negative coordinates, virtual desktop child identity, duplicate notices preserving position, provider restart/lock invalidation, and restored Linux focus. Check exact existing line/word/character/spelling requests and password-free braille output.
- [x] Run `go test ./internal/app -run WineReview -count=1`; confirm missing Wine review ownership is the red failure.
- [x] Wire one registry instance to review construction and remote registration. Add a lifecycle entry whose shutdown cancels registry work before its connection closes; reuse the existing reader supervisor. Event-loop callbacks reconcile registry notices with current Xorg foreground geometry/title-child metadata and worker focus. Ambiguous/unknown ownership rejects Wine review; raw titles alone cannot establish it.
- [x] Before presenting flat results, synchronize current foreground and require matching provider/focus lifetime/revision/epoch. On a revision transition, call `ClearFocus` to retire the old worker document epoch before restoring any new focus, even when the same control identity returns; duplicate notices without a transition preserve position. Retire queued work on focus loss, lock, destruction, or provider death. Prevent native events for an earlier Linux window from overwriting current Wine review focus. Keep policy in the controller and event ordering in the loop.
- [x] Run app/review/remote/wine race checks and existing Linux flat-review, foreground, titles, password, spelling, and braille tests. PASS proves shared behavior and output queues; it is not a live speech claim. Review the diff.
## Task 7: Prove physical key routing through both architectures
**Files:** Create `reader/tests/wine_review_x11.py`, `reader/internal/app/wine_review_native_test.go`; extend helper `tests/review_fixture.c`/`review_integration.py` with deterministic handshake and stalled-capture modes.
**Consumes:** installed-shaped helper build, real Go service/command router, registry and capture paths. Use the existing private X11 fixture's physical XTest keycode helper, not modifier-changing keysym synthesis.
- [x] Write a private-session/display acceptance fixture that fails unless keypad line/word/character, spelling/phonetic presses, window start/end, line end and vertical-word review produce expected text. Assert fixture focus, caret, selection and document content remain unchanged; ordinary application arrow keys still work afterward.
- [x] Add focus-switch, negative-coordinate, virtual-desktop, two-prefix, reader/helper restart, and streaming-output phases. Stall capture deliberately and prove menu/focus speech still arrives, key release remains balanced, Linux review responds, cancellation joins within the normal shutdown budget, and no late Wine text is spoken after switching away.
- [x] Run the new fixture before final wiring is considered complete; investigate any failure at the helper/registry/focus/command/output boundary instead of weakening assertions.
- [x] Run both architectures sequentially, then the existing helper MSAA/menu/edit/list/control fixtures and focused reader native Xorg/Fenrir handoff checks. Record command, exit status, capture/input timing and final output evidence; PASS requires real X11 command consumption and correct queued speech/braille. Physical listening remains a separate user check.
## Task 8: Review, document, build, and deliver matching binaries
**Files:** Update helper `README.md`, `tests/README.md`; reader `docs/testing/wine-accessibility.md`, `MIGRATION_PLAN.md` only for scope/evidence/gaps. Source changes remain in their owning repositories.
- [x] Review both complete diffs against the spec, including unrelated-file preservation and password exclusion. Obtain one independent whole-change code review for native execution, or the selected task reviews if subagent execution was chosen; resolve verified findings and rerun affected tests.
- [x] Run `git diff --check` in both repositories, build the helper and reader, and run only outstanding relevant checks after the last edits. Do not broaden testing again without a new failure/change/concern.
- [x] Update docs with exact protocol/support limits and actual command evidence. Clearly distinguish private fixtures, full-router/native checks, installed-binary verification and remaining MUSHclient listening/braille checks. Remove the now-resolved Wine flat-review gap while preserving excluded providers/routing gaps.
- [x] Prepare matching binaries and backups, then use tool approval to install outside writable roots. Replace mapped helper artifacts atomically. Reload the helper/reader through verified lifecycle paths while preserving MUSHclient and the current game connection. Verify the new processes stay alive and registration/capture uses the new protocol.
- [x] Provide concise live checks: current/previous/next line in output notepad, word/character/spelling, independent caret, new incoming output, plugin dialogs, and return to Linux/Fenrir. Keep changes reviewable and leave contribution submission to the user.
## Execution prerequisites and handoff
The user approved the spec, plan and native inline execution on 2026-10-11.
Tasks 1–8 are complete. Independent whole-change review and its verified fix
pass are complete. Matching binaries are installed and the actual desktop
check passed with the current MUSHclient process and game socket preserved.
The isolated execution ledger records exact checks, decisions and live gates.
At execution start, apply the chosen execution skill and its isolation rules.
Inspect both repositories' working state; preserve a scoped baseline of the
existing helper changes, and obtain predictable tool escalation for repository
metadata or reader-source writes outside writable roots. Do not discard or
automatically submit the current work. Track task completion in this document.
@@ -1,233 +0,0 @@
# Cthulhu review keys in Wine
Status: approved, implemented, verified and installed on 2026-10-11. Independent review fixes and matching-binary desktop checks completed; MUSHclient listening and physical braille acceptance remain live checks.
## Intended outcome
The user wants the existing Cthulhu review keys to work in Wine, especially
MUSHclient running in `~/.local/bard`. Keep the current desktop keyboard
bindings. Review must read and navigate independently of application focus,
caret, selection, and keyboard input. The user confirmed this scope on
2026-10-11.
Support current/previous/next line, word, and character; current-word spelling
and phonetics; current-character phonetics; window start/end; word above/below;
and line end. Reuse current speech and braille presentation and boundary
behavior. This is flat text review, not semantic parent/child object navigation,
Say All, editing, mouse activation, or braille routing into Windows controls.
Cover exposed visible text and controls in 32-bit and 64-bit Wine applications.
The concrete MUSHclient cases are its command Edit and read-only output notepad.
Its custom game display does not become readable merely by adding this bridge;
the existing output plugin remains the source of reviewable game text.
## Existing behavior and cause
`wine-access/main.cpp` supplies normalized focus/menu/editor events to
Cthulhu's session D-Bus service. The 32-bit standard Edit feedback change is
already implemented and locally installed. Preserve that work.
`cthulhu-go/internal/app/runtime_commands.go` sends review commands to
`review.Worker`. Its `CaptureReviewWindow` provider currently captures only
AT-SPI objects. Wine focus events go to a separate speech presenter and never
establish a review provider or a text snapshot. A spoken caret line is not a
reviewable window.
`review.FlatCursor` already navigates immutable window trees and preserves a
position through stable object identities. `AccessibleTree.Replace` preserves
generations of live identities. Reuse these mechanisms instead of implementing
a second Wine-specific navigation algorithm or reviewing cached speech.
## Architecture
### Wine capture provider
Add a focused capture module beside `main.cpp`, rather than putting enumeration
and serialization in its event loop. The helper publishes a read-only D-Bus
capture endpoint on its existing unique session-bus connection. Multiple Wine
prefixes therefore have independent endpoints without competing for one name.
Capture uses a dedicated COM STA worker, independent of the existing native
event/menu thread. Permit one active capture and one pending capture; reject
additional requests promptly. Native accessibility objects stay on that STA
and are released there. Application queries use `SendMessageTimeoutW` where
available. A COM call that does not return cannot be forcibly cancelled safely;
the Go deadline discards its result, and the busy provider rejects further
capture work instead of accumulating it. Helper shutdown remains bounded by
the supervisor's existing process-stop policy.
Collect a complete snapshot of the foreground Wine window/dialog: hierarchy,
stable identities, roles, captions, protected states, clipped screen bounds,
visible text ranges, and the focused Edit's caret when available. Traverse MSAA
objects and use existing standard-control queries when Wine exposes only a
generic client. Standard Edit includes Windows Forms Edit subclasses, in both
architectures. Preserve mnemonic decoding for captions and literal ampersands
in values. Password text is excluded before copying or serializing it.
For Edit text, collect displayed visual lines, including soft wraps and blank
rows, with native character positions and client clipping. Do not scroll,
select text, change focus, send navigation keys, or copy to the clipboard.
The helper converts UTF-16 offsets to Unicode code-point offsets before
serialization, retaining native offsets for diagnostics. Computing absolute
offsets requires the preceding source text, so bound full Edit source reads at
one million UTF-16 units in total per window and return `limited` above that
bound. Only displayed ranges cross D-Bus. No approximate character geometry
is presented as native.
First implementation uses standard Edit and exposed native/MSAA captions and
values. Custom painted text, RichEdit, and Scintilla text capture are excluded
unless their existing accessibility data supplies usable visible text. Their
caret feedback remains as it is today.
### Additive protocol
Leave `PresentAccessibleEvent` and NVDA/Tolk contracts unchanged. Add:
* `RegisterWineReviewProvider(object_path: o, protocol_version: u) -> b` to the Cthulhu
service. Version 1 registration is associated with the actual unique D-Bus
sender after the existing same-user authorization, never a caller-supplied
destination name. Registration supplies no native references.
* `PresentWineReviewFocus(instance: s, window: s, focus: s, revision: t,
title: s, role: s, protected: b, bounds: (iiii)) -> b` for that registered
sender. Window and focus are opaque lifetime identities. Emit on focus/owner
changes and retire identities
on destruction. The existing bounded foreground poll recovers missed events.
* `CaptureWindow(expected_window: s, expected_focus: s, expected_revision: t)
-> a{sv}` on
`/org/stormux/Cthulhu1/WineReview`, interface
`org.stormux.Cthulhu1.WineReview`. Return a versioned owned snapshot with its
identities, revision, bounds, nodes, text ranges, and capture status.
The capture dictionary has required keys `version: u`, `instance: s`,
`window: s`, `focus: s`, `revision: t`, `status: s`, `bounds: (iiii)`, and
`nodes: aa{sv}`. Status is `ok`, `unavailable`, `stale`, `busy`, or `limited`;
non-ok responses contain no nodes. Each node has `id: s`, `parent: s` (empty
only at root), `role: s`, `name: s`, `protected: b`, `visible: b`,
`bounds: (iiii)`, `text_known: b`, `caret_known: b`, `caret: t`,
`caret_utf16: t`, and `ranges: a(tttts(iiii))`. Ranges contain code-point
start/end, native UTF-16 start/end, exact UTF-8 text, and bounds. `caret`
counts code points. Go validates offsets, rune counts, and the resulting
tree before use. Captions and identity fields
are individually bounded at 4096 and 256 UTF-8 bytes respectively. Limit
traversal depth to 64 and reject cycles or duplicate node identities.
Revision tracks focus/window lifetime changes, not incoming text. Within a
capture, recheck foreground/focus identity before returning and verify that
each collected control's text length and visible ranges remain consistent.
Return `stale` on a detected capture race, with at most one recapture under
the original deadline. Text updates do not retire live control identities.
Native handles in identities are opaque fixed-width integers. Include a
helper-instance identity and a lifetime counter so reused HWNDs cannot inherit
an old review position. MSAA child identities remain subordinate to their
owning window lifetime; destroy/reorder events retire uncertain child identity.
Do not serialize pointers, COM objects, or prefix environment data.
The capture endpoint authorizes the current Cthulhu service owner under the
same-user trust model. Reject invalid versions, identities, dimensions, offsets,
UTF-8, and oversized messages at both ends. Older helpers/readers retain normal
speech; review reports unavailable when this optional protocol is absent.
### Go ownership and integration
Place provider registry, capture client, validation, and snapshot conversion
in cohesive `internal/wine` files. The registry owns its session-bus connection
and tracks provider disconnection. Copied focus notices go to the event-loop
owner through a bounded mailbox; D-Bus callbacks never mutate event-loop state.
Compose a review capturer around the existing AT-SPI adapter. Wine identities
use the registered helper's unique bus name and a reserved `/wine/...` object
path namespace. Only this composition layer routes those identities to Wine;
they must never be sent to AT-SPI native operations. Ordinary AT-SPI capture
keeps its current path.
The event loop owns which focus source is eligible. On Xorg, corroborate Wine
window bounds and metadata with the current foreground client and epoch.
Support a child window inside Wine's virtual desktop using the existing
foreground/title discovery. Title equality alone cannot establish ownership.
When foreground ownership is unknown, reject Wine capture rather than using
an earlier Linux focus. Returning to Linux restores its normal review path.
`review.Worker` remains the only owner of the flat cursor and capture ordering.
Before each Wine review command, obtain a fresh bounded snapshot, updating a
stable owned tree so live object generations and independent review offsets
survive text updates. Refreshing only on focus events would miss streaming
MUD output. Reuse existing capture/result queues and presentation methods;
native text is not fetched on the input or event loop.
Accept a result only if the foreground epoch, registered provider, focus,
window lifetime, and source revision still match. Focus change, provider death,
helper restart, window destruction, or lock activation retires Wine review
ownership and queued work. Repeated focus notifications for the same live
control preserve review position. Empty focused edits remain valid anchors so
review can reach other visible content in the window.
## Bounds, failures, and presentation
Use a 500 ms total Wine capture deadline, at most 4096 nodes, 256 visible text
ranges per control, 4096 code points per visual line, 65536 captured code points
per control, and 1 MiB of text per window. Enforce a 2 MiB serialized snapshot
limit including metadata. Exhaustion returns a limited status; never present a
partial snapshot as a complete window. The existing review command FIFO remains
bounded at 16. Shutdown cancels outstanding D-Bus work and drops late results.
Unavailable capture, no visible content, and capture limits use existing
complete review messages. Keep Python/current Go's silent movement boundaries,
spelling and phonetic sequences, and blank/space handling. Review speech and
braille go through the normal queues and settings; no direct speech calls from
the helper. Debug logs include provider/source identities, epochs, native
UTF-16 and converted offsets, capture timing, command, final speech, and braille
text. Password contents never enter debug logs.
## Verification and acceptance
Write failing behavioral checks before production changes:
* Helper fixtures, 32-bit and 64-bit: visible wrapped text and blank lines,
Unicode/surrogate conversion, read-only multiline output, empty command field,
captions/native control fallback, hidden/clipped content, password exclusion,
unchanged application caret/selection/focus, and capture limits.
* Go protocol and review tests: real private-session D-Bus registration/capture,
sender association and malformed payload rejection; all listed review units,
spelling/phonetics and quiet boundaries; text updates preserving position;
independent provider identities for two prefixes; focus switches, reused
handles, provider death, helper restarts, stale replies, timeout/queue limits,
and cancellation.
* Existing helper menu/editor/list regressions and focused Go review, remote,
Wine, speech and braille tests with race checks.
* Private Xorg fixture driving physical X11 key events through the actual Go
command router and both Wine architectures. Verify key press/release balance,
focus switching to Linux, input responsiveness during a delayed capture, and
no application caret movement caused by review keys.
Run private displays sequentially. Recorded speech/braille requests establish
queue behavior, not audible speech or physical braille acceptance. Live
MUSHclient acceptance must check the user's existing desktop review keys in the
command field and output notepad, repeated-word spelling, reading while output
arrives, plugin dialogs, and returning to Linux. Install matching tested helper
and reader builds only after verification; preserve backups and game state.
## Work locations and delivery
Helper changes belong in this standalone repository. Reader integration and
its tests belong in `/home/deedra/git/cthulhu-go`, with focused evidence in its
Wine testing note and the corresponding gap updated in `MIGRATION_PLAN.md`.
The reader directory and installed binaries are outside this session's writable
roots, so writes there require tool approval. Existing uncommitted helper
changes and `cthulhu.log` must be preserved.
After user approval of this document, write a reviewable implementation plan
and obtain the required plan/execution decision. Keep source changes available
for human review; do not submit them automatically.
## Reference evidence
Local behavior references: Cthulhu Python `flat_review_presenter.py` for desktop
bindings, independent navigation, spelling, braille and silent boundaries;
Cthulhu-Go `flat_review.go`, `flat_worker.go`, `flat.go`, `flat_layout.go`,
`AccessibleTree.Replace`, foreground/title ownership, and remote authorization.
The Python Wine bridge provides speech events, not a reusable review provider.
Microsoft documents [Edit character positions](https://learn.microsoft.com/en-us/windows/win32/controls/em-posfromchar)
and [Edit visible-line/text operations](https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/Controls/edit-controls-text-operations.md).
These establish the native capture approach, not Wine or MUSHclient acceptance.