docs: add Tolk NVDA presence compatibility design

This commit is contained in:
Storm Dragon
2026-04-10 14:07:25 -04:00
parent ec7e073431
commit a98aa174f8

View File

@@ -0,0 +1,195 @@
# Tolk NVDA Presence Compatibility Design
## Goal
Allow applications running under Wine or Proton to use the official upstream `Tolk.dll` unchanged while routing Tolk speech through the existing Linux NVDA-to-Cthulhu path.
The compatibility layer must satisfy only the checks that Tolk performs when selecting its NVDA driver. It must not require replacing `Tolk.dll`, patching Tolk, or using a Tolk-specific DLL override.
## Confirmed Constraints
- The shipped `Tolk.dll` must remain the official upstream binary.
- The existing `wine2speechd` package already provides replacement `nvdaControllerClient32.dll` and `nvdaControllerClient64.dll` implementations for Linux.
- The current blocker is Tolk detection, not the downstream speech transport.
- Scope is limited to making Tolk believe NVDA is present; broader NVDA emulation is out of scope.
## Current Tolk Behavior
From `tolk/src/ScreenReaderDriverNVDA.cpp`, Tolk considers NVDA active only when both of the following succeed:
1. `nvdaController_testIfRunning() == 0`
2. `FindWindow(L"wxWindowClassNR", L"NVDA")` returns a window handle
If either check fails, Tolk will not select the NVDA driver and speech output through Tolk will fail.
## Recommended Approach
Extend the existing Wine NVDA compatibility stack with a minimal NVDA presence helper. The solution has two parts:
1. The existing custom NVDA controller DLLs continue handling `nvdaController_*` API calls and forwarding them into the Linux bridge.
2. A lightweight Windows helper process running inside Wine creates the exact window Tolk expects for NVDA detection.
This keeps the official `Tolk.dll` untouched and confines the compatibility contract to Tolk's actual checks.
## Architecture
### 1. NVDA controller DLLs
The custom `nvdaControllerClient32.dll` and `nvdaControllerClient64.dll` remain the Wine-visible implementation that applications and Tolk load.
Required behavior:
- `nvdaController_speakText` forwards speech to the existing Linux bridge.
- `nvdaController_brailleMessage` continues current behavior.
- `nvdaController_cancelSpeech` continues current behavior.
- `nvdaController_testIfRunning` returns success only when the Linux bridge is reachable and the compatibility environment is operational.
`nvdaController_testIfRunning` must not return success solely because the DLL loaded. It is the main guard against false positive Tolk detection.
### 2. NVDA presence helper
A small Windows executable is added to the Wine-side compatibility package. Its only job is to create and maintain a top-level window with:
- class name: `wxWindowClassNR`
- window title: `NVDA`
Required behavior:
- starts quickly and remains idle
- single-instance per Wine prefix or session
- exits cleanly without user interaction
- does not present visible UI unless Wine forces a window surface
- can be launched independently or on demand by the controller DLL
### 3. Startup coordination
The presence helper must be running before Tolk calls `FindWindow`, or Tolk detection will fail.
Acceptable coordination strategies:
- preferred: launch the helper as part of the existing Wine accessibility startup path
- acceptable: lazily launch the helper the first time the custom NVDA DLL is loaded, then wait briefly for the window to appear
The preferred strategy is external startup rather than in-DLL process creation because it separates concerns and avoids loader-time side effects.
## Detection Contract
Tolk compatibility is considered successful only when all of the following are true:
- official `Tolk.dll` loads normally
- Tolk loads the custom NVDA controller DLL
- `nvdaController_testIfRunning()` returns `0`
- `FindWindow(L"wxWindowClassNR", L"NVDA")` succeeds
- `Tolk_DetectScreenReader()` returns `NVDA`
- `Tolk_Output()` delivers speech through the existing Linux bridge
If the Linux bridge is unavailable, the compatibility layer must fail closed:
- `nvdaController_testIfRunning()` returns failure
- Tolk does not report NVDA as active
The dummy NVDA window alone must never make Tolk think speech is available.
## Packaging
The compatibility feature belongs with the Wine NVDA compatibility stack, not in Tolk itself.
Expected package contents:
- `nvdaControllerClient32.dll`
- `nvdaControllerClient64.dll`
- `nvda-presence-helper.exe` or similarly named helper
- startup integration so the helper is available in Wine and Proton environments where Tolk-based games run
No `Tolk.dll` replacement or override is added.
## Error Handling
### Bridge unavailable
- `nvdaController_testIfRunning` returns failure
- speech-related entry points return the existing failure behavior
- Tolk should not detect NVDA
### Helper missing or failed to start
- `FindWindow` fails
- Tolk should not detect NVDA
- logging should identify missing helper startup distinctly from bridge connectivity failures
### Duplicate helper instances
- duplicates must resolve harmlessly, preferably by allowing one owner and exiting the rest
## Logging
Add targeted logging in the compatibility layer only. Logging should make these states distinguishable:
- controller DLL loaded
- bridge connectivity check succeeded or failed
- presence helper started
- presence window created
- Tolk compatibility ready
No Tolk-side logging changes are needed because Tolk is not being modified.
## Testing
### Functional test
Create or reuse a small Wine test application that:
1. calls `Tolk_Load()`
2. calls `Tolk_DetectScreenReader()`
3. calls `Tolk_Output(L\"test\", false)`
Expected result with bridge and helper active:
- `Tolk_DetectScreenReader()` returns `NVDA`
- speech reaches Cthulhu through the existing NVDA path
### Negative tests
1. Bridge down, helper up:
`Tolk_DetectScreenReader()` must not return `NVDA`
2. Bridge up, helper down:
`Tolk_DetectScreenReader()` must not return `NVDA`
3. Both down:
`Tolk_DetectScreenReader()` must not return `NVDA`
### Regression check
Verify that existing non-Tolk NVDA speech consumers continue using the current `wine2speechd` path without requiring Tolk-specific configuration.
## Out of Scope
- adding a plugin mechanism to Tolk
- maintaining a Tolk fork
- broader NVDA desktop emulation beyond what Tolk checks
- compatibility with applications that perform additional NVDA-specific probing outside the current `wine2speechd` contract
- anti-cheat or anti-tamper guarantees beyond avoiding Tolk replacement
## Risks
### Wine window behavior
The helper must create a window that `FindWindow` can discover reliably under Wine and Proton. If Wine normalizes or alters class registration behavior, the helper may need adjustment.
### Timing
If a game calls Tolk very early, helper startup races could cause intermittent detection failure. This is why pre-starting the helper is preferred.
### Split responsibility
The controller DLL and helper must agree on readiness. If they drift apart, Tolk may see the window but still fail to speak. The fail-closed `testIfRunning` check is the protection against this.
## Recommendation
Implement the feature in the existing Wine NVDA compatibility stack, not in Tolk and not in Cthulhu core.
The smallest correct implementation is:
1. fix or confirm `nvdaController_testIfRunning()` behavior in the custom DLL
2. add a minimal Wine helper that exposes the NVDA window Tolk checks for
3. wire startup so the helper is available before Tolk detection occurs
4. verify with a small Tolk test program under Wine