docs: add Tolk NVDA presence compatibility design
This commit is contained in:
195
docs/superpowers/specs/2026-04-10-tolk-nvda-presence-design.md
Normal file
195
docs/superpowers/specs/2026-04-10-tolk-nvda-presence-design.md
Normal 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
|
||||
Reference in New Issue
Block a user