To make Fenrir easier to approach for new developer, start code migration to be pep8 compliant.

This commit is contained in:
Storm Dragon
2025-07-01 22:23:50 -04:00
parent 4bcf82178e
commit 7408951152
345 changed files with 8688 additions and 3852 deletions

View File

@@ -4,19 +4,30 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core.i18n import _
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
pass
def getDescription(self):
return _('enables or disables tracking of highlighted')
def run(self):
if not self.env['runtime']['settingsManager'].getSettingAsBool('focus', 'highlight'):
if not self.env['runtime']['settingsManager'].getSettingAsBool(
'focus', 'highlight'):
return
attributeDelta = self.env['runtime']['attributeManager'].getAttributeDelta()
self.env['runtime']['outputManager'].presentText(attributeDelta, soundIcon='', interrupt=True, flush=False)
attributeDelta = self.env['runtime']['attributeManager'].getAttributeDelta(
)
self.env['runtime']['outputManager'].presentText(
attributeDelta, soundIcon='', interrupt=True, flush=False)
def setCallback(self, callback):
pass

View File

@@ -5,18 +5,25 @@
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core.i18n import _
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return ''
def run(self):
if self.env['screen']['newAttribDelta'] != '':
return
return
if self.env['runtime']['screenManager'].isScreenChange():
return
if self.env['runtime']['cursorManager'].isCursorVerticalMove():
@@ -25,25 +32,34 @@ class command():
return
# hack for pdmenu and maybe other dialog apps that place the cursor at last cell/row
# this is not to be identified as history
if (self.env['screen']['newCursor']['x'] == self.env['runtime']['screenManager'].getColumns() - 1) and (self.env['screen']['newCursor']['y'] == self.env['runtime']['screenManager'].getRows() - 1):
if (self.env['screen']['newCursor']['x'] == self.env['runtime']['screenManager'].getColumns(
) - 1) and (self.env['screen']['newCursor']['y'] == self.env['runtime']['screenManager'].getRows() - 1):
return
if self.env['runtime']['inputManager'].getShortcutType() in ['KEY']:
if not (self.env['runtime']['inputManager'].getLastDeepestInput() in [['KEY_UP'],['KEY_DOWN']]):
return
if not (
self.env['runtime']['inputManager'].getLastDeepestInput() in [
['KEY_UP'],
['KEY_DOWN']]):
return
elif self.env['runtime']['inputManager'].getShortcutType() in ['BYTE']:
if not (self.env['runtime']['byteManager'].getLastByteKey() in [b'^[[A',b'^[[B']):
return
if not (
self.env['runtime']['byteManager'].getLastByteKey() in [
b'^[[A',
b'^[[B']):
return
# Get the current cursor's line from both old and new content
prevLine = self.env['screen']['oldContentText'].split('\n')[self.env['screen']['newCursor']['y']]
currLine = self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']]
prevLine = self.env['screen']['oldContentText'].split(
'\n')[self.env['screen']['newCursor']['y']]
currLine = self.env['screen']['newContentText'].split(
'\n')[self.env['screen']['newCursor']['y']]
is_blank = currLine.strip() == ''
if prevLine == currLine:
if self.env['screen']['newDelta'] != '':
return
announce = currLine
if not is_blank:
currPrompt = currLine.find('$')
@@ -55,18 +71,22 @@ class command():
announce = currLine
if currPrompt > 0:
remove_digits = str.maketrans('0123456789', ' ')
if prevLine[:currPrompt].translate(remove_digits) == currLine[:currPrompt].translate(remove_digits):
announce = currLine[currPrompt+1:]
if prevLine[:currPrompt].translate(
remove_digits) == currLine[:currPrompt].translate(remove_digits):
announce = currLine[currPrompt + 1:]
else:
announce = currLine
if is_blank:
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True, flush=False)
self.env['runtime']['outputManager'].presentText(
_("blank"), soundIcon='EmptyLine', interrupt=True, flush=False)
else:
self.env['runtime']['outputManager'].presentText(announce, interrupt=True, flush=False)
self.env['runtime']['outputManager'].presentText(
announce, interrupt=True, flush=False)
self.env['commandsIgnore']['onScreenUpdate']['CHAR_DELETE_ECHO'] = True
self.env['commandsIgnore']['onScreenUpdate']['CHAR_ECHO'] = True
self.env['commandsIgnore']['onScreenUpdate']['INCOMING_IGNORE'] = True
def setCallback(self, callback):
pass

