19 Commits

Author SHA1 Message Date
Storm Dragon f18c31df6c Merge branch 'testing' bug fix for remoteDriver 2025-06-07 18:24:44 -04:00
Storm Dragon ca0e3b5987 Reverted changes to remot driver because some settings, e.g. highlight mode, stopped working. 2025-06-07 18:22:31 -04:00
Storm Dragon 0009d90a68 Updated .gitignore 2025-06-07 13:35:10 -04:00
Storm Dragon 2c2efc56f0 Progress beeps should now rok with dd's progress flag. 2025-06-07 13:23:45 -04:00
Storm Dragon 3dca3e5b23 Merged for new release. 2025-06-07 12:23:53 -04:00
Storm Dragon 27c35939b1 A few minor tweaks to progressbar beeps. 2025-06-07 11:13:15 -04:00
Storm Dragon 7e87ebf04b Attempt to improve how Fenrir reads multiple characters in a row, e.g. [------]. 2025-06-07 10:51:02 -04:00
Storm Dragon ec6c135581 Keybinding for silence until prompt returns added to laptop.conf. Fenrir+Shift+Enter 2025-06-07 10:20:09 -04:00
Storm Dragon 998c63cc71 Fixed a few typos in settings file, changed progressbars to true by default. 2025-06-07 01:48:58 -04:00
Storm Dragon 26c6e32c59 Add the ability to add custom prompts to the settings file, generic prompts are covered with the existing detection code. 2025-06-07 01:26:21 -04:00
Storm Dragon 97e2da614b 2 new features, silence speech until prompt returns and progress bar beeps. 2025-06-07 00:52:13 -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
13 changed files with 519 additions and 51 deletions
+1
View File
@@ -6,3 +6,4 @@ dist/
build/
*.kate-swp
.directory
CLAUDE.md
+2 -1
View File
@@ -73,7 +73,8 @@ KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
KEY_KPPLUS=last_incoming
KEY_KPPLUS=progress_bar_monitor
KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
+2 -1
View File
@@ -75,9 +75,10 @@ KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
KEY_FENRIR,KEY_ENTER=temp_disable_speech
KEY_FENRIR,KEY_SHIFT,KEY_ENTER=silence_until_prompt
KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_ENTER=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
KEY_FENRIR,KEY_SHIFT,KEY_E=toggle_emoticons
KEY_FENRIR,KEY_ENTER=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
+2 -1
View File
@@ -72,7 +72,8 @@ KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
KEY_KPPLUS=last_incoming
KEY_KPPLUS=progress_bar_monitor
KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
#=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
+21 -2
View File
@@ -28,6 +28,9 @@ genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
#the following command is used to generate a frequency beep
genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
# Enable progress bar monitoring with ascending tones by default
progressMonitoring=True
[speech]
# Turn speech on or off:
enabled=True
@@ -163,7 +166,7 @@ autoPresentIndent=False
# 1 = sound only
# 2 = speak only
autoPresentIndentMode=1
# play a sound when attributes are changeing
# play a sound when attributes change
hasAttributes=True
# shell for PTY emulatiun (empty = default shell)
shell=
@@ -211,8 +214,24 @@ list=
vmenuPath=
quickMenu=speech#rate;speech#pitch;speech#volume
[prompt]
# Custom prompt patterns for silence until prompt feature
# You can add your own shell prompt patterns as regular expressions
# Each pattern should be on a separate line, format: customPatterns=pattern1,pattern2,pattern3
# Examples:
# For PS1='[\u@\h \W] \$ ' use: \[.*@.*\s.*\]\s*[$#>]\s*
# For "[user@hostname ~] $" use: \[.*@.*\s.*\]\s*[$#>]\s*
# For custom prompts ending with specific strings, use patterns like: .*your_prompt_ending$
customPatterns=
# Specific prompt strings to match exactly (useful for very specific custom prompts)
# Format: exactMatches=prompt1,prompt2,prompt3
# Examples:
# exactMatches=[storm@fenrir ~] $,[root@fenrir ~] #
exactMatches=
[time]
# automatic time anouncement
# automatic time announcement
enabled=False
# present time
presentTime=True
@@ -1,23 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Presents the text which was last received')
def run(self):
self.env['runtime']['outputManager'].presentText(self.env['screen']['newDelta'], interrupt=True)
def setCallback(self, callback):
pass
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import re
import time
import threading
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
# Use commandBuffer like other commands
if 'progressMonitoring' not in self.env['commandBuffer']:
# Check if progress monitoring should be enabled by default from settings
try:
defaultEnabled = self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'progressMonitoring')
except:
# If setting doesn't exist, default to False
defaultEnabled = False
self.env['commandBuffer']['progressMonitoring'] = defaultEnabled
self.env['commandBuffer']['lastProgressTime'] = 0
self.env['commandBuffer']['lastProgressValue'] = -1
def shutdown(self):
self.stopProgressMonitoring()
def getDescription(self):
return _('Toggle progress bar monitoring with ascending tones')
def run(self):
# Check if commandBuffer exists
if 'progressMonitoring' not in self.env['commandBuffer']:
self.env['commandBuffer']['progressMonitoring'] = False
self.env['commandBuffer']['lastProgressTime'] = 0
self.env['commandBuffer']['lastProgressValue'] = -1
if self.env['commandBuffer']['progressMonitoring']:
self.stopProgressMonitoring()
self.env['runtime']['outputManager'].presentText(_("Progress monitoring disabled"), interrupt=True)
else:
self.startProgressMonitoring()
self.env['runtime']['outputManager'].presentText(_("Progress monitoring enabled"), interrupt=True)
def startProgressMonitoring(self):
self.env['commandBuffer']['progressMonitoring'] = True
self.env['commandBuffer']['lastProgressTime'] = time.time()
self.env['commandBuffer']['lastProgressValue'] = -1
# Don't control speech - let user decide with silence_until_prompt
def stopProgressMonitoring(self):
self.env['commandBuffer']['progressMonitoring'] = False
# Don't control speech - progress monitor is beep-only
def detectProgress(self, text):
if not self.env['runtime']['progressMonitoring']:
return
currentTime = time.time()
# Pattern 1: Percentage (50%, 25.5%, etc.)
percentMatch = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
if percentMatch:
percentage = float(percentMatch.group(1))
if percentage != self.env['runtime']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['runtime']['lastProgressValue'] = percentage
self.env['runtime']['lastProgressTime'] = currentTime
return
# Pattern 2: Fraction (15/100, 3 of 10, etc.)
fractionMatch = re.search(r'(\d+)\s*(?:of|/)\s*(\d+)', text)
if fractionMatch:
current = int(fractionMatch.group(1))
total = int(fractionMatch.group(2))
if total > 0:
percentage = (current / total) * 100
if percentage != self.env['runtime']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['runtime']['lastProgressValue'] = percentage
self.env['runtime']['lastProgressTime'] = currentTime
return
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
barMatch = re.search(r'\[([#=\-\*]+)([^\]]*)\]', text)
if barMatch:
filled = len(barMatch.group(1))
total = filled + len(barMatch.group(2))
if total > 0:
percentage = (filled / total) * 100
if percentage != self.env['runtime']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['runtime']['lastProgressValue'] = percentage
self.env['runtime']['lastProgressTime'] = currentTime
return
# Pattern 4: Generic activity indicators (Loading..., Working..., etc.)
activityPattern = re.search(r'(loading|processing|working|installing|downloading|compiling|building).*\.{2,}', text, re.IGNORECASE)
if activityPattern:
# Play a steady beep every 2 seconds for ongoing activity
if currentTime - self.env['runtime']['lastProgressTime'] >= 2.0:
self.playActivityBeep()
self.env['runtime']['lastProgressTime'] = currentTime
def playProgressTone(self, percentage):
# Map 0-100% to 400-1200Hz frequency range
frequency = 400 + (percentage * 8)
frequency = max(400, min(1200, frequency)) # Clamp to safe range
self.env['runtime']['outputManager'].playFrequence(frequency, 0.15, interrupt=False)
def playActivityBeep(self):
# Single tone for generic activity
self.env['runtime']['outputManager'].playFrequence(800, 0.1, interrupt=False)
def setCallback(self, callback):
pass
@@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import re
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
# Use commandBuffer like other commands
if 'silenceUntilPrompt' not in self.env['commandBuffer']:
self.env['commandBuffer']['silenceUntilPrompt'] = False
def shutdown(self):
pass
def getDescription(self):
return _('Toggle speech silence until shell prompt returns')
def run(self):
if self.env['commandBuffer']['silenceUntilPrompt']:
self.disableSilence()
else:
self.enableSilence()
def enableSilence(self):
self.env['commandBuffer']['silenceUntilPrompt'] = True
self.env['runtime']['outputManager'].presentText(_("Speech silenced until prompt returns"), soundIcon='SpeechOff', interrupt=True)
# Disable speech but don't use the normal temp disable that reactivates on keypress
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', 'False')
def disableSilence(self):
self.env['commandBuffer']['silenceUntilPrompt'] = False
# Re-enable speech
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', 'True')
self.env['runtime']['outputManager'].presentText(_("Speech restored"), soundIcon='SpeechOn', interrupt=True)
def checkForPrompt(self, text):
"""Check if the current line contains a shell prompt pattern"""
if not self.env['commandBuffer']['silenceUntilPrompt']:
return False
# First check for exact matches from settings (with backward compatibility)
try:
exactMatches = self.env['runtime']['settingsManager'].getSetting('prompt', 'exactMatches')
if exactMatches:
exactList = [match.strip() for match in exactMatches.split(',') if match.strip()]
for exactMatch in exactList:
if text.strip() == exactMatch:
self.env['runtime']['debug'].writeDebugOut("Found exact prompt match: " + exactMatch, debug.debugLevel.INFO)
self.disableSilence()
return True
except:
# Prompt section doesn't exist in settings, skip custom exact matches
pass
# Get custom patterns from settings (with backward compatibility)
promptPatterns = []
try:
customPatterns = self.env['runtime']['settingsManager'].getSetting('prompt', 'customPatterns')
# Add custom patterns from settings if they exist
if customPatterns:
customList = [pattern.strip() for pattern in customPatterns.split(',') if pattern.strip()]
promptPatterns.extend(customList)
except:
# Prompt section doesn't exist in settings, skip custom patterns
pass
# Add default shell prompt patterns
promptPatterns.extend([
r'^\s*\\\$\s*$', # Just $ (with whitespace)
r'^\s*#\s*$', # Just # (with whitespace)
r'^\s*>\s*$', # Just > (with whitespace)
r'.*@.*[\\\$#>]\s*$', # Contains @ and ends with prompt char (user@host style)
r'^\[.*\]\s*[\\\$#>]\s*$', # [anything]$ style prompts
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$', # Simple shell names like bash-5.1$
])
for pattern in promptPatterns:
try:
if re.search(pattern, text.strip()):
self.env['runtime']['debug'].writeDebugOut("Found prompt pattern: " + pattern, debug.debugLevel.INFO)
self.disableSilence()
return True
except re.error as e:
# Invalid regex pattern, skip it and log the error
self.env['runtime']['debug'].writeDebugOut("Invalid prompt pattern: " + pattern + " Error: " + str(e), debug.debugLevel.ERROR)
continue
return False
def setCallback(self, callback):
pass
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return 'Detects progress patterns for progress bar monitoring'
def run(self):
# Only run if progress monitoring is enabled
try:
if 'progressMonitoring' in self.env['commandBuffer'] and self.env['commandBuffer']['progressMonitoring']:
# Only check new incoming text (newDelta), but filter out screen changes
if self.env['screen']['newDelta'] and self.isRealProgressUpdate():
self.detectProgress(self.env['screen']['newDelta'])
except Exception as e:
# Silently ignore errors to avoid disrupting normal operation
pass
def isRealProgressUpdate(self):
"""Check if this is a real progress update vs screen change/window switch"""
# If the screen/application changed, it's not a progress update
if self.env['runtime']['screenManager'].isScreenChange():
return False
# If there was a large cursor movement, it's likely navigation, not progress
if self.env['runtime']['cursorManager'].isCursorVerticalMove():
xMove = abs(self.env['screen']['newCursor']['x'] - self.env['screen']['oldCursor']['x'])
yMove = abs(self.env['screen']['newCursor']['y'] - self.env['screen']['oldCursor']['y'])
# Large movements suggest navigation, not progress output
if yMove > 2 or xMove > 20:
return False
# Check if delta is too large (screen change) vs small incremental updates
deltaLength = len(self.env['screen']['newDelta'])
if deltaLength > 200: # Allow longer progress lines like Claude Code's status
return False
return True
def detectProgress(self, text):
import re
import time
currentTime = time.time()
# Debug: Print what we're checking
self.env['runtime']['debug'].writeDebugOut("Progress detector checking: '" + text + "'", debug.debugLevel.INFO)
# Note: Auto-disable on 100% completion removed to respect user settings
# Pattern 1: Percentage (50%, 25.5%, etc.)
percentMatch = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
if percentMatch:
percentage = float(percentMatch.group(1))
# Only trigger on realistic progress percentages (0-100%)
if 0 <= percentage <= 100:
self.env['runtime']['debug'].writeDebugOut("Found percentage: " + str(percentage), debug.debugLevel.INFO)
if percentage != self.env['commandBuffer']['lastProgressValue']:
self.env['runtime']['debug'].writeDebugOut("Playing tone for: " + str(percentage), debug.debugLevel.INFO)
self.playProgressTone(percentage)
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 1b: Time/token activity (not percentage-based, so use single beep)
timeMatch = re.search(r'(\d+)s\s', text)
tokenMatch = re.search(r'(\d+)\s+tokens', text)
# Pattern 1c: dd command output (bytes copied with transfer rate)
ddMatch = re.search(r'\d+\s+bytes.*copied.*\d+\s+s.*[kMGT]?B/s', text)
# Pattern 1d: Curl-style transfer data (bytes, speed indicators)
curlMatch = re.search(r'(\d+\s+\d+\s+\d+\s+\d+.*?(?:k|M|G)?.*?--:--:--|Speed)', text)
if timeMatch or tokenMatch or ddMatch or curlMatch:
# For non-percentage progress, use a single activity beep every 2 seconds
if currentTime - self.env['commandBuffer']['lastProgressTime'] >= 2.0:
self.env['runtime']['debug'].writeDebugOut("Playing activity beep for transfer progress", debug.debugLevel.INFO)
self.playActivityBeep()
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 2: Fraction (15/100, 3 of 10, etc.)
fractionMatch = re.search(r'(\d+)\s*(?:of|/)\s*(\d+)', text)
if fractionMatch:
current = int(fractionMatch.group(1))
total = int(fractionMatch.group(2))
if total > 0:
percentage = (current / total) * 100
if percentage != self.env['commandBuffer']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
barMatch = re.search(r'\[([#=\-\*]+)([^\]]*)\]', text)
if barMatch:
filled = len(barMatch.group(1))
total = filled + len(barMatch.group(2))
if total > 0:
percentage = (filled / total) * 100
if percentage != self.env['commandBuffer']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 4: Generic activity indicators (Loading..., Working..., etc.)
activityPattern = re.search(r'(loading|processing|working|installing|downloading|compiling|building).*\.{2,}', text, re.IGNORECASE)
if activityPattern:
# Play a steady beep every 2 seconds for ongoing activity
if currentTime - self.env['commandBuffer']['lastProgressTime'] >= 2.0:
self.playActivityBeep()
self.env['commandBuffer']['lastProgressTime'] = currentTime
def playProgressTone(self, percentage):
# Map 0-100% to 400-1200Hz frequency range
frequency = 400 + (percentage * 8)
frequency = max(400, min(1200, frequency)) # Clamp to safe range
# Use Sox directly for clean quiet tones like: play -qn synth .1 tri 400 gain -8
self.playQuietTone(frequency, 0.1)
def playActivityBeep(self):
# Single tone for generic activity
self.playQuietTone(800, 0.08)
def playQuietTone(self, frequency, duration):
"""Play a quiet tone using Sox directly"""
import subprocess
import shlex
# Build the Sox command: play -qn synth <duration> tri <frequency> gain -8
command = f"play -qn synth {duration} tri {frequency} gain -8"
try:
# Only play if sound is enabled
if self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
subprocess.Popen(shlex.split(command), stdin=None, stdout=None, stderr=None, shell=False)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut("Sox tone error: " + str(e), debug.debugLevel.ERROR)
def setCallback(self, callback):
pass
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return 'Detects shell prompts for silence until prompt feature'
def run(self):
# Only run if silence until prompt is active
try:
if 'silenceUntilPrompt' in self.env['commandBuffer'] and self.env['commandBuffer']['silenceUntilPrompt']:
# Check the current line for prompt patterns
if self.env['screen']['newContentText']:
lines = self.env['screen']['newContentText'].split('\n')
if lines and self.env['screen']['newCursor']['y'] < len(lines):
currentLine = lines[self.env['screen']['newCursor']['y']]
self.checkForPrompt(currentLine)
except Exception as e:
# Silently ignore errors to avoid disrupting normal operation
pass
def checkForPrompt(self, text):
"""Check if the current line contains a shell prompt pattern"""
import re
# Debug: Print what we're checking
self.env['runtime']['debug'].writeDebugOut("Prompt detector checking: '" + text + "'", debug.debugLevel.INFO)
# First check for exact matches from settings (with backward compatibility)
try:
exactMatches = self.env['runtime']['settingsManager'].getSetting('prompt', 'exactMatches')
if exactMatches:
exactList = [match.strip() for match in exactMatches.split(',') if match.strip()]
for exactMatch in exactList:
if text.strip() == exactMatch:
self.env['runtime']['debug'].writeDebugOut("Found exact prompt match: " + exactMatch, debug.debugLevel.INFO)
self._restoreSpeech()
return True
except:
# Prompt section doesn't exist in settings, skip custom exact matches
pass
# Get custom patterns from settings (with backward compatibility)
promptPatterns = []
try:
customPatterns = self.env['runtime']['settingsManager'].getSetting('prompt', 'customPatterns')
# Add custom patterns from settings if they exist
if customPatterns:
customList = [pattern.strip() for pattern in customPatterns.split(',') if pattern.strip()]
promptPatterns.extend(customList)
except:
# Prompt section doesn't exist in settings, skip custom patterns
pass
# Add default shell prompt patterns
promptPatterns.extend([
r'^\s*\\\$\s*$', # Just $ (with whitespace)
r'^\s*#\s*$', # Just # (with whitespace)
r'^\s*>\s*$', # Just > (with whitespace)
r'.*@.*[\\\$#>]\s*$', # Contains @ and ends with prompt char (user@host style)
r'^\[.*\]\s*[\\\$#>]\s*$', # [anything]$ style prompts
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$', # Simple shell names like bash-5.1$
])
for pattern in promptPatterns:
try:
if re.search(pattern, text.strip()):
self.env['runtime']['debug'].writeDebugOut("Found prompt pattern: " + pattern, debug.debugLevel.INFO)
self._restoreSpeech()
return True
except re.error as e:
# Invalid regex pattern, skip it and log the error
self.env['runtime']['debug'].writeDebugOut("Invalid prompt pattern: " + pattern + " Error: " + str(e), debug.debugLevel.ERROR)
continue
return False
def _restoreSpeech(self):
"""Helper method to restore speech when prompt is detected"""
# Disable silence mode
self.env['commandBuffer']['silenceUntilPrompt'] = False
# Re-enable speech
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', 'True')
self.env['runtime']['outputManager'].presentText(_("Speech restored"), soundIcon='SpeechOn', interrupt=True)
def setCallback(self, callback):
pass
+3 -3
View File
@@ -38,7 +38,7 @@ class textManager():
if name[0] == name[1]:
newText += ' ' + str(numberOfChars) + ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name[0], True) + ' '
else:
newText += ' ' + str(int(numberOfChars / 2)) + ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name, True) + ' '
newText += ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name[0], True) + ' ' + str(int(numberOfChars / 2)) + ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name[1], True) + ' '
lastPos = span[1]
if lastPos != 0:
newText += ' '
@@ -46,7 +46,7 @@ class textManager():
lastPos = 0
for match in self.regExSingle.finditer(newText):
span = match.span()
result += text[lastPos:span[0]]
result += newText[lastPos:span[0]]
numberOfChars = len(newText[span[0]:span[1]])
name = newText[span[0]:span[1]][:2]
if not self.env['runtime']['punctuationManager'].isPuctuation(name[0]):
@@ -55,7 +55,7 @@ class textManager():
if name[0] == name[1]:
result += ' ' + str(numberOfChars) + ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name[0], True) + ' '
else:
result += ' ' + str(int(numberOfChars / 2)) + ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name, True) + ' '
result += ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name[0], True) + ' ' + str(int(numberOfChars / 2)) + ' ' + self.env['runtime']['punctuationManager'].proceedPunctuation(name[1], True) + ' '
lastPos = span[1]
if lastPos != 0:
result += ' '
+2 -2
View File
@@ -4,5 +4,5 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
version = "2025.06.06"
codeName = "testing"
version = "2025.06.07"
codeName = "master"
@@ -45,28 +45,17 @@ class driver(remoteDriver):
continue
if self.fenrirSock in r:
client_sock, client_addr = self.fenrirSock.accept()
response = "ERROR: Failed to process command\n"
try:
rawdata = client_sock.recv(8129)
data = rawdata.decode("utf-8").rstrip().lstrip()
if data:
# Process the command and get response
result = self.env['runtime']['remoteManager'].handleRemoteIncommingWithResponse(data)
if result['success']:
response = f"OK: {result['message']}\n"
else:
response = f"ERROR: {result['message']}\n"
else:
response = "ERROR: Empty command\n"
except Exception as e:
response = f"ERROR: {str(e)}\n"
# Send response back to client
try:
client_sock.send(response.encode("utf-8"))
except:
pass
try:
data = rawdata.decode("utf-8").rstrip().lstrip()
eventQueue.put({"Type":fenrirEventType.RemoteIncomming,
"Data": data
})
except:
pass
try:
client_sock.close()
except: