diff --git a/bookstorm.py b/bookstorm.py index a3c71be..da1adbc 100755 --- a/bookstorm.py +++ b/bookstorm.py @@ -741,7 +741,6 @@ class BookReader: else: message = f"Screen reader engine active: {self.readingEngine.get_active_reader_name()}" print(message) - self.speechEngine.speak(message) else: # Reload piper-tts with new voice (check if available first) if not TtsEngine.is_available(): diff --git a/src/options_menu.py b/src/options_menu.py index 1e6b189..2ece649 100644 --- a/src/options_menu.py +++ b/src/options_menu.py @@ -9,6 +9,7 @@ Inspired by soundstorm's hierarchical menu system. from pathlib import Path from src.tts_engine import TtsEngine +from src.screen_reader_engine import ScreenReaderEngine class OptionsMenu: @@ -32,6 +33,40 @@ class OptionsMenu: self.ttsReloadCallback = ttsReloadCallback self.currentSelection = 0 self.inMenu = False + self.screenReaderName = None + self._refresh_screen_reader_target() + + def _refresh_screen_reader_target(self): + """Detect active supported screen reader for optional engine entry.""" + self.screenReaderName = None + try: + detectionEngine = ScreenReaderEngine(quiet=True) + if detectionEngine.is_available(): + activeName = detectionEngine.get_active_reader_name() + if activeName and activeName != "Unavailable": + self.screenReaderName = activeName + else: + self.screenReaderName = "Screen Reader" + detectionEngine.close() + except Exception: + self.screenReaderName = None + + def _get_engine_labels(self): + """Return user-facing labels for available engine values.""" + engineLabels = { + 'piper': 'Piper-TTS', + 'speechd': 'Speech-Dispatcher' + } + if self.screenReaderName: + engineLabels['screenreader'] = self.screenReaderName + return engineLabels + + def _get_engine_cycle_order(self): + """Return engine cycle order, skipping screen-reader mode when unavailable.""" + engineOrder = ['piper', 'speechd'] + if self.screenReaderName: + engineOrder.append('screenreader') + return engineOrder def show_main_menu(self): """ @@ -41,11 +76,7 @@ class OptionsMenu: Menu items as list of dicts """ readerEngine = self.config.get_reader_engine() - engineLabels = { - 'piper': 'Piper-TTS', - 'speechd': 'Speech-Dispatcher', - 'screenreader': 'Screen Reader' - } + engineLabels = self._get_engine_labels() readerEngineText = engineLabels.get(readerEngine, 'Piper-TTS') menuItems = [ @@ -147,13 +178,10 @@ class OptionsMenu: def _toggle_reader_engine(self): """Cycle reader engine: piper-tts, speech-dispatcher, screen reader.""" + self._refresh_screen_reader_target() currentEngine = self.config.get_reader_engine() - engineOrder = ['piper', 'speechd', 'screenreader'] - engineLabels = { - 'piper': 'Piper-TTS', - 'speechd': 'Speech-Dispatcher', - 'screenreader': 'Screen Reader' - } + engineOrder = self._get_engine_cycle_order() + engineLabels = self._get_engine_labels() if currentEngine not in engineOrder: currentEngine = 'piper' @@ -203,7 +231,8 @@ class OptionsMenu: if readerEngine == 'speechd': return self._select_speechd_voice() - self.speechEngine.speak("Voice selection is managed by your active screen reader.") + readerName = self.screenReaderName if self.screenReaderName else "your active screen reader" + self.speechEngine.speak(f"Voice selection is managed by {readerName}.") return True def _select_piper_voice(self): @@ -481,6 +510,7 @@ class OptionsMenu: def enter_menu(self): """Enter the options menu""" + self._refresh_screen_reader_target() self.inMenu = True self.currentSelection = 0 self.inVoiceMenu = False diff --git a/src/screen_reader_engine.py b/src/screen_reader_engine.py index 796e275..875f1f1 100644 --- a/src/screen_reader_engine.py +++ b/src/screen_reader_engine.py @@ -167,11 +167,12 @@ class ScreenReaderRemoteController: class ScreenReaderEngine: """Book reading engine that speaks via active screen reader D-Bus APIs.""" - def __init__(self): + def __init__(self, quiet=False): self.speechLock = threading.Lock() self.isAvailable = False self.activeController = None self.availableControllers = [] + self.quiet = quiet self.isReading = False self.isPausedReading = False @@ -185,7 +186,8 @@ class ScreenReaderEngine: def _initialize_controllers(self): """Discover available screen reader remote controllers.""" if not HAS_DASBUS: - print("Warning: python-dasbus not installed. Screen reader engine unavailable.") + if not self.quiet: + print("Warning: python-dasbus not installed. Screen reader engine unavailable.") return controllerCandidates = [ @@ -207,10 +209,11 @@ class ScreenReaderEngine: self.activeController = self._select_active_controller() self.isAvailable = self.activeController is not None - if self.activeController: - print(f"Screen reader engine connected to {self.activeController.displayName}.") - else: - print("Warning: No supported screen reader D-Bus service detected (Orca/Cthulhu).") + if not self.quiet: + if self.activeController: + print(f"Screen reader engine connected to {self.activeController.displayName}.") + else: + print("Warning: No supported screen reader D-Bus service detected (Orca/Cthulhu).") def _is_process_running(self, processName): """Return True when the process name is running."""