View File

@@ -4,110 +4,137 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
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 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']:
# Check if current line is a prompt - if so, reset progress state
# Check if current line is a prompt - if so, reset progress
# state
if self.isCurrentLinePrompt():
self.resetProgressState()
# Only check new incoming text (newDelta), but filter out screen changes
# Only check new incoming text (newDelta), but filter out
# screen changes
elif 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 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'])
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
# 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
# Check if current line looks like a prompt - progress unlikely during prompts
# Check if current line looks like a prompt - progress unlikely during
# prompts
if self.isCurrentLinePrompt():
return False
return True
def resetProgressState(self):
"""Reset progress state when a prompt is detected, allowing new progress operations to start fresh"""
self.env['runtime']['debug'].writeDebugOut("Resetting progress state due to prompt detection", debug.debugLevel.INFO)
self.env['runtime']['debug'].writeDebugOut(
"Resetting progress state due to prompt detection",
debug.debugLevel.INFO)
self.env['commandBuffer']['lastProgressValue'] = -1
self.env['commandBuffer']['lastProgressTime'] = 0
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
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.)
# Filter out common non-progress percentages (weather, system stats, etc.)
# Filter out common non-progress percentages (weather, system stats,
# 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:
# Filter out weather/system stats that contain percentages
if not re.search(r'\b(?:humidity|cpu|memory|disk|usage|temp|weather|forecast)\b', text, re.IGNORECASE):
self.env['runtime']['debug'].writeDebugOut("Found percentage: " + str(percentage), debug.debugLevel.INFO)
if not re.search(
r'\b(?:humidity|cpu|memory|disk|usage|temp|weather|forecast)\b',
text,
re.IGNORECASE):
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.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)
# 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)
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)
# 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:
@@ -120,7 +147,7 @@ class command():
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
# Improved pattern to avoid matching IRC channels like [#channel]
barMatch = re.search(r'\[([#=\-\*]+)([\s\.]*)\]', text)
@@ -128,131 +155,157 @@ class command():
filled = len(barMatch.group(1))
unfilled = len(barMatch.group(2))
total = filled + unfilled
# Require at least 2 progress chars total and unfilled portion must be spaces/dots
if total >= 2 and (not barMatch.group(2) or re.match(r'^[\s\.]*$', barMatch.group(2))):
# Require at least 2 progress chars total and unfilled portion must
# be spaces/dots
if total >= 2 and (
not barMatch.group(2) or re.match(
r'^[\s\.]*$',
barMatch.group(2))):
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)
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:
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
# 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
# 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
# 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)
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)
self.env['runtime']['debug'].writeDebugOut(
"Sox tone error: " + str(e), debug.debugLevel.ERROR)
def isCurrentLinePrompt(self):
"""Check if the current line looks like a standalone prompt (not command with progress)"""
import re
try:
# Get the current screen content
if not self.env['screen']['newContentText']:
return False
lines = self.env['screen']['newContentText'].split('\n')
if not lines:
return False
# Check the last line (most common) and current cursor line for prompt patterns
# Check the last line (most common) and current cursor line for
# prompt patterns
linesToCheck = []
# Add last line (most common for prompts)
if lines:
linesToCheck.append(lines[-1])
# Add current cursor line if different from last line
if (self.env['screen']['newCursor']['y'] < len(lines) and
self.env['screen']['newCursor']['y'] != len(lines) - 1):
linesToCheck.append(lines[self.env['screen']['newCursor']['y']])
if (self.env['screen']['newCursor']['y'] < len(lines) and
self.env['screen']['newCursor']['y'] != len(lines) - 1):
linesToCheck.append(
lines[self.env['screen']['newCursor']['y']])
# Standalone prompt patterns (no commands mixed in)
standalonePromptPatterns = [
r'^\s*\$\s*$', # Just $ (with whitespace)
r'^\s*#\s*$', # Just # (with whitespace)
r'^\s*#\s*$', # Just # (with whitespace)
r'^\s*>\s*$', # Just > (with whitespace)
r'^\[.*\]\s*[\\\$#>]\s*$', # [path]$ without commands
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$', # bash-5.1$ without commands
# Interactive prompt patterns (these ARE standalone)
r'.*\?\s*\[[YyNn]/[YyNn]\]\s*$', # ? [Y/n] or ? [y/N] style
r'.*\?\s*\[[Yy]es/[Nn]o\]\s*$', # ? [Yes/No] style
r'.*continue\?\s*\[[YyNn]/[YyNn]\].*$', # "continue? [Y/n]" style
# "continue? [Y/n]" style
r'.*continue\?\s*\[[YyNn]/[YyNn]\].*$',
r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$', # pacman style prompts
# Authentication prompts (these ARE standalone)
r'^\[[Ss]udo\]\s*[Pp]assword\s*for\s+.*:\s*$', # [sudo] password
r'^[Pp]assword\s*:\s*$', # Password:
r'.*[Pp]assword\s*:\s*$', # general password prompts
# Continuation prompts (these ARE standalone)
r'^[Pp]ress\s+any\s+key\s+to\s+continue.*$', # Press any key
r'^[Aa]re\s+you\s+sure\?\s*.*$', # Are you sure?
]
for line in linesToCheck:
line = line.strip()
if not line:
continue
# Check if this line contains both a prompt AND other content (like commands)
# If so, don't treat it as a standalone prompt
hasPromptMarker = bool(re.search(r'.*@.*[\\\$#>]', line) or re.search(r'^\[.*\]\s*[\\\$#>]', line))
hasPromptMarker = bool(
re.search(
r'.*@.*[\\\$#>]',
line) or re.search(
r'^\[.*\]\s*[\\\$#>]',
line))
if hasPromptMarker:
# If line has prompt marker but also has significant content after it,
# If line has prompt marker but also has significant content after it,
# it's a command line, not a standalone prompt
promptEnd = max(
line.rfind('$'),
line.rfind('#'),
line.rfind('$'),
line.rfind('#'),
line.rfind('>'),
line.rfind('\\')
)
if promptEnd >= 0 and promptEnd < len(line) - 5: # More than just whitespace after prompt
if promptEnd >= 0 and promptEnd < len(
line) - 5: # More than just whitespace after prompt
continue # This is a command line, not a standalone prompt
for pattern in standalonePromptPatterns:
try:
if re.search(pattern, line):
return True
except re.error:
continue
return False
except Exception:
# If anything fails, assume it's not a prompt to be safe
return False
def setCallback(self, callback):
pass
pass

