From e7f69b215947cde8722dd17d5239b32f70fdafb8 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 13 Aug 2026 08:32:47 -0400 Subject: [PATCH] Fenrir should no longer fail to report its current speech-dispatcher module. --- .../speechDriver/speechdDriver.py | 18 +++- tests/unit/test_speechd_voice_selection.py | 100 +++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/fenrirscreenreader/speechDriver/speechdDriver.py b/src/fenrirscreenreader/speechDriver/speechdDriver.py index 210e3315..f7ae49fd 100644 --- a/src/fenrirscreenreader/speechDriver/speechdDriver.py +++ b/src/fenrirscreenreader/speechDriver/speechdDriver.py @@ -66,6 +66,23 @@ class driver(speech_driver): self._sd = speechd.SSIPClient("fenrir-dev") self._punct = speechd.PunctuationMode() self._is_initialized = True + + configured_module = self.env["runtime"][ + "SettingsManager" + ].get_setting("speech", "module") + if not configured_module: + try: + active_module = self._sd.get_output_module() + if active_module: + self.module = active_module + self.env["runtime"]["SettingsManager"].set_setting( + "speech", "module", active_module + ) + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "SpeechDriver get_output_module:" + str(e), + debug.DebugLevel.ERROR, + ) except Exception as e: self.env["runtime"]["DebugManager"].write_debug_out( "SpeechDriver initialize:" + str(e), debug.DebugLevel.ERROR @@ -226,4 +243,3 @@ class driver(speech_driver): self.env["runtime"]["DebugManager"].write_debug_out( "SpeechDriver set_volume:" + str(e), debug.DebugLevel.ERROR ) - diff --git a/tests/unit/test_speechd_voice_selection.py b/tests/unit/test_speechd_voice_selection.py index f1e137f4..9bb15151 100644 --- a/tests/unit/test_speechd_voice_selection.py +++ b/tests/unit/test_speechd_voice_selection.py @@ -1,10 +1,15 @@ +import sys from types import SimpleNamespace from unittest.mock import Mock from fenrirscreenreader.commands.commands import voice_browser from fenrirscreenreader.commands.commands import voice_browser_safe from fenrirscreenreader.core import dynamicVoiceMenu -from fenrirscreenreader.core.quickMenuManager import SpeechHelperMixin +from fenrirscreenreader.core.quickMenuManager import ( + QuickMenuManager, + SpeechHelperMixin, +) +from fenrirscreenreader.speechDriver import speechdDriver from fenrirscreenreader.utils.speechd_utils import get_synthesis_voice_name @@ -88,3 +93,96 @@ def test_espeak_voice_selection_keeps_language_and_variant_behavior(): ) assert voice == "en-us+female3" + + +class RuntimeSettings: + def __init__(self, module="", voice="en-us", language=""): + self.values = { + ("speech", "module"): module, + ("speech", "voice"): voice, + ("speech", "language"): language, + } + + def get_setting(self, section, setting): + return self.values[(section, setting)] + + def set_setting(self, section, setting, value): + self.values[(section, setting)] = value + + +def test_speechd_driver_resolves_blank_module_from_active_default(monkeypatch): + settings = RuntimeSettings() + client = Mock() + client.get_output_module.return_value = "rhvoice" + speechd = SimpleNamespace( + SSIPClient=Mock(return_value=client), + PunctuationMode=Mock(return_value=Mock()), + ) + monkeypatch.setitem(sys.modules, "speechd", speechd) + environment = { + "runtime": { + "DebugManager": Mock(), + "SettingsManager": settings, + } + } + + driver = speechdDriver.driver() + driver.initialize(environment) + + assert settings.get_setting("speech", "module") == "rhvoice" + assert driver.module == "rhvoice" + + +def test_speechd_driver_keeps_running_when_default_module_query_fails( + monkeypatch, +): + settings = RuntimeSettings() + client = Mock() + client.get_output_module.side_effect = RuntimeError("query failed") + speechd = SimpleNamespace( + SSIPClient=Mock(return_value=client), + PunctuationMode=Mock(return_value=Mock()), + ) + monkeypatch.setitem(sys.modules, "speechd", speechd) + environment = { + "runtime": { + "DebugManager": Mock(), + "SettingsManager": settings, + } + } + + driver = speechdDriver.driver() + driver.initialize(environment) + + assert driver._is_initialized is True + assert settings.get_setting("speech", "module") == "" + + +def test_quick_menu_espeak_voice_can_move_away_and_return(): + settings = RuntimeSettings(module="rhvoice") + manager = QuickMenuManager() + manager.env = { + "runtime": { + "DebugManager": Mock(), + "OutputManager": Mock(), + "SettingsManager": settings, + "SpeechDriver": Mock(), + } + } + manager._modules_cache = ["rhvoice", "espeak-ng"] + manager._cache_timestamp = float("inf") + manager._voices_cache["espeak-ng"] = [ + "en-gb", + "en-us", + "en-us+female2", + ] + + assert manager.cycle_speech_module("next") is True + assert settings.get_setting("speech", "module") == "espeak-ng" + assert settings.get_setting("speech", "voice") == "en-us" + + assert manager.cycle_speech_voice("next") is True + assert settings.get_setting("speech", "voice") == "en-us+female2" + + assert manager.cycle_speech_voice("prev") is True + assert settings.get_setting("speech", "voice") == "en-us"