Keep remote speech active across config profile switches - #74
Open
LeonarddeR wants to merge 2 commits into
Open
Conversation
SynthDetector swaps the remote synth into synthDriverHandler._curSynth without touching config.conf["speech"]["synth"], but NVDA's synthDriverHandler.handlePostConfigProfileSwitch reloads the configured synthesizer whenever it does not match the active one. Every profile switch therefore terminated the remote synth, and nothing brought it back: the rescan on focus change bails out unless remote speech is the configured synthesizer. While a SynthDetector is alive, take NVDA's handler over, both on config.post_configProfileSwitch and as the module attribute, so the calls from speech.manager are covered too, and skip the reload while remote speech is active without being configured. The original handler and registration are put back on terminate. Also broaden the wording of the setting that governs this, as it now covers more than recovery after a connection loss. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR ensures Remote Speech (server-side) remains active across NVDA configuration profile switches when it was activated opportunistically (without changing the configured synthesizer), by intercepting NVDA’s post-profile-switch synth reload logic while SynthDetector is active.
Changes:
- Take over NVDA’s post-config-profile-switch synth handler while
SynthDetectoris alive, skipping reload when Remote Speech is active but not configured. - Restore the original handler and extension-point registration on
SynthDetector.terminate(). - Rename the UI/readme setting label to reflect broader behavior (“switch to remote speech when available”).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| readme.md | Updates the setting name/description to match new behavior (activation + persistence across profile switches). |
| addon/globalPlugins/rdAccess/synthDetect.py | Adds handler takeover/restore to prevent synth reload during profile switches while Remote Speech is opportunistically active. |
| addon/globalPlugins/rdAccess/settingsPanel.py | Updates the translated label text for the existing recoverRemoteSpeech setting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+69
to
+72
| original = synthDriverHandler.handlePostConfigProfileSwitch | ||
| if not config.post_configProfileSwitch.unregister(original): | ||
| log.debugWarning("NVDA's synthesizer profile switch handler was not registered") | ||
| self._nvdaHandlePostConfigProfileSwitch = original |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LeonarddeR
enabled auto-merge (squash)
July 29, 2026 12:17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When NVDA on the remote desktop server starts with a synthesizer other than Remote Speech configured,
SynthDetectoractivates the remote synth by assigningsynthDriverHandler._curSynthdirectly, deliberately leavingconfig.conf["speech"]["synth"]alone.NVDA''s
synthDriverHandler.handlePostConfigProfileSwitchassumes_curSynthalways mirrors config:So every configuration profile switch cancelled and terminated the remote synth and loaded the configured one. Nothing recovered it either:
event_gainFocustriggers a rescan withoutforce, and_queueBgScanbails out unless remote speech is the configured synthesizer. Only a lock state change forced a rescan.Setting Remote Speech as the configured synthesizer would fix the mismatch, but writing real config is not acceptable here.
Fix
For as long as a
SynthDetectoris alive, it takes NVDA''s handler over and skips the synthesizer reload while remote speech is active without being configured.Both the extension point registration and the module attribute are replaced.
Action.registerstores a reference to the function object itself, so patching the module attribute alone would leave the registered handler untouched, while only re-registering would leave the three direct calls inspeech.manager(profile triggers inside a speech sequence) unhandled. Our handler is moved to the start of the registration order; NVDA registers its own second, and anOrderedDictcannot re-insert at an index.The original handler and its registration are restored on
terminate(), which is reached both when server mode is disabled and when the setting below is switched off.The suppressed branch simply returns.
RemoteDriver.loadSettingsonly touches_localSettings, i.e.fallbackSynth, and_fallbackreads that value straight out ofconfig.conf["speech"]["remote"]rather than off the driver, so there is nothing to re-sync.Braille is unaffected: the remote display is auto-detected through NVDA''s standard mechanism with
config.conf["braille"]["display"] == "auto", whichbrailleHandler.handlePostConfigProfileSwitchalready preserves via_lastRequestedDisplayName.Setting renamed
"Automatically recover remote speech after connection loss" is now "Automatically switch to remote speech when available", since the option governs activation, recovery and surviving profile switches. The config key
recoverRemoteSpeechis unchanged, so existing configurations keep their value.Testing
prek run --all-filespasses (ruff, ty, 202 unit tests).synthDetectcannot be imported by the test harness, so this path is verified manually against a real session: connect with eSpeak configured on the server, confirm speech moves to the client, then activate a profile manually and by focusing an app, and confirm speech stays remote and the log shows noLoaded synthDriver espeak. Disconnecting still falls back perfallbackSynth, and turning the option off restores NVDA''s stock behaviour.🤖 Generated with Claude Code