View File

@@ -4,21 +4,24 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
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 getDescription(self):
return 'Detects shell prompts for silence until prompt feature'
def run(self):
# Only run if silence until prompt is active
try:
@@ -26,55 +29,69 @@ class command():
# 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']]
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
self.env['runtime']['debug'].writeDebugOut('prompt_detector run: Error in prompt detection: ' + str(e), debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(
'prompt_detector run: Error in prompt detection: ' + str(e),
debug.debugLevel.ERROR)
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)
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')
exactMatches = self.env['runtime']['settingsManager'].getSetting(
'prompt', 'exactMatches')
if exactMatches:
exactList = [match.strip() for match in exactMatches.split(',') if match.strip()]
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.env['runtime']['debug'].writeDebugOut(
"Found exact prompt match: " + exactMatch, debug.debugLevel.INFO)
self._restoreSpeech()
return True
except Exception as e:
# Prompt section doesn't exist in settings, skip custom exact matches
# 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')
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()]
customList = [
pattern.strip() for pattern in customPatterns.split(',') if pattern.strip()]
promptPatterns.extend(customList)
except Exception as e:
# 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*>\s*$', # Just > (with whitespace)
r'.*@.*[\\\$#>]\s*$', # Contains @ and ends with prompt char (user@host style)
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$
# Simple shell names like bash-5.1$
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$',
# Interactive prompt patterns
# Package manager confirmation prompts
r'.*\?\s*\[[YyNn]/[YyNn]\]\s*$', # ? [Y/n] or ? [y/N] style
@@ -83,47 +100,57 @@ class command():
r'.*\?\s*\([Yy]es/[Nn]o\)\s*$', # ? (Yes/No) style
r'.*continue\?\s*\[[YyNn]/[YyNn]\].*$', # "continue? [Y/n]" style
r'.*ok\s*\[[YyNn]/[YyNn]\].*$', # "Is this ok [y/N]:" style
r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$', # pacman ":: Proceed? [Y/n]" style
# pacman ":: Proceed? [Y/n]" style
r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$',
# Authentication prompts
r'^\[[Ss]udo\]\s*[Pp]assword\s*for\s+.*:\s*$', # [sudo] password for user:
# [sudo] password for user:
r'^\[[Ss]udo\]\s*[Pp]assword\s*for\s+.*:\s*$',
r'^[Pp]assword\s*:\s*$', # Password:
r'.*[Pp]assword\s*:\s*$', # general password prompts
r".*'s\s*[Pp]assword\s*:\s*$", # user's password:
r'^[Ee]nter\s+[Pp]assphrase.*:\s*$', # Enter passphrase:
r'^[Pp]lease\s+enter\s+[Pp]assphrase.*:\s*$', # Please enter passphrase:
# Please enter passphrase:
r'^[Pp]lease\s+enter\s+[Pp]assphrase.*:\s*$',
# General confirmation and continuation prompts
r'^[Pp]ress\s+any\s+key\s+to\s+continue.*$', # Press any key to continue
# Press any key to continue
r'^[Pp]ress\s+any\s+key\s+to\s+continue.*$',
r'^[Aa]re\s+you\s+sure\?\s*.*$', # Are you sure?
r'^[Pp]lease\s+confirm.*$', # Please confirm
r'.*confirm.*\([YyNn]/[YyNn]\).*$', # confirm (y/n)
r'.*\([Yy]/[Nn]\)\s*$', # ends with (Y/n) or (y/N)
])
for pattern in promptPatterns:
try:
if re.search(pattern, text.strip()):
self.env['runtime']['debug'].writeDebugOut("Found prompt pattern: " + pattern, debug.debugLevel.INFO)
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)
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
# Also disable the keypress-based speech restoration since we're enabling it now
# Also disable the keypress-based speech restoration since we're
# enabling it now
if 'enableSpeechOnKeypress' in self.env['commandBuffer']:
self.env['commandBuffer']['enableSpeechOnKeypress'] = False
# Re-enable speech
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', 'True')
self.env['runtime']['outputManager'].presentText(_("Speech restored"), soundIcon='SpeechOn', interrupt=True)
self.env['runtime']['settingsManager'].setSetting(
'speech', 'enabled', 'True')
self.env['runtime']['outputManager'].presentText(
_("Speech restored"), soundIcon='SpeechOn', interrupt=True)
def setCallback(self, callback):
pass
pass

