33 Commits

Author SHA1 Message Date
Storm Dragon 5ff653bd00 Progress bar and commit validator updates. 2025-08-31 14:54:07 -04:00
Storm Dragon 356f4b01c1 Got the version file wrong again. Need to be more careful. 2025-08-31 14:44:46 -04:00
Storm Dragon c7ad4d9200 merged to master. 2025-08-31 14:39:26 -04:00
Storm Dragon 90ffc2fc08 removing keyboard layouts that are no longer used 2025-08-23 18:29:42 -04:00
Storm Dragon b635f7538b Latest changes merged. Minor fixes to progress bar detection. Fixed my old habbit of camel case variable names. :) 2025-08-23 12:20:05 -04:00
Storm Dragon e255651c28 Merged testing. 2025-08-22 00:29:02 -04:00
Storm Dragon 98b9c56af7 Read all code added. It's definiately a work in progress and does not function currently. 2025-08-04 14:41:14 -04:00
Storm Dragon 8bada48a09 Fixed errors in README. Moved the audio configuration script stuff nearer the top. 2025-08-04 14:36:43 -04:00
Storm Dragon e9a0101fe7 Merged README.md 2025-08-04 14:26:55 -04:00
Storm Dragon 914535d12b A few bug fixes, better checking in place to make sure syntax and other errors do not make it to commits. 2025-07-24 18:34:12 -04:00
Storm Dragon 2dd732dc9d Emojis added, improvements to pyt mode. 2025-07-19 16:50:53 -04:00
Storm Dragon e177c7f486 Final merge for version 2025.07.16. If no problems will tag later today. 2025-07-16 12:33:21 -04:00
Storm Dragon b9abf02b12 A few minor tweaks in preparation for release. 2025-07-13 15:31:57 -04:00
Storm Dragon fe5e2c065e Merge branch 'testing' moving closer to tagged release. 2025-07-09 18:32:15 -04:00
Storm Dragon ef3ebee10c Preparing for new tagged version. Please watch for bugs. 2025-07-09 09:33:19 -04:00
Storm Dragon 271c4fc18f Table mode fixes and improvements to application detection. 2025-07-08 14:41:43 -04:00
Storm Dragon ea56b90b48 Oops, getting used to this pep8 thing myself. Fixed codeName to code_name. 2025-07-06 18:51:43 -04:00
Storm Dragon 1268d989b7 Merge after mostly converting to pep8 compliance. 2025-07-06 18:34:28 -04:00
Storm Dragon 23c3ad20a1 More code optmizations. Removed fenrir+pk_plus. The functionality of bringing back speech has been added to the temperary speech interruption key kp_enter. 2025-06-30 22:25:01 -04:00
Storm Dragon 8af1cca879 Latest changes and bug fixes. 2025-06-27 21:18:27 -04:00
Storm Dragon a394ea0222 Code cleanup and bug fixes. 2025-06-20 02:19:57 -04:00
Storm Dragon efb308ac72 latest testing code merged. Nothing major reported from testing branch, so if we get no reports, this will become the next stable release. I'm waiting a bit to tag because major new features introduced. 2025-06-17 00:53:28 -04:00
Storm Dragon f6be6c54fb Bug fixes mostlry, tested and seems to be working better. 2025-06-13 23:21:46 -04:00
Storm Dragon f18c31df6c Merge branch 'testing' bug fix for remoteDriver 2025-06-07 18:24:44 -04:00
Storm Dragon 3dca3e5b23 Merged for new release. 2025-06-07 12:23:53 -04:00
Storm Dragon 1b9a9a90b1 Fixed version conflict. 2025-06-06 20:35:07 -04:00
Storm Dragon 4c8c8d896d Fixed version conflict. 2025-06-05 16:05:11 -04:00
Storm Dragon 4672592dba Latest merge from testing. 2025-04-28 15:41:14 -04:00
Storm Dragon 7a12992b88 latest release. 2025-04-17 00:36:26 -04:00
Storm Dragon 7a87fb51bb Fixed version for master branch. 2025-04-14 20:04:14 -04:00
Storm Dragon 2cc2fda28c Actually fix the version file this time. 2025-03-02 17:59:20 -05:00
Storm Dragon c99d0f6ee1 Fixed version.py. 2025-03-02 17:44:32 -05:00
Storm Dragon 5b642cd9e2 Fixed error in settings file. 2025-02-26 17:41:01 -05:00
16 changed files with 333 additions and 64 deletions
+3 -2
View File
@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
# Read-all functionality
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_LEFTCTRL,KEY_F4=cycle_keyboard_layout
+3 -2
View File
@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
# Read-all functionality
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_LEFTCTRL,KEY_F4=cycle_keyboard_layout
@@ -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")
@@ -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")
@@ -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
@@ -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
@@ -279,7 +279,7 @@ class command:
return
# Pattern 6: Claude Code progress indicators
claude_progress_match = re.search(r'^[·✶✢✻*]\s+[\w\s-]+[…\.]*\s*\(esc to interrupt\)\s*$', text)
claude_progress_match = re.search(r'^[·✶✢✻*]\s+[^(]+[…\.]*\s*\(esc to interrupt[^)]*\)\s*$', text)
if claude_progress_match:
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
self.play_activity_beep()
@@ -143,6 +143,9 @@ class FenrirManager:
return
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
if self.environment["runtime"]["HelpManager"].is_tutorial_mode():
if self.environment["runtime"]["CommandManager"].command_exists(
@@ -166,6 +169,28 @@ class FenrirManager:
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):
if not event["data"]:
return
@@ -27,6 +27,7 @@ general_data = {
"ProcessManager",
"VmenuManager",
"QuickMenuManager",
"ReadAllManager",
"RemoteManager",
"SettingsManager",
"SayAllManager",
@@ -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()
@@ -25,6 +25,7 @@ from fenrirscreenreader.core import outputManager
from fenrirscreenreader.core import processManager
from fenrirscreenreader.core import punctuationManager
from fenrirscreenreader.core import quickMenuManager
from fenrirscreenreader.core import readAllManager
from fenrirscreenreader.core import remoteManager
from fenrirscreenreader.core import sayAllManager
from fenrirscreenreader.core import screenManager
@@ -747,6 +748,11 @@ class SettingsManager:
"QuickMenuManager"
] = quickMenuManager.QuickMenuManager()
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
# buffer
+1 -2
View File
@@ -5,5 +5,4 @@
# By Chrys, Storm Dragon, and contributors.
version = "2025.08.31"
codeName = "testing"
code_name = "testing"
code_name = "master"
@@ -924,12 +924,12 @@ class driver(inputDriver):
try:
self.UInputinject.write(e.EV_KEY, e.ecodes[key], state)
self.UInputinject.syn()
except Exception as e:
except Exception as ex:
self.env["runtime"]["DebugManager"].write_debug_out(
"evdevDriver send_key: Error sending key "
+ str(key)
+ ": "
+ str(e),
+ str(ex),
debug.DebugLevel.ERROR,
)
@@ -125,8 +125,6 @@ class driver(speech_driver):
# Ensure lock is always released, even if process termination fails
self.lock.release()
def set_callback(self, callback):
print("SpeechDummyDriver: set_callback")
def clear_buffer(self):
if not self._is_initialized:
@@ -142,7 +142,23 @@ class driver(speech_driver):
)
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:
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver speak:" + str(e), debug.DebugLevel.ERROR
@@ -210,3 +226,4 @@ class driver(speech_driver):
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_volume:" + str(e), debug.DebugLevel.ERROR
)
+10 -10
View File
@@ -76,19 +76,19 @@ else
sed -i "s/version = [\"']\{0,1\}[0-9.]\+[\"']\{0,1\}/version = \"$newVersion\"/" "$versionFile"
fi
# Check if codeName exists and isn't "stable"
if grep -q "codeName.*=.*\"stable\"" "$versionFile"; then
# Don't modify stable codeName
# Check if code_name exists and isn't "stable"
if grep -q "code_name.*=.*\"stable\"" "$versionFile"; then
# Don't modify stable code_name
:
elif grep -q "codeName.*=.*\"$branchName\"" "$versionFile"; then
# CodeName already matches branch name, no need to update
elif grep -q "code_name.*=.*\"$branchName\"" "$versionFile"; then
# code_name already matches branch name, no need to update
:
elif grep -q "codeName" "$versionFile"; then
# Update existing codeName
sed -i "s/codeName = [\"']\{0,1\}[^\"']*[\"']\{0,1\}/codeName = \"$branchName\"/" "$versionFile"
elif grep -q "code_name" "$versionFile"; then
# Update existing code_name
sed -i "s/code_name = [\"']\{0,1\}[^\"']*[\"']\{0,1\}/code_name = \"$branchName\"/" "$versionFile"
else
# Add codeName after the version line
sed -i "/version = / a\codeName = \"$branchName\"" "$versionFile"
# Add code_name after the version line
sed -i "/version = / a\code_name = \"$branchName\"" "$versionFile"
fi
# Check if the file was actually modified