Visual speech monitor added to speech history plugin. Toggle with cthulhu+shift+D

This commit is contained in:
Storm Dragon
2026-02-17 08:40:51 -05:00
parent 40e63150a6
commit ed78ffc248
3 changed files with 240 additions and 89 deletions
@@ -1,9 +1,8 @@
name = Speech History name = Speech History
version = 1.0.0 version = 1.2.0
description = Shows a searchable history of the last 50 unique utterances spoken by Cthulhu description = Shows speech history and a live floating monitor of spoken text from Cthulhu
authors = Stormux authors = Stormux
website = https://git.stormux.org/storm/cthulhu website = https://git.stormux.org/storm/cthulhu
copyright = Copyright 2025 Stormux copyright = Copyright 2025 Stormux
builtin = false builtin = false
hidden = false hidden = false
+213 -85
View File
@@ -7,39 +7,47 @@
# License as published by the Free Software Foundation; either # License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version. # version 2.1 of the License, or (at your option) any later version.
"""Speech History plugin for Cthulhu.""" """Speech history and speech monitor plugin for Cthulhu."""
import logging import logging
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk, GLib
from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import debug from cthulhu import debug
from cthulhu import speech
from cthulhu import speech_history from cthulhu import speech_history
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class SpeechHistory(Plugin): class SpeechHistory(Plugin):
"""Plugin that displays a window containing recent spoken output.""" """Plugin that provides speech history and a live speech monitor."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._activated = False self._activated = False
self._kbOpenWindow = None self._kbOpenWindow = None
self._kbToggleMonitor = None
# Speech history window state
self._window = None self._window = None
self._filterEntry = None self._filterEntry = None
self._filterText = "" self._filterText = ""
self._listStore = None self._listStore = None
self._filterModel = None self._filterModel = None
self._treeView = None self._treeView = None
self._capturePaused = False self._capturePaused = False
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Plugin initialized", True) # Live monitor window state
self._monitorWindow = None
self._monitorTextView = None
self._monitorTextBuffer = None
self._monitorEndMark = None
self._monitorLineCount = 0
self._monitorMaxLines = 500
@cthulhu_hookimpl @cthulhu_hookimpl
def activate(self, plugin=None): def activate(self, plugin=None):
@@ -47,17 +55,13 @@ class SpeechHistory(Plugin):
return return
if self._activated: if self._activated:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Already activated, skipping", True)
return True return True
try: try:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Activating plugin", True) self._register_keybindings()
self._register_keybinding()
self._activated = True self._activated = True
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Activated successfully", True)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR during activate: {e}", True)
logger.exception("Error activating SpeechHistory plugin") logger.exception("Error activating SpeechHistory plugin")
return False return False
@@ -67,46 +71,208 @@ class SpeechHistory(Plugin):
return return
try: try:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Deactivating plugin", True)
self._close_window() self._close_window()
self._disable_monitor()
self._kbOpenWindow = None self._kbOpenWindow = None
self._kbToggleMonitor = None
self._activated = False self._activated = False
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Deactivated successfully", True)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR during deactivate: {e}", True)
logger.exception("Error deactivating SpeechHistory plugin") logger.exception("Error deactivating SpeechHistory plugin")
return False return False
def _register_keybinding(self): def _register_keybindings(self):
try: if not self.app:
if not self.app: debug.printMessage(
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: No app reference; cannot register keybinding", True) debug.LEVEL_INFO,
return "SpeechHistory: No app reference; cannot register keybindings",
True,
)
return
gestureString = "kb:cthulhu+control+h" historyGesture = "kb:cthulhu+control+h"
description = "Open speech history" historyDescription = "Open speech history"
self._kbOpenWindow = self.registerGestureByString(
self._open_window,
historyDescription,
historyGesture,
)
self._kbOpenWindow = self.registerGestureByString( monitorGesture = "kb:cthulhu+shift+d"
self._open_window, monitorDescription = "Toggle speech monitor"
description, self._kbToggleMonitor = self.registerGestureByString(
gestureString, self._toggle_monitor,
monitorDescription,
monitorGesture,
)
if not self._kbOpenWindow:
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Failed to register keybinding {historyGesture}",
True,
) )
if self._kbOpenWindow: if not self._kbToggleMonitor:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Registered keybinding {gestureString}", True) debug.printMessage(
else: debug.LEVEL_INFO,
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Failed to register keybinding {gestureString}", True) f"SpeechHistory: Failed to register keybinding {monitorGesture}",
except Exception as e: True,
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR registering keybinding: {e}", True) )
logger.exception("Error registering keybinding for SpeechHistory")
# Live monitor methods
def _toggle_monitor(self, script=None, inputEvent=None):
try:
if self._monitorWindow is not None:
self._disable_monitor()
self._present_message("Speech monitor disabled.")
return True
self._enable_monitor()
self._present_message("Speech monitor enabled.")
return True
except Exception:
logger.exception("Error toggling speech monitor")
self._disable_monitor()
self._present_message("Error toggling speech monitor.")
return False
def _enable_monitor(self):
if self._monitorWindow is not None:
self._monitorWindow.show_all()
return
self._create_monitor_window()
speech.set_monitor_callbacks(writeText=self._on_spoken_text)
self._monitorWindow.show_all()
def _disable_monitor(self):
speech.set_monitor_callbacks(writeText=None)
if self._monitorWindow is not None:
self._monitorWindow.destroy()
def _create_monitor_window(self):
self._monitorWindow = Gtk.Window(title="Speech Monitor - Cthulhu")
self._monitorWindow.set_default_size(900, 320)
self._monitorWindow.set_modal(False)
self._monitorWindow.set_border_width(8)
self._monitorWindow.set_keep_above(True)
self._monitorWindow.set_accept_focus(False)
self._monitorWindow.set_focus_on_map(False)
self._monitorWindow.set_skip_taskbar_hint(True)
self._monitorWindow.set_skip_pager_hint(True)
self._monitorWindow.set_type_hint(Gdk.WindowTypeHint.UTILITY)
self._monitorWindow.connect("destroy", self._on_monitor_window_destroy)
self._monitorWindow.connect("key-press-event", self._on_monitor_window_key_press)
mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
scrolledWindow = Gtk.ScrolledWindow()
scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self._monitorTextView = Gtk.TextView()
self._monitorTextView.set_editable(False)
self._monitorTextView.set_cursor_visible(False)
self._monitorTextView.set_can_focus(False)
self._monitorTextView.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
self._monitorTextView.set_accepts_tab(False)
self._monitorTextView.set_left_margin(6)
self._monitorTextView.set_right_margin(6)
self._monitorTextView.set_top_margin(6)
self._monitorTextView.set_bottom_margin(6)
textAccessible = self._monitorTextView.get_accessible()
if textAccessible:
textAccessible.set_name("Speech monitor output")
self._monitorTextBuffer = self._monitorTextView.get_buffer()
self._monitorEndMark = self._monitorTextBuffer.create_mark(
"monitorEnd",
self._monitorTextBuffer.get_end_iter(),
False,
)
self._monitorLineCount = 0
scrolledWindow.add(self._monitorTextView)
mainBox.pack_start(scrolledWindow, True, True, 0)
buttonRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
buttonRow.set_halign(Gtk.Align.END)
clearButton = Gtk.Button(label="Clear")
closeButton = Gtk.Button(label="Close")
clearButton.connect("clicked", self._on_monitor_clear_clicked)
closeButton.connect("clicked", self._on_monitor_close_clicked)
buttonRow.pack_start(clearButton, False, False, 0)
buttonRow.pack_start(closeButton, False, False, 0)
mainBox.pack_start(buttonRow, False, False, 0)
self._monitorWindow.add(mainBox)
def _on_monitor_close_clicked(self, button):
self._disable_monitor()
def _on_monitor_clear_clicked(self, button):
if self._monitorTextBuffer is None:
return
self._monitorTextBuffer.set_text("")
self._monitorLineCount = 0
def _on_monitor_window_destroy(self, widget):
speech.set_monitor_callbacks(writeText=None)
self._monitorWindow = None
self._monitorTextView = None
self._monitorTextBuffer = None
self._monitorEndMark = None
self._monitorLineCount = 0
def _on_monitor_window_key_press(self, widget, event):
if event.keyval == Gdk.KEY_Escape:
self._disable_monitor()
return True
return False
def _on_spoken_text(self, text):
if not text or not isinstance(text, str):
return
GLib.idle_add(self._append_text_on_main_thread, text)
def _append_text_on_main_thread(self, text):
if self._monitorTextBuffer is None:
return False
self._monitorTextBuffer.insert(self._monitorTextBuffer.get_end_iter(), f"{text}\n")
self._monitorLineCount += 1
if self._monitorLineCount > self._monitorMaxLines:
excess = self._monitorLineCount - self._monitorMaxLines
startIter = self._monitorTextBuffer.get_start_iter()
cutIter = self._monitorTextBuffer.get_iter_at_line(excess)
self._monitorTextBuffer.delete(startIter, cutIter)
self._monitorLineCount = self._monitorMaxLines
if self._monitorEndMark is not None and self._monitorTextView is not None:
self._monitorTextBuffer.move_mark(
self._monitorEndMark,
self._monitorTextBuffer.get_end_iter(),
)
self._monitorTextView.scroll_mark_onscreen(self._monitorEndMark)
return False
# Speech history methods
def _open_window(self, script=None, inputEvent=None): def _open_window(self, script=None, inputEvent=None):
try: try:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Open window requested", True)
if self._window: if self._window:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window already open; presenting", True)
self._window.present() self._window.present()
return True return True
@@ -120,10 +286,8 @@ class SpeechHistory(Plugin):
elif self._filterEntry: elif self._filterEntry:
self._filterEntry.grab_focus() self._filterEntry.grab_focus()
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window shown", True)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR opening window: {e}", True)
logger.exception("Error opening SpeechHistory window") logger.exception("Error opening SpeechHistory window")
self._resume_capture() self._resume_capture()
return False return False
@@ -141,7 +305,6 @@ class SpeechHistory(Plugin):
self._filterText = "" self._filterText = ""
# Filter row
filterRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) filterRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
filterLabel = Gtk.Label(label="_Filter:") filterLabel = Gtk.Label(label="_Filter:")
filterLabel.set_use_underline(True) filterLabel.set_use_underline(True)
@@ -156,7 +319,6 @@ class SpeechHistory(Plugin):
filterRow.pack_start(self._filterEntry, True, True, 0) filterRow.pack_start(self._filterEntry, True, True, 0)
mainBox.pack_start(filterRow, False, False, 0) mainBox.pack_start(filterRow, False, False, 0)
# List
self._listStore = Gtk.ListStore(str) self._listStore = Gtk.ListStore(str)
self._filterModel = self._listStore.filter_new() self._filterModel = self._listStore.filter_new()
self._filterModel.set_visible_func(self._filter_visible_func) self._filterModel.set_visible_func(self._filter_visible_func)
@@ -169,7 +331,7 @@ class SpeechHistory(Plugin):
textRenderer = Gtk.CellRendererText() textRenderer = Gtk.CellRendererText()
textRenderer.set_property("wrap-width", 640) textRenderer.set_property("wrap-width", 640)
textRenderer.set_property("wrap-mode", 2) # Pango.WrapMode.WORD_CHAR textRenderer.set_property("wrap-mode", 2)
textColumn = Gtk.TreeViewColumn("Spoken Text", textRenderer, text=0) textColumn = Gtk.TreeViewColumn("Spoken Text", textRenderer, text=0)
textColumn.set_resizable(True) textColumn.set_resizable(True)
textColumn.set_expand(True) textColumn.set_expand(True)
@@ -180,7 +342,6 @@ class SpeechHistory(Plugin):
scrolled.add(self._treeView) scrolled.add(self._treeView)
mainBox.pack_start(scrolled, True, True, 0) mainBox.pack_start(scrolled, True, True, 0)
# Buttons
buttonRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) buttonRow = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
buttonRow.set_halign(Gtk.Align.END) buttonRow.set_halign(Gtk.Align.END)
@@ -201,16 +362,12 @@ class SpeechHistory(Plugin):
self._refresh_list(selectFirst=True) self._refresh_list(selectFirst=True)
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window created", True)
def _on_filter_changed(self, entry): def _on_filter_changed(self, entry):
try: try:
self._filterText = entry.get_text() or "" self._filterText = entry.get_text() or ""
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Filter changed '{self._filterText}'", True)
if self._filterModel: if self._filterModel:
self._filterModel.refilter() self._filterModel.refilter()
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR filtering: {e}", True)
logger.exception("Error updating speech history filter") logger.exception("Error updating speech history filter")
def _filter_visible_func(self, model, treeIter, data=None): def _filter_visible_func(self, model, treeIter, data=None):
@@ -221,8 +378,7 @@ class SpeechHistory(Plugin):
spokenText = model[treeIter][0] or "" spokenText = model[treeIter][0] or ""
return spokenText.lower().startswith(filterText) return spokenText.lower().startswith(filterText)
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR in filter func: {e}", True)
return True return True
def _refresh_list(self, selectFirst=False): def _refresh_list(self, selectFirst=False):
@@ -230,31 +386,19 @@ class SpeechHistory(Plugin):
if not self._listStore: if not self._listStore:
return return
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Refreshing list", True)
self._listStore.clear() self._listStore.clear()
items = speech_history.get_items() items = speech_history.get_items()
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Retrieved {len(items)} items (paused={speech_history.is_capture_paused()})",
True,
)
for item in items: for item in items:
self._listStore.append([item]) self._listStore.append([item])
if self._filterModel: if self._filterModel:
self._filterModel.refilter() self._filterModel.refilter()
debug.printMessage(
debug.LEVEL_INFO,
f"SpeechHistory: Filtered items visible={len(self._filterModel)} filter='{self._filterText}'",
True,
)
if selectFirst and self._treeView and len(self._filterModel) > 0: if selectFirst and self._treeView and len(self._filterModel) > 0:
selection = self._treeView.get_selection() selection = self._treeView.get_selection()
selection.select_path(Gtk.TreePath.new_first()) selection.select_path(Gtk.TreePath.new_first())
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR refreshing list: {e}", True)
logger.exception("Error refreshing speech history list") logger.exception("Error refreshing speech history list")
def _get_selected_text(self): def _get_selected_text(self):
@@ -268,8 +412,7 @@ class SpeechHistory(Plugin):
return None return None
return model[treeIter][0] return model[treeIter][0]
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR getting selection: {e}", True)
logger.exception("Error getting selected speech history item") logger.exception("Error getting selected speech history item")
return None return None
@@ -284,11 +427,8 @@ class SpeechHistory(Plugin):
clipboard.set_text(selectedText, -1) clipboard.set_text(selectedText, -1)
clipboard.store() clipboard.store()
preview = selectedText[:60] + ("..." if len(selectedText) > 60 else "")
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Copied to clipboard '{preview}'", True)
self._present_message("Copied to clipboard.") self._present_message("Copied to clipboard.")
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR copying: {e}", True)
logger.exception("Error copying speech history item to clipboard") logger.exception("Error copying speech history item to clipboard")
self._present_message("Error copying to clipboard.") self._present_message("Error copying to clipboard.")
@@ -300,33 +440,27 @@ class SpeechHistory(Plugin):
return return
removed = speech_history.remove(selectedText) removed = speech_history.remove(selectedText)
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: Remove requested removed={removed}", True)
if removed: if removed:
self._refresh_list(selectFirst=True) self._refresh_list(selectFirst=True)
self._present_message("Removed from history.") self._present_message("Removed from history.")
else: else:
self._present_message("Item not found in history.") self._present_message("Item not found in history.")
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR removing: {e}", True)
logger.exception("Error removing speech history item") logger.exception("Error removing speech history item")
self._present_message("Error removing item from history.") self._present_message("Error removing item from history.")
def _on_close_clicked(self, button): def _on_close_clicked(self, button):
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Close button clicked", True)
self._close_window() self._close_window()
def _close_window(self): def _close_window(self):
try: try:
if self._window: if self._window:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Closing window", True)
self._window.destroy() self._window.destroy()
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR closing window: {e}", True)
logger.exception("Error closing SpeechHistory window") logger.exception("Error closing SpeechHistory window")
self._resume_capture() self._resume_capture()
def _on_window_destroy(self, widget): def _on_window_destroy(self, widget):
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Window destroyed", True)
self._window = None self._window = None
self._filterEntry = None self._filterEntry = None
self._listStore = None self._listStore = None
@@ -338,14 +472,12 @@ class SpeechHistory(Plugin):
def _on_window_key_press(self, widget, event): def _on_window_key_press(self, widget, event):
try: try:
if event.keyval == Gdk.KEY_Escape: if event.keyval == Gdk.KEY_Escape:
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Escape pressed; closing window", True)
self._close_window() self._close_window()
return True return True
if not self._filterEntry or self._filterEntry.is_focus(): if not self._filterEntry or self._filterEntry.is_focus():
return False return False
# If user starts typing anywhere, move focus to filter and update it.
if event.keyval == Gdk.KEY_BackSpace: if event.keyval == Gdk.KEY_BackSpace:
currentText = self._filterEntry.get_text() or "" currentText = self._filterEntry.get_text() or ""
if currentText: if currentText:
@@ -376,8 +508,7 @@ class SpeechHistory(Plugin):
self._filterEntry.set_text(currentText + charTyped) self._filterEntry.set_text(currentText + charTyped)
self._filterEntry.set_position(-1) self._filterEntry.set_position(-1)
return True return True
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR in key handler: {e}", True)
logger.exception("Error handling key press in SpeechHistory window") logger.exception("Error handling key press in SpeechHistory window")
return False return False
@@ -385,7 +516,6 @@ class SpeechHistory(Plugin):
if self._capturePaused: if self._capturePaused:
return return
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Pausing capture while window is open", True)
speech_history.pause_capture(reason="SpeechHistory window open") speech_history.pause_capture(reason="SpeechHistory window open")
self._capturePaused = True self._capturePaused = True
@@ -393,7 +523,6 @@ class SpeechHistory(Plugin):
if not self._capturePaused: if not self._capturePaused:
return return
debug.printMessage(debug.LEVEL_INFO, "SpeechHistory: Resuming capture (window closed)", True)
speech_history.resume_capture(reason="SpeechHistory window closed") speech_history.resume_capture(reason="SpeechHistory window closed")
self._capturePaused = False self._capturePaused = False
@@ -408,6 +537,5 @@ class SpeechHistory(Plugin):
state.activeScript.presentMessage(message, resetStyles=False) state.activeScript.presentMessage(message, resetStyles=False)
else: else:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: {message}", True) debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: {message}", True)
except Exception as e: except Exception:
debug.printMessage(debug.LEVEL_INFO, f"SpeechHistory: ERROR presenting message: {e}", True)
logger.exception("Error presenting message from SpeechHistory") logger.exception("Error presenting message from SpeechHistory")
+25 -1
View File
@@ -36,7 +36,7 @@ __license__ = "LGPL"
import importlib import importlib
import time import time
from typing import TYPE_CHECKING, Optional, List, Dict, Any, Union from typing import TYPE_CHECKING, Optional, List, Dict, Any, Union, Callable
from . import debug from . import debug
from . import logger from . import logger
@@ -73,6 +73,9 @@ _speechserver: Optional[SpeechServer] = None
# The last time something was spoken. # The last time something was spoken.
_timestamp: float = 0.0 _timestamp: float = 0.0
# Optional callback for live monitoring of spoken text.
_monitorWriteTextCallback: Optional[Callable[[str], None]] = None
def _initSpeechServer(moduleName: Optional[str], speechServerInfo: Optional[Any]) -> None: def _initSpeechServer(moduleName: Optional[str], speechServerInfo: Optional[Any]) -> None:
global _speechserver global _speechserver
@@ -168,6 +171,21 @@ def setSpeechServer(speechServer: SpeechServer) -> None:
global _speechserver global _speechserver
_speechserver = speechServer _speechserver = speechServer
def set_monitor_callbacks(writeText: Optional[Callable[[str], None]] = None) -> None:
"""Sets runtime callbacks for live speech monitoring."""
global _monitorWriteTextCallback
_monitorWriteTextCallback = writeText
def _write_to_monitor(text: str) -> None:
"""Writes text to the active speech monitor callback if set."""
if _monitorWriteTextCallback is None:
return
try:
_monitorWriteTextCallback(text)
except Exception:
debug.printException(debug.LEVEL_INFO)
def __resolveACSS(acss: Optional[Any] = None) -> ACSS: def __resolveACSS(acss: Optional[Any] = None) -> ACSS:
if isinstance(acss, ACSS): if isinstance(acss, ACSS):
family = acss.get(acss.FAMILY) family = acss.get(acss.FAMILY)
@@ -237,6 +255,8 @@ def _speak(text: str, acss: Optional[Any], interrupt: bool) -> None:
except Exception: except Exception:
debug.printException(debug.LEVEL_INFO) debug.printException(debug.LEVEL_INFO)
_write_to_monitor(text)
if not _speechserver: if not _speechserver:
logLine = f"SPEECH OUTPUT: '{text}' {acss}" logLine = f"SPEECH OUTPUT: '{text}' {acss}"
debug.printMessage(debug.LEVEL_INFO, logLine, True) debug.printMessage(debug.LEVEL_INFO, logLine, True)
@@ -393,6 +413,8 @@ def speakKeyEvent(event: Any, acss: Optional[Any] = None) -> None:
if log: if log:
log.info(logLine) log.info(logLine)
_write_to_monitor(msg.strip())
if _speechserver: if _speechserver:
_speechserver.speakKeyEvent(event, acss) # type: ignore _speechserver.speakKeyEvent(event, acss) # type: ignore
@@ -425,6 +447,8 @@ def speakCharacter(character: str, acss: Optional[Any] = None) -> None:
if log: if log:
log.info(f"SPEECH OUTPUT: '{character}'") log.info(f"SPEECH OUTPUT: '{character}'")
_write_to_monitor(character)
if _speechserver: if _speechserver:
_speechserver.speakCharacter(character, acss=acss) # type: ignore _speechserver.speakCharacter(character, acss=acss) # type: ignore