View File

@@ -5,34 +5,47 @@
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core.i18n import _
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return 'No Description found'
def run(self):
if not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'autoReadIncoming'):
if not self.env['runtime']['settingsManager'].getSettingAsBool(
'speech', 'autoReadIncoming'):
return
# is there something to read?
if not self.env['runtime']['screenManager'].isDelta(ignoreSpace=True):
return
# this must be a keyecho or something
#if len(self.env['screen']['newDelta'].strip(' \n\t')) <= 1:
xMove = abs(self.env['screen']['newCursor']['x'] - self.env['screen']['oldCursor']['x'])
yMove = abs(self.env['screen']['newCursor']['y'] - self.env['screen']['oldCursor']['y'])
# if len(self.env['screen']['newDelta'].strip(' \n\t')) <= 1:
xMove = abs(
self.env['screen']['newCursor']['x'] -
self.env['screen']['oldCursor']['x'])
yMove = abs(
self.env['screen']['newCursor']['y'] -
self.env['screen']['oldCursor']['y'])
if (xMove >= 1) and xMove == len(self.env['screen']['newDelta']):
# if len(self.env['screen']['newDelta'].strip(' \n\t0123456789')) <= 2:
if not '\n' in self.env['screen']['newDelta']:
# if len(self.env['screen']['newDelta'].strip(' \n\t0123456789'))
# <= 2:
if '\n' not in self.env['screen']['newDelta']:
return
#print(xMove, yMove, len(self.env['screen']['newDelta']), len(self.env['screen']['newNegativeDelta']))
self.env['runtime']['outputManager'].presentText(self.env['screen']['newDelta'], interrupt=False, flush=False)
# print(xMove, yMove, len(self.env['screen']['newDelta']), len(self.env['screen']['newNegativeDelta']))
self.env['runtime']['outputManager'].presentText(
self.env['screen']['newDelta'], interrupt=False, flush=False)
def setCallback(self, callback):
pass

