Read all code added. It's definiately a work in progress and does not function currently.

This commit is contained in:
Storm Dragon
2025-08-04 14:41:14 -04:00
parent 8bada48a09
commit 98b9c56af7
13 changed files with 321 additions and 51 deletions

View File

@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific # linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume # Read-all functionality
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume KEY_FENRIR,KEY_CTRL,KEY_DOWN=read_all_by_line
KEY_FENRIR,KEY_CTRL,KEY_PAGEDOWN=read_all_by_page
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout KEY_F4=cycle_keyboard_layout

View File

@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific # linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume # Read-all functionality
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume KEY_FENRIR,KEY_CTRL,KEY_DOWN=read_all_by_line
KEY_FENRIR,KEY_CTRL,KEY_PAGEDOWN=read_all_by_page
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout KEY_F4=cycle_keyboard_layout

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import importlib.util
import os
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "adjustment_base.py")
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
adjustment_command = _module.adjustment_command
class command(adjustment_command):
def __init__(self):
super().__init__("alsa", "volume", "dec")

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import importlib.util
import os
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "adjustment_base.py")
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
adjustment_command = _module.adjustment_command
class command(adjustment_command):
def __init__(self):
super().__init__("alsa", "volume", "inc")

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Read all text line by line from current position")
def run(self):
self.env["runtime"]["OutputManager"].present_text(
"Read-all functionality temporarily disabled",
interrupt=True
)
return
# Check if speechd is available for callbacks
try:
speech_driver = self.env['runtime']['SpeechDriver']
if not (hasattr(speech_driver, '_sd') and speech_driver._sd):
self.env["runtime"]["OutputManager"].present_text(
_("Read all requires speech-dispatcher - not available with current speech driver"),
interrupt=True
)
return
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllByLine run: Error checking speech driver: {e}",
debug.DebugLevel.ERROR
)
return
# Check if ReadAllManager is available
if "ReadAllManager" not in self.env["runtime"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager not in runtime"),
interrupt=True
)
return
elif not self.env["runtime"]["ReadAllManager"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager is None"),
interrupt=True
)
return
# Start continuous line-by-line reading
if self.env["runtime"]["ReadAllManager"].is_active():
# Stop if already active
self.env["runtime"]["ReadAllManager"].stop_read_all()
else:
# Start line-by-line reading
self.env["runtime"]["ReadAllManager"].start_read_all('line')
def set_callback(self, callback):
pass

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Read all text page by page from current position")
def run(self):
# Check if speechd is available for callbacks
try:
speech_driver = self.env['runtime']['SpeechDriver']
if not (hasattr(speech_driver, '_sd') and speech_driver._sd):
self.env["runtime"]["OutputManager"].present_text(
_("Read all requires speech-dispatcher - not available with current speech driver"),
interrupt=True
)
return
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllByPage run: Error checking speech driver: {e}",
debug.DebugLevel.ERROR
)
return
# Check if ReadAllManager is available
if "ReadAllManager" not in self.env["runtime"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager not in runtime"),
interrupt=True
)
return
elif not self.env["runtime"]["ReadAllManager"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager is None"),
interrupt=True
)
return
# Start continuous page-by-page reading
if self.env["runtime"]["ReadAllManager"].is_active():
# Stop if already active
self.env["runtime"]["ReadAllManager"].stop_read_all()
else:
# Start page-by-page reading
self.env["runtime"]["ReadAllManager"].start_read_all('page')
def set_callback(self, callback):
pass

View File

@@ -143,6 +143,9 @@ class FenrirManager:
return return
current_command = event["data"] current_command = event["data"]
# Check for active read-all and interrupt it on any other key press
self._interrupt_read_all_if_active(current_command)
# special modes # special modes
if self.environment["runtime"]["HelpManager"].is_tutorial_mode(): if self.environment["runtime"]["HelpManager"].is_tutorial_mode():
if self.environment["runtime"]["CommandManager"].command_exists( if self.environment["runtime"]["CommandManager"].command_exists(
@@ -166,6 +169,28 @@ class FenrirManager:
current_command, "commands" current_command, "commands"
) )
def _interrupt_read_all_if_active(self, current_command):
"""Check for active read-all and interrupt it on any other key press."""
try:
# Skip interruption for read-all commands themselves (they toggle)
if current_command in ["read_all_by_line", "read_all_by_page"]:
return
# Stop read-all if any other command is executed
if ("ReadAllManager" in self.environment["runtime"] and
self.environment["runtime"]["ReadAllManager"] and
self.environment["runtime"]["ReadAllManager"].is_active()):
self.environment["runtime"]["ReadAllManager"].stop_read_all()
except Exception as e:
# Ignore errors in interrupt logic to prevent cascading issues
self.environment["runtime"]["DebugManager"].write_debug_out(
f"_interrupt_read_all_if_active: {e}",
debug.DebugLevel.ERROR
)
def handle_remote_incomming(self, event): def handle_remote_incomming(self, event):
if not event["data"]: if not event["data"]:
return return

View File

@@ -27,6 +27,7 @@ general_data = {
"ProcessManager", "ProcessManager",
"VmenuManager", "VmenuManager",
"QuickMenuManager", "QuickMenuManager",
"ReadAllManager",
"RemoteManager", "RemoteManager",
"SettingsManager", "SettingsManager",
"SayAllManager", "SayAllManager",

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import threading
import time
class ReadAllManager:
"""Manages continuous read-all functionality with speech completion callbacks."""
def __init__(self):
self.active = False
self.mode = None # 'line' or 'page'
self.stop_requested = False
self.env = None
self.items_read = 0
self._lock = threading.Lock()
def initialize(self, environment):
self.env = environment
def shutdown(self):
self.stop_read_all()
def start_read_all(self, mode):
"""Start continuous reading in line or page mode."""
with self._lock:
if self.active:
self.stop_read_all()
self.active = True
self.mode = mode
self.stop_requested = False
self.items_read = 0
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllManager: Starting read-all mode '{mode}'",
debug.DebugLevel.INFO
)
# Send first navigation key
self._send_navigation_key()
def stop_read_all(self):
"""Stop continuous reading."""
with self._lock:
if not self.active:
return
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllManager: Stopping read-all after {self.items_read} {self.mode}s",
debug.DebugLevel.INFO
)
self.active = False
self.stop_requested = True
self.mode = None
# Cancel any pending speech
try:
self.env['runtime']['SpeechDriver'].cancel()
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllManager: Error canceling speech: {e}",
debug.DebugLevel.ERROR
)
def is_active(self):
"""Check if read-all is currently active."""
return self.active
def get_mode(self):
"""Get current read-all mode ('line' or 'page')."""
return self.mode
def _send_navigation_key(self):
"""Send appropriate navigation key based on mode."""
if not self.active or self.stop_requested:
return
try:
if self.mode == 'line':
key_sequence = [[1, 'KEY_DOWN'], [0, 'KEY_DOWN']]
key_name = 'KEY_DOWN'
elif self.mode == 'page':
key_sequence = [[1, 'KEY_PAGEDOWN'], [0, 'KEY_PAGEDOWN']]
key_name = 'KEY_PAGEDOWN'
else:
return
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllManager: Sending {key_name} ({self.items_read + 1})",
debug.DebugLevel.INFO
)
self.env['runtime']['InputManager'].send_keys(key_sequence)
self.items_read += 1
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllManager: Error sending key: {e}",
debug.DebugLevel.ERROR
)
self.stop_read_all()
def speech_completed(self):
"""Called when speech completes - continue reading if active."""
if not self.active or self.stop_requested:
return
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllManager: Speech completed, continuing read-all",
debug.DebugLevel.INFO
)
# Small delay to prevent overwhelming the system
def continue_reading():
time.sleep(0.1)
if self.active and not self.stop_requested:
self._send_navigation_key()
# Use daemon thread to avoid blocking
thread = threading.Thread(target=continue_reading, daemon=True)
thread.start()

View File

@@ -25,6 +25,7 @@ from fenrirscreenreader.core import outputManager
from fenrirscreenreader.core import processManager from fenrirscreenreader.core import processManager
from fenrirscreenreader.core import punctuationManager from fenrirscreenreader.core import punctuationManager
from fenrirscreenreader.core import quickMenuManager from fenrirscreenreader.core import quickMenuManager
from fenrirscreenreader.core import readAllManager
from fenrirscreenreader.core import remoteManager from fenrirscreenreader.core import remoteManager
from fenrirscreenreader.core import sayAllManager from fenrirscreenreader.core import sayAllManager
from fenrirscreenreader.core import screenManager from fenrirscreenreader.core import screenManager
@@ -747,6 +748,11 @@ class SettingsManager:
"QuickMenuManager" "QuickMenuManager"
] = quickMenuManager.QuickMenuManager() ] = quickMenuManager.QuickMenuManager()
environment["runtime"]["QuickMenuManager"].initialize(environment) environment["runtime"]["QuickMenuManager"].initialize(environment)
environment["runtime"][
"ReadAllManager"
] = readAllManager.ReadAllManager()
environment["runtime"]["ReadAllManager"].initialize(environment)
# only possible after having input and screen managers with clean # only possible after having input and screen managers with clean
# buffer # buffer

View File

@@ -924,12 +924,12 @@ class driver(inputDriver):
try: try:
self.UInputinject.write(e.EV_KEY, e.ecodes[key], state) self.UInputinject.write(e.EV_KEY, e.ecodes[key], state)
self.UInputinject.syn() self.UInputinject.syn()
except Exception as e: except Exception as ex:
self.env["runtime"]["DebugManager"].write_debug_out( self.env["runtime"]["DebugManager"].write_debug_out(
"evdevDriver send_key: Error sending key " "evdevDriver send_key: Error sending key "
+ str(key) + str(key)
+ ": " + ": "
+ str(e), + str(ex),
debug.DebugLevel.ERROR, debug.DebugLevel.ERROR,
) )

View File

@@ -125,8 +125,6 @@ class driver(speech_driver):
# Ensure lock is always released, even if process termination fails # Ensure lock is always released, even if process termination fails
self.lock.release() self.lock.release()
def set_callback(self, callback):
print("SpeechDummyDriver: set_callback")
def clear_buffer(self): def clear_buffer(self):
if not self._is_initialized: if not self._is_initialized:

View File

@@ -142,7 +142,23 @@ class driver(speech_driver):
) )
try: try:
self._sd.speak(text) # Check if read-all is active and add callback if needed
if ("ReadAllManager" in self.env["runtime"] and
self.env["runtime"]["ReadAllManager"] and
self.env["runtime"]["ReadAllManager"].is_active()):
import speechd
def read_all_callback(msg_id, client_id, event_type, *args):
if (event_type == speechd.CallbackType.END and
self.env["runtime"]["ReadAllManager"] and
self.env["runtime"]["ReadAllManager"].is_active()):
self.env["runtime"]["ReadAllManager"].speech_completed()
self._sd.speak(text, callback=read_all_callback,
event_types=(speechd.CallbackType.END,))
else:
self._sd.speak(text)
except Exception as e: except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out( self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver speak:" + str(e), debug.DebugLevel.ERROR "SpeechDriver speak:" + str(e), debug.DebugLevel.ERROR
@@ -210,3 +226,4 @@ class driver(speech_driver):
self.env["runtime"]["DebugManager"].write_debug_out( self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_volume:" + str(e), debug.DebugLevel.ERROR "SpeechDriver set_volume:" + str(e), debug.DebugLevel.ERROR
) )