Summary
Since v0.16.2 (commit 83f73a8), NvdaBackend::initialize() returns success even when NVDA is not running. Because the NVDA backend is registered at the highest Windows priority (103), BackendRegistry::acquire_best() selects this non-functional backend ahead of backends that are actually running (e.g. JAWS at 100), and subsequent speak()/output() calls fail silently with BackendNotAvailable.
Affected versions
v0.16.2, v0.16.3, v0.16.4. Last good version: v0.16.1.
Root cause
In source/backends/nvda.cpp, initialize():
if (server_supports_interface(controller_handle, nvdaController_NvdaController_v1_0_c_ifspec) &&
nvdaController_testIfRunning(controller_handle) != ERROR_SUCCESS)
return std::unexpected(BackendError::BackendNotAvailable);
return {};
When NVDA is not running there is no RPC server at the NvdaCtlr.* ncalrpc endpoint, so server_supports_interface() returns false (its RpcMgmtInqIfIds call fails). The && short-circuits, nvdaController_testIfRunning() is never evaluated, and the function falls through to return {};, reporting the backend as available.
Prior to 83f73a8 the check was simply if (nvdaController_testIfRunning(...) != ERROR_SUCCESS) return ...BackendNotAvailable;, which correctly failed when NVDA was not running.
Impact
NvdaBackend is registered at priority 103, the highest of the Windows backends. acquire_best() returns the first backend (in descending priority) whose initialize() succeeds, so on any Windows machine where NVDA is not running it returns the NVDA backend and never reaches the running screen reader. speak()/output()/braille() then fail with BackendNotAvailable (they still call testIfRunning), leaving callers with a backend that reports a valid name and SUPPORTS_SPEAK but produces no output. Observed in the field with a JAWS user: acquire_best() reported NVDA, no speech was produced, and rolling back to v0.16.1 restored JAWS.
Suggested fix
The testIfRunning liveness check should not be gated behind a short-circuiting &&. Either require both conditions:
if (!server_supports_interface(controller_handle, nvdaController_NvdaController_v1_0_c_ifspec) ||
nvdaController_testIfRunning(controller_handle) != ERROR_SUCCESS)
return std::unexpected(BackendError::BackendNotAvailable);
...or simply restore the pre-83f73a8 check (if (nvdaController_testIfRunning(...) != ERROR_SUCCESS) return ...;), which is unconditional and known-good.
I'm happy to open a PR, though I don't have a JAWS setup to verify the screen-reader-selection path — let me know if you'd like one.
Reproduction
- On Windows, ensure NVDA is not running (another screen reader such as JAWS may be running).
- Call
prism_registry_acquire_best().
- The returned backend is NVDA, and
prism_backend_output() returns BackendNotAvailable / produces no speech.
Environment: Prism v0.16.2–v0.16.4, Windows x64, dynamic release build.
Summary
Since v0.16.2 (commit
83f73a8),NvdaBackend::initialize()returns success even when NVDA is not running. Because the NVDA backend is registered at the highest Windows priority (103),BackendRegistry::acquire_best()selects this non-functional backend ahead of backends that are actually running (e.g. JAWS at 100), and subsequentspeak()/output()calls fail silently withBackendNotAvailable.Affected versions
v0.16.2, v0.16.3, v0.16.4. Last good version: v0.16.1.
Root cause
In
source/backends/nvda.cpp,initialize():When NVDA is not running there is no RPC server at the
NvdaCtlr.*ncalrpc endpoint, soserver_supports_interface()returns false (itsRpcMgmtInqIfIdscall fails). The&&short-circuits,nvdaController_testIfRunning()is never evaluated, and the function falls through toreturn {};, reporting the backend as available.Prior to
83f73a8the check was simplyif (nvdaController_testIfRunning(...) != ERROR_SUCCESS) return ...BackendNotAvailable;, which correctly failed when NVDA was not running.Impact
NvdaBackendis registered at priority 103, the highest of the Windows backends.acquire_best()returns the first backend (in descending priority) whoseinitialize()succeeds, so on any Windows machine where NVDA is not running it returns the NVDA backend and never reaches the running screen reader.speak()/output()/braille()then fail withBackendNotAvailable(they still calltestIfRunning), leaving callers with a backend that reports a valid name andSUPPORTS_SPEAKbut produces no output. Observed in the field with a JAWS user:acquire_best()reportedNVDA, no speech was produced, and rolling back to v0.16.1 restored JAWS.Suggested fix
The
testIfRunningliveness check should not be gated behind a short-circuiting&&. Either require both conditions:...or simply restore the pre-
83f73a8check (if (nvdaController_testIfRunning(...) != ERROR_SUCCESS) return ...;), which is unconditional and known-good.I'm happy to open a PR, though I don't have a JAWS setup to verify the screen-reader-selection path — let me know if you'd like one.
Reproduction
prism_registry_acquire_best().prism_backend_output()returnsBackendNotAvailable/ produces no speech.Environment: Prism v0.16.2–v0.16.4, Windows x64, dynamic release build.