Merged for new release.
This commit is contained in:
@ -72,6 +72,12 @@ def create_argument_parser():
|
||||
action='store_true',
|
||||
help='Force Fenrir to respond on all screens, ignoring ignoreScreen setting'
|
||||
)
|
||||
argumentParser.add_argument(
|
||||
'-i', '-I', '--ignore-screen',
|
||||
metavar='SCREEN',
|
||||
action='append',
|
||||
help='Ignore specific screen(s). Can be used multiple times. Same as ignoreScreen setting.'
|
||||
)
|
||||
return argumentParser
|
||||
|
||||
def validate_arguments(cliArgs):
|
||||
|
@ -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
|
121
src/fenrirscreenreader/commands/commands/progress_bar_monitor.py
Normal file
121
src/fenrirscreenreader/commands/commands/progress_bar_monitor.py
Normal file
@ -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,156 @@
|
||||
#!/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: 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 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
|
@ -320,6 +320,14 @@ class settingsManager():
|
||||
|
||||
if cliArgs.force_all_screens:
|
||||
environment['runtime']['force_all_screens'] = True
|
||||
|
||||
if cliArgs.ignore_screen:
|
||||
currentIgnoreScreen = self.getSetting('screen', 'ignoreScreen')
|
||||
if currentIgnoreScreen:
|
||||
ignoreScreens = currentIgnoreScreen.split(',') + cliArgs.ignore_screen
|
||||
else:
|
||||
ignoreScreens = cliArgs.ignore_screen
|
||||
self.setSetting('screen', 'ignoreScreen', ','.join(ignoreScreens))
|
||||
|
||||
if not os.path.exists(self.getSetting('sound','theme') + '/soundicons.conf'):
|
||||
if os.path.exists(soundRoot + self.getSetting('sound','theme')):
|
||||
|
@ -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 += ' '
|
||||
|
@ -4,5 +4,5 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
version = "2025.06.06"
|
||||
version = "2025.06.07"
|
||||
codeName = "master"
|
||||
|
Reference in New Issue
Block a user