#!/usr/bin/env python3 # -*- coding: utf-8 -*- # 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: 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 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) 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 Exception as e: # 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 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*$', # Contains @ and ends with prompt char (user@host style) r'^\[.*\]\s*[\\\$#>]\s*$', # [anything]$ style prompts # 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 r'.*\?\s*\[[Yy]es/[Nn]o\]\s*$', # ? [Yes/No] style 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 r'.*ok\s*\[[YyNn]/[YyNn]\].*$', # "Is this ok [y/N]:" style # pacman ":: Proceed? [Y/n]" style r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$', # Authentication prompts # [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: # Please enter passphrase: r'^[Pp]lease\s+enter\s+[Pp]assphrase.*:\s*$', # General confirmation and continuation prompts # 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._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 # 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) def setCallback(self, callback): pass