Speech history added, bound to fenrir+control+h.

This commit is contained in:
Storm Dragon
2026-05-20 21:02:56 -04:00
parent 4caef89f6b
commit ac7348895f
16 changed files with 611 additions and 3 deletions
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("opens speech history")
def run(self):
self.env["runtime"]["SpeechHistoryManager"].open_history()
def set_callback(self, callback):
pass
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("closes speech history")
def run(self):
self.env["runtime"]["SpeechHistoryManager"].close_history()
def set_callback(self, callback):
pass
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("copies current speech history item to the clipboard")
def run(self):
self.env["runtime"]["SpeechHistoryManager"].copy_current_to_clipboard()
def set_callback(self, callback):
pass
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("speaks current speech history item")
def run(self):
self.env["runtime"]["SpeechHistoryManager"].present_current()
def set_callback(self, callback):
pass
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("selects the next speech history item")
def run(self):
self.env["runtime"]["SpeechHistoryManager"].next_entry()
def set_callback(self, callback):
pass
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("selects the previous speech history item")
def run(self):
self.env["runtime"]["SpeechHistoryManager"].prev_entry()
def set_callback(self, callback):
pass
+21 -3
View File
@@ -103,6 +103,10 @@ class FenrirManager:
self.environment["runtime"][
"InputManager"
].clear_event_buffer()
if self.environment["runtime"]["SpeechHistoryManager"].is_active():
self.environment["runtime"][
"InputManager"
].clear_event_buffer()
self.detect_shortcut_command()
@@ -159,6 +163,14 @@ class FenrirManager:
current_command, "vmenu-navigation"
)
return
elif self.environment["runtime"]["SpeechHistoryManager"].is_active():
if self.environment["runtime"]["CommandManager"].command_exists(
current_command, "speech-history"
):
self.environment["runtime"]["CommandManager"].execute_command(
current_command, "speech-history"
)
return
# default
self.environment["runtime"]["CommandManager"].execute_command(
@@ -298,12 +310,18 @@ class FenrirManager:
if self.command != "":
self.singleKeyCommand = True
elif (
self.environment["runtime"]["DiffReviewManager"].is_active()
(
self.environment["runtime"]["DiffReviewManager"].is_active()
or self.environment["runtime"][
"SpeechHistoryManager"
].is_active()
)
and self.command != ""
):
# Diff mode uses non-Fenrir modified bindings (Shift/Ctrl).
# Modal modes use non-Fenrir modified bindings.
# Promote resolved shortcuts to executable commands so
# combinations like Shift+H and Ctrl+Right are dispatched.
# combinations like Shift+H, Ctrl+Right, and plain arrows
# are dispatched.
self.singleKeyCommand = True
if not (self.singleKeyCommand or self.modifierInput):
@@ -20,6 +20,7 @@ general_data = {
"ScreenManager",
"InputManager",
"OutputManager",
"SpeechHistoryManager",
"HelpManager",
"MemoryManager",
"EventManager",
@@ -48,5 +49,6 @@ general_data = {
"onSwitchApplicationProfile",
"help",
"vmenu-navigation",
"speech-history",
],
}
@@ -75,6 +75,14 @@ class OutputManager:
return
if (len(text) > 1) and (text.strip(string.whitespace) == ""):
return
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"speech", "enabled"
):
speech_history_manager = self.env["runtime"].get(
"SpeechHistoryManager"
)
if speech_history_manager:
speech_history_manager.add_text(text)
is_capital = self._should_announce_capital(text, announce_capital)
use_pitch_for_capital = False
@@ -16,6 +16,7 @@ runtime_data = {
"CommandManager": None,
"ScreenManager": None,
"OutputManager": None,
"SpeechHistoryManager": None,
"DebugManager": None,
"SettingsManager": None,
"FenrirManager": None,
@@ -32,6 +32,7 @@ settings_data = {
"hardware_baud_rate": 9600,
"auto_read_incoming": True,
"read_numbers_as_digits": False,
"history_size": 50,
"rapid_update_threshold": 5,
"rapid_update_window": 0.3,
"batch_flush_interval": 0.5,
@@ -29,6 +29,7 @@ from fenrirscreenreader.core import readAllManager
from fenrirscreenreader.core import remoteManager
from fenrirscreenreader.core import sayAllManager
from fenrirscreenreader.core import screenManager
from fenrirscreenreader.core import speechHistoryManager
from fenrirscreenreader.core import tableManager
from fenrirscreenreader.core import textManager
from fenrirscreenreader.core import vmenuManager
@@ -738,6 +739,11 @@ class SettingsManager:
environment["runtime"]["OutputManager"] = outputManager.OutputManager()
environment["runtime"]["OutputManager"].initialize(environment)
environment["runtime"][
"SpeechHistoryManager"
] = speechHistoryManager.SpeechHistoryManager()
environment["runtime"]["SpeechHistoryManager"].initialize(environment)
environment["runtime"]["InputManager"] = inputManager.InputManager()
environment["runtime"]["InputManager"].initialize(environment)
self.load_keyboard_layout(environment)
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
class SpeechHistoryManager:
def __init__(self):
self.env = None
self.history = []
self.curr_index = -1
self.active = False
self.bindings_backup = None
self.raw_bindings_backup = None
def initialize(self, environment):
self.env = environment
def shutdown(self):
self.set_active(False)
def is_active(self):
return self.active
def add_text(self, text):
if self.active:
return False
if not isinstance(text, str):
return False
if text == "":
return False
text_key = self._get_history_key(text)
if text_key == "":
return False
if text_key in [self._get_history_key(item) for item in self.history]:
return False
history_size = self._get_history_size()
if history_size <= 0:
return False
self.history.insert(0, text)
del self.history[history_size:]
if self.curr_index >= len(self.history):
self.curr_index = len(self.history) - 1
return True
def open_history(self):
if not self.history:
self.env["runtime"]["OutputManager"].present_text(
_("speech history empty"), interrupt=True
)
return False
self.curr_index = -1
self.set_active(True)
self.env["runtime"]["OutputManager"].present_text(
_("Speech history"), interrupt=True
)
return True
def close_history(self, announce=True):
if announce:
self.env["runtime"]["OutputManager"].present_text(
_("speech history closed"), interrupt=True
)
self.set_active(False)
def next_entry(self):
if not self._has_history():
return
if self.curr_index == -1:
self.curr_index = 0
self.present_current()
return
if self.curr_index <= 0:
self.curr_index = 0
self.env["runtime"]["OutputManager"].present_text(
_("First speech history item"), interrupt=True
)
self.present_current(interrupt=False)
return
self.curr_index -= 1
self.present_current()
def prev_entry(self):
if not self._has_history():
return
if self.curr_index == -1:
self.curr_index = 0
self.present_current()
return
if self.curr_index >= len(self.history) - 1:
self.curr_index = len(self.history) - 1
self.env["runtime"]["OutputManager"].present_text(
_("Last speech history item"), interrupt=True
)
self.present_current(interrupt=False)
return
self.curr_index += 1
self.present_current()
def present_current(self, interrupt=True):
if not self._has_history():
self.env["runtime"]["OutputManager"].present_text(
_("speech history empty"), interrupt=True
)
return
self.env["runtime"]["OutputManager"].present_text(
self.history[self.curr_index], interrupt=interrupt
)
def copy_current_to_clipboard(self):
if not self._has_history():
self.close_history()
return
text = self.history[self.curr_index]
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", text
)
self.env["runtime"]["OutputManager"].present_text(
_("copied to clipboard"),
sound_icon="CopyToClipboard",
interrupt=True,
)
self.set_active(False)
def set_active(self, active):
if active == self.active:
return
self.active = active
if self.active:
self._install_bindings()
else:
self._restore_bindings()
def _has_history(self):
if not self.history:
self.curr_index = -1
return False
if self.curr_index >= len(self.history):
self.curr_index = len(self.history) - 1
return True
def _get_history_size(self):
try:
return self.env["runtime"]["SettingsManager"].get_setting_as_int(
"speech", "history_size"
)
except Exception:
return 50
def _get_history_key(self, text):
return " ".join(text.split())
def _install_bindings(self):
self.bindings_backup = self.env["bindings"].copy()
self.raw_bindings_backup = self.env["rawBindings"].copy()
self.env["bindings"] = {
str([1, ["KEY_UP"]]): "SPEECH_HISTORY_PREV",
str([1, ["KEY_DOWN"]]): "SPEECH_HISTORY_NEXT",
str([1, ["KEY_SPACE"]]): "SPEECH_HISTORY_CURRENT",
str([1, ["KEY_ENTER"]]): "SPEECH_HISTORY_COPY",
str([1, ["KEY_KPENTER"]]): "SPEECH_HISTORY_COPY",
str([1, ["KEY_ESC"]]): "SPEECH_HISTORY_CLOSE",
}
self.env["rawBindings"] = {
str([1, ["KEY_UP"]]): [1, ["KEY_UP"]],
str([1, ["KEY_DOWN"]]): [1, ["KEY_DOWN"]],
str([1, ["KEY_SPACE"]]): [1, ["KEY_SPACE"]],
str([1, ["KEY_ENTER"]]): [1, ["KEY_ENTER"]],
str([1, ["KEY_KPENTER"]]): [1, ["KEY_KPENTER"]],
str([1, ["KEY_ESC"]]): [1, ["KEY_ESC"]],
}
def _restore_bindings(self):
if self.bindings_backup is not None:
self.env["bindings"] = self.bindings_backup
if self.raw_bindings_backup is not None:
self.env["rawBindings"] = self.raw_bindings_backup
self.bindings_backup = None
self.raw_bindings_backup = None