7.3 KiB
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.dllmust remain the official upstream binary. - The existing
wine2speechdpackage already provides replacementnvdaControllerClient32.dllandnvdaControllerClient64.dllimplementations 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:
nvdaController_testIfRunning() == 0FindWindow(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:
- The existing custom NVDA controller DLLs continue handling
nvdaController_*API calls and forwarding them into the Linux bridge. - 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_speakTextforwards speech to the existing Linux bridge.nvdaController_brailleMessagecontinues current behavior.nvdaController_cancelSpeechcontinues current behavior.nvdaController_testIfRunningreturns 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.dllloads normally - Tolk loads the custom NVDA controller DLL
nvdaController_testIfRunning()returns0FindWindow(L"wxWindowClassNR", L"NVDA")succeedsTolk_DetectScreenReader()returnsNVDATolk_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.dllnvdaControllerClient64.dllnvda-presence-helper.exeor 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_testIfRunningreturns failure- speech-related entry points return the existing failure behavior
- Tolk should not detect NVDA
Helper missing or failed to start
FindWindowfails- 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:
- calls
Tolk_Load() - calls
Tolk_DetectScreenReader() - calls
Tolk_Output(L\"test\", false)
Expected result with bridge and helper active:
Tolk_DetectScreenReader()returnsNVDA- speech reaches Cthulhu through the existing NVDA path
Negative tests
- Bridge down, helper up:
Tolk_DetectScreenReader()must not returnNVDA - Bridge up, helper down:
Tolk_DetectScreenReader()must not returnNVDA - Both down:
Tolk_DetectScreenReader()must not returnNVDA
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
wine2speechdcontract - 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:
- fix or confirm
nvdaController_testIfRunning()behavior in the custom DLL - add a minimal Wine helper that exposes the NVDA window Tolk checks for
- wire startup so the helper is available before Tolk detection occurs
- verify with a small Tolk test program under Wine