View File

@@ -1,4 +1,7 @@
#!/usr/bin/env python3
from fenrirscreenreader.core.i18n import _
import time
# -*- coding: utf-8 -*-
@@ -9,26 +12,41 @@ import time
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return 'No Description found'
def run(self):
if not self.env['runtime']['settingsManager'].getSettingAsBool('promote', 'enabled'):
if not self.env['runtime']['settingsManager'].getSettingAsBool(
'promote', 'enabled'):
return
if self.env['runtime']['settingsManager'].getSetting('promote', 'list').strip(" \t\n") == '':
if self.env['runtime']['settingsManager'].getSetting(
'promote', 'list').strip(" \t\n") == '':
return
if int(time.time() - self.env['input']['lastInputTime']) < self.env['runtime']['settingsManager'].getSettingAsInt('promote', 'inactiveTimeoutSec'):
if int(
time.time() -
self.env['input']['lastInputTime']) < self.env['runtime']['settingsManager'].getSettingAsInt(
'promote',
'inactiveTimeoutSec'):
return
if len(self.env['runtime']['settingsManager'].getSetting('promote', 'list')) == 0:
if len(
self.env['runtime']['settingsManager'].getSetting(
'promote',
'list')) == 0:
return
for promote in self.env['runtime']['settingsManager'].getSetting('promote', 'list').split(','):
if promote in self.env['screen']['newDelta']:
self.env['runtime']['outputManager'].playSoundIcon('PromotedText')
for promote in self.env['runtime']['settingsManager'].getSetting(
'promote', 'list').split(','):
if promote in self.env['screen']['newDelta']:
self.env['runtime']['outputManager'].playSoundIcon(
'PromotedText')
self.env['input']['lastInputTime'] = time.time()
return
def setCallback(self, callback):
pass

View File

@@ -5,21 +5,32 @@
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core.i18n import _
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return 'No Description found'
def run(self):
if not self.env['runtime']['settingsManager'].getSettingAsBool('barrier','enabled'):
if not self.env['runtime']['settingsManager'].getSettingAsBool(
'barrier', 'enabled'):
return
if not self.env['runtime']['screenManager'].isDelta(ignoreSpace=True):
return
self.env['runtime']['barrierManager'].handleLineBarrier(self.env['screen']['newContentText'].split('\n'), self.env['screen']['newCursor']['x'],self.env['screen']['newCursor']['y'])
self.env['runtime']['barrierManager'].handleLineBarrier(
self.env['screen']['newContentText'].split('\n'),
self.env['screen']['newCursor']['x'],
self.env['screen']['newCursor']['y'])
def setCallback(self, callback):
pass