Latest changes and bug fixes.

This commit is contained in:
Storm Dragon
2025-06-27 21:18:27 -04:00
51 changed files with 445 additions and 229 deletions
+1
View File
@@ -1,4 +1,5 @@
daemonize daemonize
dbus-python
evdev evdev
pexpect pexpect
pyenchant pyenchant
+1 -1
View File
@@ -37,7 +37,7 @@ for directory in directories:
if not forceSettingsFlag: if not forceSettingsFlag:
try: try:
files = [f for f in files if not f.endswith('settings.conf')] files = [f for f in files if not f.endswith('settings.conf')]
except: except Exception as e:
pass pass
elif 'config/scripts' in directory: elif 'config/scripts' in directory:
destDir = '/usr/share/fenrirscreenreader/scripts' destDir = '/usr/share/fenrirscreenreader/scripts'
@@ -10,7 +10,7 @@ initialized = False
try: try:
import enchant import enchant
initialized = True initialized = True
except: except Exception as e:
pass pass
class command(): class command():
@@ -30,7 +30,7 @@ class command():
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory') clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
clipboardFile.write(clipboard) clipboardFile.write(clipboard)
clipboardFile.close() clipboardFile.close()
os.chmod(clipboardFilePath, 0o666) os.chmod(clipboardFilePath, 0o644)
self.env['runtime']['outputManager'].presentText(_('clipboard exported to file'), interrupt=True) self.env['runtime']['outputManager'].presentText(_('clipboard exported to file'), interrupt=True)
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('export_clipboard_to_file:run: Filepath:'+ clipboardFile +' trace:' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('export_clipboard_to_file:run: Filepath:'+ clipboardFile +' trace:' + str(e),debug.debugLevel.ERROR)
@@ -19,7 +19,7 @@ class command():
# Check if progress monitoring should be enabled by default from settings # Check if progress monitoring should be enabled by default from settings
try: try:
defaultEnabled = self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'progressMonitoring') defaultEnabled = self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'progressMonitoring')
except: except Exception as e:
# If setting doesn't exist, default to False # If setting doesn't exist, default to False
defaultEnabled = False defaultEnabled = False
self.env['commandBuffer']['progressMonitoring'] = defaultEnabled self.env['commandBuffer']['progressMonitoring'] = defaultEnabled
@@ -201,7 +201,7 @@ class command():
return False return False
except Exception: except Exception as e:
# If anything fails, assume it's not a prompt to be safe # If anything fails, assume it's not a prompt to be safe
return False return False
@@ -10,7 +10,7 @@ initialized = False
try: try:
import enchant import enchant
initialized = True initialized = True
except: except Exception as e:
pass pass
class command(): class command():
@@ -35,7 +35,7 @@ class command():
if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language: if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language:
try: try:
self.updateSpellLanguage() self.updateSpellLanguage()
except: except Exception as e:
return return
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor() cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
@@ -56,7 +56,7 @@ class command():
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.disableSilence() self.disableSilence()
return True return True
except: 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 pass
@@ -68,7 +68,7 @@ class command():
if customPatterns: 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) promptPatterns.extend(customList)
except: except Exception as e:
# Prompt section doesn't exist in settings, skip custom patterns # Prompt section doesn't exist in settings, skip custom patterns
pass pass
@@ -9,7 +9,7 @@ initialized = False
try: try:
import enchant import enchant
initialized = True initialized = True
except: except Exception as e:
pass pass
class command(): class command():
@@ -36,7 +36,7 @@ class command():
if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language: if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language:
try: try:
self.updateSpellLanguage() self.updateSpellLanguage()
except: except Exception as e:
return return
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor() cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
@@ -11,7 +11,7 @@ initialized = False
try: try:
import enchant import enchant
initialized = True initialized = True
except: except Exception as e:
pass pass
class command(): class command():
@@ -40,7 +40,7 @@ class command():
if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language: if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language:
try: try:
self.updateSpellLanguage() self.updateSpellLanguage()
except: except Exception as e:
return return
# just when horizontal cursor move worddetection is needed # just when horizontal cursor move worddetection is needed
@@ -111,17 +111,17 @@ class command():
try: try:
if os.path.exists("/bin/"+currWord): if os.path.exists("/bin/"+currWord):
return return
except: except Exception as e:
pass pass
try: try:
if os.path.exists("/usr/bin/"+currWord): if os.path.exists("/usr/bin/"+currWord):
return return
except: except Exception as e:
pass pass
try: try:
if os.path.exists("/sbin/"+currWord): if os.path.exists("/sbin/"+currWord):
return return
except: except Exception as e:
pass pass
if not self.spellChecker.check(currWord): if not self.spellChecker.check(currWord):
@@ -23,7 +23,7 @@ class command():
for deviceEntry in deviceList: for deviceEntry in deviceList:
# dont play sounds for virtual devices # dont play sounds for virtual devices
playSound = playSound or not deviceEntry['virtual'] playSound = playSound or not deviceEntry['virtual']
except: except Exception as e:
playSound = True playSound = True
if playSound: if playSound:
if time.time() - self.lastTime > 5: if time.time() - self.lastTime > 5:
@@ -31,7 +31,7 @@ class command():
self.checkForPrompt(currentLine) self.checkForPrompt(currentLine)
except Exception as e: except Exception as e:
# Silently ignore errors to avoid disrupting normal operation # Silently ignore errors to avoid disrupting normal operation
pass self.env['runtime']['debug'].writeDebugOut('prompt_detector run: Error in prompt detection: ' + str(e), debug.debugLevel.ERROR)
def checkForPrompt(self, text): def checkForPrompt(self, text):
"""Check if the current line contains a shell prompt pattern""" """Check if the current line contains a shell prompt pattern"""
@@ -50,7 +50,7 @@ class command():
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() self._restoreSpeech()
return True return True
except: 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 pass
@@ -62,7 +62,7 @@ class command():
if customPatterns: 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) promptPatterns.extend(customList)
except: except Exception as e:
# Prompt section doesn't exist in settings, skip custom patterns # Prompt section doesn't exist in settings, skip custom patterns
pass pass
@@ -4,7 +4,12 @@
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.commands.vmenu_navigation.vmenu_search_base import VMenuSearchCommand import importlib.util
import os
_spec = importlib.util.spec_from_file_location("vmenu_search_base", os.path.join(os.path.dirname(__file__), "vmenu_search_base.py"))
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
VMenuSearchCommand = _module.VMenuSearchCommand
class command(VMenuSearchCommand): class command(VMenuSearchCommand):
def __init__(self): def __init__(self):
@@ -0,0 +1,134 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import os
import shutil
import tempfile
import configparser
from datetime import datetime
class ConfigCommand():
"""Base class for configuration management commands"""
def __init__(self):
self.env = None
self.settingsFile = None
self.config = None
def initialize(self, environment):
self.env = environment
self.settingsFile = self.env['runtime']['settingsManager'].settingsFile
self.config = self.env['runtime']['settingsManager'].settings
def shutdown(self):
pass
def setCallback(self, callback):
pass
def presentText(self, text, interrupt=True, flush=True):
"""Present text to user with proper speech handling"""
self.env['runtime']['outputManager'].presentText(text, interrupt=interrupt, flush=flush)
def playSound(self, soundName):
"""Play system sound"""
soundIcon = ''
if soundName == 'Accept':
soundIcon = 'Accept'
elif soundName == 'Error':
soundIcon = 'ErrorSound'
elif soundName == 'Cancel':
soundIcon = 'Cancel'
if soundIcon:
self.env['runtime']['outputManager'].presentText('', soundIcon=soundIcon, interrupt=False)
def backupConfig(self, announce=True):
"""Create backup of current configuration file"""
try:
if not os.path.exists(self.settingsFile):
message = "Configuration file not found"
if announce:
self.presentText(message)
return False, message
# Create backup with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{self.settingsFile}.backup_{timestamp}"
shutil.copy2(self.settingsFile, backup_path)
message = f"Configuration backed up to {backup_path}"
if announce:
self.presentText("Configuration backup created successfully")
return True, message
except Exception as e:
message = f"Failed to backup configuration: {str(e)}"
if announce:
self.presentText(message)
return False, message
def reloadConfig(self):
"""Reload configuration from file"""
try:
# Force settings manager to reload from file
self.env['runtime']['settingsManager'].loadSettings()
self.config = self.env['runtime']['settingsManager'].settings
return True
except Exception as e:
self.presentText(f"Failed to reload configuration: {str(e)}")
return False
def findDefaultConfig(self):
"""Find default configuration file path"""
# Look for default config in multiple locations
default_paths = [
'/etc/fenrir/settings/settings.conf.default',
'/usr/share/fenrir/settings/settings.conf',
os.path.join(os.path.dirname(self.settingsFile), 'settings.conf.default'),
os.path.join(os.path.dirname(os.path.dirname(self.settingsFile)), 'settings', 'settings.conf.default')
]
for path in default_paths:
if os.path.exists(path):
return path
return None
def createBasicDefaults(self):
"""Create basic default configuration manually"""
try:
# Create a new config with basic defaults
self.config = configparser.ConfigParser()
# Add basic sections and settings
self.config.add_section('speech')
self.config.set('speech', 'driver', 'speechdDriver')
self.config.set('speech', 'rate', '0.75')
self.config.set('speech', 'pitch', '0.5')
self.config.set('speech', 'volume', '1.0')
self.config.add_section('sound')
self.config.set('sound', 'driver', 'genericDriver')
self.config.set('sound', 'volume', '0.7')
self.config.add_section('keyboard')
self.config.set('keyboard', 'driver', 'evdevDriver')
self.config.set('keyboard', 'keyboardLayout', 'desktop')
self.config.add_section('screen')
self.config.set('screen', 'driver', 'vcsaDriver')
self.config.add_section('general')
self.config.set('general', 'debugMode', 'Off')
return True
except Exception as e:
self.presentText(f"Failed to create basic defaults: {str(e)}")
return False
@@ -27,8 +27,8 @@ class command():
speechDriver = self.env['runtime']['speechDriver'] speechDriver = self.env['runtime']['speechDriver']
speechDriver.shutdown() speechDriver.shutdown()
speechDriver.initialize(self.env) speechDriver.initialize(self.env)
except: except Exception as e:
pass print(f'revert_to_saved speechDriver: Error reinitializing speech driver: {str(e)}')
# Reinitialize sound system with restored settings # Reinitialize sound system with restored settings
if 'soundDriver' in self.env['runtime']: if 'soundDriver' in self.env['runtime']:
@@ -36,8 +36,8 @@ class command():
soundDriver = self.env['runtime']['soundDriver'] soundDriver = self.env['runtime']['soundDriver']
soundDriver.shutdown() soundDriver.shutdown()
soundDriver.initialize(self.env) soundDriver.initialize(self.env)
except: except Exception as e:
pass print(f'revert_to_saved soundDriver: Error reinitializing sound driver: {str(e)}')
self.env['runtime']['outputManager'].presentText("Successfully reverted to saved settings", interrupt=False, flush=False) self.env['runtime']['outputManager'].presentText("Successfully reverted to saved settings", interrupt=False, flush=False)
self.env['runtime']['outputManager'].presentText("All temporary changes have been discarded", interrupt=False, flush=False) self.env['runtime']['outputManager'].presentText("All temporary changes have been discarded", interrupt=False, flush=False)
@@ -38,7 +38,7 @@ class command():
runtimeValue = runtimeSettings[section][option] runtimeValue = runtimeSettings[section][option]
try: try:
fileValue = fileConfig[section][option] fileValue = fileConfig[section][option]
except: except Exception as e:
fileValue = "" fileValue = ""
if runtimeValue != fileValue: if runtimeValue != fileValue:
@@ -17,7 +17,7 @@ class command():
try: try:
# Get current pitch # Get current pitch
currentPitch = float(self.env['runtime']['settingsManager'].getSetting('speech', 'pitch')) currentPitch = float(self.env['runtime']['settingsManager'].getSetting('speech', 'pitch'))
except: except Exception as e:
currentPitch = 0.5 currentPitch = 0.5
# Present current pitch # Present current pitch
@@ -17,7 +17,7 @@ class command():
try: try:
# Get current rate # Get current rate
currentRate = float(self.env['runtime']['settingsManager'].getSetting('speech', 'rate')) currentRate = float(self.env['runtime']['settingsManager'].getSetting('speech', 'rate'))
except: except Exception as e:
currentRate = 0.5 currentRate = 0.5
# Present current rate # Present current rate
@@ -18,7 +18,7 @@ class command():
# Get current rate from runtime settings (may be temporary) # Get current rate from runtime settings (may be temporary)
settingsManager = self.env['runtime']['settingsManager'] settingsManager = self.env['runtime']['settingsManager']
currentRate = float(settingsManager.getSetting('speech', 'rate')) currentRate = float(settingsManager.getSetting('speech', 'rate'))
except: except Exception as e:
currentRate = 0.5 currentRate = 0.5
# Present current rate # Present current rate
@@ -38,8 +38,8 @@ class command():
if 'speechDriver' in self.env['runtime']: if 'speechDriver' in self.env['runtime']:
try: try:
self.env['runtime']['speechDriver'].setRate(newRate) self.env['runtime']['speechDriver'].setRate(newRate)
except: except Exception as e:
pass print(f'adjust_speech_rate setRate: Error setting speech rate: {str(e)}')
newPercent = int(newRate * 100) newPercent = int(newRate * 100)
self.env['runtime']['outputManager'].presentText(f"Speech rate temporarily set to {newPercent} percent", interrupt=False, flush=False) self.env['runtime']['outputManager'].presentText(f"Speech rate temporarily set to {newPercent} percent", interrupt=False, flush=False)
@@ -17,7 +17,7 @@ class command():
try: try:
# Get current volume # Get current volume
currentVolume = float(self.env['runtime']['settingsManager'].getSetting('speech', 'volume')) currentVolume = float(self.env['runtime']['settingsManager'].getSetting('speech', 'volume'))
except: except Exception as e:
currentVolume = 1.0 currentVolume = 1.0
# Present current volume # Present current volume
@@ -38,19 +38,19 @@ class command():
try: try:
ratePercent = int(float(rate) * 100) ratePercent = int(float(rate) * 100)
self.env['runtime']['outputManager'].presentText(f"Rate: {ratePercent} percent", interrupt=True) self.env['runtime']['outputManager'].presentText(f"Rate: {ratePercent} percent", interrupt=True)
except: except Exception as e:
self.env['runtime']['outputManager'].presentText(f"Rate: {rate}", interrupt=True) self.env['runtime']['outputManager'].presentText(f"Rate: {rate}", interrupt=True)
try: try:
pitchPercent = int(float(pitch) * 100) pitchPercent = int(float(pitch) * 100)
self.env['runtime']['outputManager'].presentText(f"Pitch: {pitchPercent} percent", interrupt=True) self.env['runtime']['outputManager'].presentText(f"Pitch: {pitchPercent} percent", interrupt=True)
except: except Exception as e:
self.env['runtime']['outputManager'].presentText(f"Pitch: {pitch}", interrupt=True) self.env['runtime']['outputManager'].presentText(f"Pitch: {pitch}", interrupt=True)
try: try:
volumePercent = int(float(volume) * 100) volumePercent = int(float(volume) * 100)
self.env['runtime']['outputManager'].presentText(f"Volume: {volumePercent} percent", interrupt=True) self.env['runtime']['outputManager'].presentText(f"Volume: {volumePercent} percent", interrupt=True)
except: except Exception as e:
self.env['runtime']['outputManager'].presentText(f"Volume: {volume}", interrupt=True) self.env['runtime']['outputManager'].presentText(f"Volume: {volume}", interrupt=True)
self.env['runtime']['outputManager'].playSound('Accept') self.env['runtime']['outputManager'].playSound('Accept')
@@ -74,8 +74,8 @@ class attributeManager():
except KeyError: except KeyError:
try: try:
return self.defaultAttributes[0] return self.defaultAttributes[0]
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('attributeManager getAttributeByXY: Error accessing default attributes: ' + str(e), debug.debugLevel.ERROR)
return None return None
def appendDefaultAttributes(self, attribute): def appendDefaultAttributes(self, attribute):
if not attribute: if not attribute:
@@ -214,12 +214,12 @@ class attributeManager():
try: try:
try: try:
attributeFormatString = attributeFormatString.replace('fenrirFontSize', int(attribute[8])) attributeFormatString = attributeFormatString.replace('fenrirFontSize', int(attribute[8]))
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('attributeManager formatAttributeToString: Error formatting font size as int: ' + str(e), debug.debugLevel.ERROR)
try: try:
attributeFormatString = attributeFormatString.replace('fenrirFontSize', str(attribute[8])) attributeFormatString = attributeFormatString.replace('fenrirFontSize', str(attribute[8]))
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('attributeManager formatAttributeToString: Error formatting font size as string: ' + str(e), debug.debugLevel.ERROR)
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirFontSize', _('default')) attributeFormatString = attributeFormatString.replace('fenrirFontSize', _('default'))
+4 -3
View File
@@ -58,8 +58,8 @@ class byteManager():
return return
try: try:
self.env['runtime']['debug'].writeDebugOut("handleByteInput " + eventData.decode('utf8') ,debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("handleByteInput " + eventData.decode('utf8') ,debug.debugLevel.INFO)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('byteManager handleByteInput: Error decoding byte data: ' + str(e), debug.debugLevel.ERROR)
self.handleByteStream(eventData) self.handleByteStream(eventData)
def handleSingleByteSequence(self, eventData): def handleSingleByteSequence(self, eventData):
convertedEscapeSequence = self.unifyEscapeSeq(eventData) convertedEscapeSequence = self.unifyEscapeSeq(eventData)
@@ -140,7 +140,8 @@ class byteManager():
try: try:
repeat = int(chr(cleanShortcut[0])) repeat = int(chr(cleanShortcut[0]))
cleanShortcut = cleanShortcut[2:] cleanShortcut = cleanShortcut[2:]
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('byteManager loadByteShortcuts: Error parsing repeat count: ' + str(e), debug.debugLevel.ERROR)
repeat = 1 repeat = 1
cleanShortcut = cleanShortcut cleanShortcut = cleanShortcut
shortcut = b'' shortcut = b''
@@ -84,11 +84,12 @@ class commandManager():
fileName = fileName.split('/')[-1] fileName = fileName.split('/')[-1]
if fileName.startswith('__'): if fileName.startswith('__'):
continue continue
try: # Skip base classes that shouldn't be loaded as commands
if self.env['commands'][section][fileName.upper()] != None: if fileName.endswith('_base'):
continue continue
except: # Check if command already exists to prevent duplicate loading
pass if fileName.upper() in self.env['commands'][section] and self.env['commands'][section][fileName.upper()] is not None:
continue
if fileExtension.lower() == '.py': if fileExtension.lower() == '.py':
command_mod = module_utils.importModule(fileName, command) command_mod = module_utils.importModule(fileName, command)
self.env['commands'][section][fileName.upper()] = command_mod.command() self.env['commands'][section][fileName.upper()] = command_mod.command()
@@ -249,7 +250,7 @@ class commandManager():
def commandExists(self, command, section = 'commands'): def commandExists(self, command, section = 'commands'):
try: try:
return( command in self.env['commands'][section]) return( command in self.env['commands'][section])
except: except Exception as e:
return False return False
def getShortcutForCommand(self, command, formatKeys = False): def getShortcutForCommand(self, command, formatKeys = False):
shortcut = [] shortcut = []
@@ -282,6 +283,6 @@ class commandManager():
formattedKey = formattedKey.replace('key_kp', ' keypad ') formattedKey = formattedKey.replace('key_kp', ' keypad ')
formattedKey = formattedKey.replace('key_', ' ') formattedKey = formattedKey.replace('key_', ' ')
shortcut.append(formattedKey) shortcut.append(formattedKey)
except: except Exception as e:
pass pass
return shortcut return shortcut
+4 -3
View File
@@ -87,8 +87,8 @@ class cursorManager():
currApp = self.env['runtime']['applicationManager'].getCurrentApplication() currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if self.env['commandBuffer']['windowArea'][currApp]['1'] != None: if self.env['commandBuffer']['windowArea'][currApp]['1'] != None:
return True return True
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('cursorManager isApplicationWindowSet: Error checking window area: ' + str(e), debug.debugLevel.ERROR)
return False return False
def setWindowForApplication(self, start = None, end = None): def setWindowForApplication(self, start = None, end = None):
x1 = 0 x1 = 0
@@ -129,6 +129,7 @@ class cursorManager():
currApp = self.env['runtime']['applicationManager'].getCurrentApplication() currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
try: try:
del self.env['commandBuffer']['windowArea'][currApp] del self.env['commandBuffer']['windowArea'][currApp]
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('cursorManager clearWindowForApplication: Error clearing window area: ' + str(e), debug.debugLevel.ERROR)
return False return False
return True return True
+3 -2
View File
@@ -19,8 +19,9 @@ class debugManager():
def __del__(self): def __del__(self):
try: try:
self.shutdown() self.shutdown()
except: except Exception as e:
pass # Cannot use debug manager to log its own shutdown errors
print(f'debugManager shutdown: Error during shutdown: {e}')
def openDebugFile(self, fileName = ''): def openDebugFile(self, fileName = ''):
self._fileOpened = False self._fileOpened = False
@@ -159,8 +159,8 @@ class DynamicApplyVoiceCommand:
speechDriver = self.env['runtime']['speechDriver'] speechDriver = self.env['runtime']['speechDriver']
speechDriver.shutdown() speechDriver.shutdown()
speechDriver.initialize(self.env) speechDriver.initialize(self.env)
except: except Exception as e:
pass # If this fails, at least we tried self.env['runtime']['debug'].writeDebugOut('dynamicVoiceMenu: Error reinitializing speech driver: ' + str(e), debug.debugLevel.ERROR)
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=False, flush=False) self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=False, flush=False)
+13 -5
View File
@@ -15,7 +15,7 @@ from ctypes import c_bool
class eventManager(): class eventManager():
def __init__(self): def __init__(self):
self.running = Value(c_bool, True) self.running = Value(c_bool, True)
self._eventQueue = Queue() # multiprocessing.Queue() self._eventQueue = Queue(maxsize=100) # Bounded queue to prevent memory exhaustion
self.cleanEventQueue() self.cleanEventQueue()
def initialize(self, environment): def initialize(self, environment):
self.env = environment self.env = environment
@@ -85,8 +85,16 @@ class eventManager():
return False return False
if event == fenrirEventType.Ignore: if event == fenrirEventType.Ignore:
return False return False
if self.getEventQueueSize() > 50: # Use bounded queue - if full, this will block briefly or drop older events
if not event in [fenrirEventType.ScreenUpdate, fenrirEventType.HeartBeat]: try:
self.cleanEventQueue() self._eventQueue.put({"Type":event,"Data":data}, timeout=0.1)
self._eventQueue.put({"Type":event,"Data":data}) except Exception as e:
# Queue full - drop oldest event and add new one for critical events
if event in [fenrirEventType.ScreenUpdate, fenrirEventType.KeyboardInput]:
try:
self._eventQueue.get_nowait() # Remove oldest
self._eventQueue.put({"Type":event,"Data":data}, timeout=0.1)
except:
pass # If still can't add, drop the event
# For non-critical events, just drop them if queue is full
return True return True
+6 -6
View File
@@ -138,8 +138,8 @@ class fenrirManager():
def handlePlugInputDevice(self, event): def handlePlugInputDevice(self, event):
try: try:
self.environment['runtime']['inputManager'].setLastDetectedDevices(event['Data']) self.environment['runtime']['inputManager'].setLastDetectedDevices(event['Data'])
except: except Exception as e:
pass self.environment['runtime']['debug'].writeDebugOut('handlePlugInputDevice: Error setting last detected devices: ' + str(e), debug.debugLevel.ERROR)
self.environment['runtime']['inputManager'].handlePlugInputDevice(event['Data']) self.environment['runtime']['inputManager'].handlePlugInputDevice(event['Data'])
self.environment['runtime']['commandManager'].executeDefaultTrigger('onPlugInputDevice', force=True) self.environment['runtime']['commandManager'].executeDefaultTrigger('onPlugInputDevice', force=True)
self.environment['runtime']['inputManager'].setLastDetectedDevices(None) self.environment['runtime']['inputManager'].setLastDetectedDevices(None)
@@ -200,16 +200,16 @@ class fenrirManager():
stringBuffer.value = bytes(name, 'UTF-8') stringBuffer.value = bytes(name, 'UTF-8')
libc.prctl(15, byref(stringBuffer), 0, 0, 0) libc.prctl(15, byref(stringBuffer), 0, 0, 0)
return True return True
except: except Exception as e:
pass self.environment['runtime']['debug'].writeDebugOut('setProcName: Error setting process name: ' + str(e), debug.debugLevel.ERROR)
return False return False
def shutdownRequest(self): def shutdownRequest(self):
try: try:
self.environment['runtime']['eventManager'].stopMainEventLoop() self.environment['runtime']['eventManager'].stopMainEventLoop()
except: except Exception as e:
pass self.environment['runtime']['debug'].writeDebugOut('shutdownRequest: Error stopping main event loop: ' + str(e), debug.debugLevel.ERROR)
def captureSignal(self, sigInit, frame): def captureSignal(self, sigInit, frame):
self.shutdownRequest() self.shutdownRequest()
+2 -2
View File
@@ -30,8 +30,8 @@ class helpManager():
else: else:
try: try:
self.env['bindings'] = self.env['runtime']['settingsManager'].getBindingBackup() self.env['bindings'] = self.env['runtime']['settingsManager'].getBindingBackup()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('helpManager setTutorialMode: Error restoring binding backup: ' + str(e), debug.debugLevel.ERROR)
def isTutorialMode(self): def isTutorialMode(self):
return self.env['general']['tutorialMode'] return self.env['general']['tutorialMode']
def getFormattedShortcutForCommand(self, command): def getFormattedShortcutForCommand(self, command):
+2 -2
View File
@@ -76,5 +76,5 @@ class inputDriver():
return return
try: try:
self.removeAllDevices() self.removeAllDevices()
except: except Exception as e:
pass print(f'inputDriver __del__: Error removing devices: {str(e)}')
+13 -13
View File
@@ -44,8 +44,8 @@ class inputManager():
event = None event = None
try: try:
event = self.env['runtime']['inputDriver'].getInputEvent() event = self.env['runtime']['inputDriver'].getInputEvent()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('inputManager getInputEvent: Error getting input event: ' + str(e), debug.debugLevel.ERROR)
return event return event
def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True): def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True):
self.executeDeviceGrab = newExecuteDeviceGrab self.executeDeviceGrab = newExecuteDeviceGrab
@@ -77,8 +77,8 @@ class inputManager():
# Force a release of devices if possible through alternative means # Force a release of devices if possible through alternative means
try: try:
self.env['runtime']['inputDriver'].forceUngrab() self.env['runtime']['inputDriver'].forceUngrab()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('inputManager handleDeviceGrab: Error forcing ungrab: ' + str(e), debug.debugLevel.ERROR)
break break
time.sleep(0.25) time.sleep(0.25)
self.env['runtime']['debug'].writeDebugOut(f"retry ungrabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut(f"retry ungrabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
@@ -170,8 +170,8 @@ class inputManager():
self.env['runtime']['inputDriver'].toggleLedState(1) self.env['runtime']['inputDriver'].toggleLedState(1)
elif mEvent['EventName'] == 'KEY_SCROLLLOCK': elif mEvent['EventName'] == 'KEY_SCROLLLOCK':
self.env['runtime']['inputDriver'].toggleLedState(2) self.env['runtime']['inputDriver'].toggleLedState(2)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('inputManager handleLedStates: Error toggling LED state: ' + str(e), debug.debugLevel.ERROR)
def grabAllDevices(self): def grabAllDevices(self):
if self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
try: try:
@@ -192,18 +192,18 @@ class inputManager():
def updateInputDevices(self, newDevice = None): def updateInputDevices(self, newDevice = None):
try: try:
self.env['runtime']['inputDriver'].updateInputDevices(newDevice) self.env['runtime']['inputDriver'].updateInputDevices(newDevice)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('inputManager updateInputDevices: Error updating input devices: ' + str(e), debug.debugLevel.ERROR)
try: try:
if self.env['runtime']['screenManager']: if self.env['runtime']['screenManager']:
self.handleDeviceGrab(force = True) self.handleDeviceGrab(force = True)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('inputManager updateInputDevices: Error handling device grab: ' + str(e), debug.debugLevel.ERROR)
def removeAllDevices(self): def removeAllDevices(self):
try: try:
self.env['runtime']['inputDriver'].removeAllDevices() self.env['runtime']['inputDriver'].removeAllDevices()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('inputManager removeAllDevices: Error removing devices: ' + str(e), debug.debugLevel.ERROR)
def convertEventName(self, eventName): def convertEventName(self, eventName):
if not eventName: if not eventName:
@@ -364,7 +364,7 @@ class inputManager():
for key in keys: for key in keys:
try: try:
shortcutRepeat = int(key) shortcutRepeat = int(key)
except: except Exception as e:
if not self.isValidKey(key.upper()): if not self.isValidKey(key.upper()):
self.env['runtime']['debug'].writeDebugOut("invalid key : "+ key.upper() + ' command:' +commandName ,debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut("invalid key : "+ key.upper() + ' command:' +commandName ,debug.debugLevel.WARNING)
invalid = True invalid = True
+7 -4
View File
@@ -30,11 +30,12 @@ class memoryManager():
if not self.listStorageValid(name): if not self.listStorageValid(name):
return return
if self.listStorage[name]['maxLength'] == None: if self.listStorage[name]['maxLength'] == None:
self.listStorage[name]['list'] = [value] + self.listStorage[name]['list'] # Fallback: if maxLength is still None, apply default limit of 1000
self.listStorage[name]['list'] = [value] + self.listStorage[name]['list'][:999]
else: else:
self.listStorage[name]['list'] = [value] + self.listStorage[name]['list'][:self.listStorage[name]['maxLength'] -1] self.listStorage[name]['list'] = [value] + self.listStorage[name]['list'][:self.listStorage[name]['maxLength'] -1]
self.listStorage[name]['index'] = 0 self.listStorage[name]['index'] = 0
def addIndexList(self, name, maxLength = None, currList = [], currIndex = -1): def addIndexList(self, name, maxLength = 1000, currList = [], currIndex = -1):
if len(currList) != 0 and (currIndex == -1): if len(currList) != 0 and (currIndex == -1):
currIndex = 0 currIndex = 0
self.listStorage[name] = {'list': currList, 'index': currIndex, 'maxLength': maxLength} self.listStorage[name] = {'list': currList, 'index': currIndex, 'maxLength': maxLength}
@@ -102,7 +103,8 @@ class memoryManager():
return False return False
try: try:
return self.listStorage[name]['index'] return self.listStorage[name]['index']
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('memoryManager getCurrentIndex: Error accessing index for ' + name + ': ' + str(e), debug.debugLevel.ERROR)
return -1 return -1
def isIndexListEmpty(self, name): def isIndexListEmpty(self, name):
if not self.listStorageValid(name): if not self.listStorageValid(name):
@@ -119,5 +121,6 @@ class memoryManager():
return None return None
try: try:
return self.listStorage[name]['list'][currIndex] return self.listStorage[name]['list'][currIndex]
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('memoryManager getIndexListElement: Error accessing element for ' + name + ': ' + str(e), debug.debugLevel.ERROR)
return None return None
+6 -6
View File
@@ -111,8 +111,8 @@ class outputManager():
try: try:
self.env['runtime']['speechDriver'].cancel() self.env['runtime']['speechDriver'].cancel()
self.env['runtime']['debug'].writeDebugOut("Interrupt speech", debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Interrupt speech", debug.debugLevel.INFO)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('outputManager interruptOutput: Error canceling speech: ' + str(e), debug.debugLevel.ERROR)
def playSoundIcon(self, soundIcon='', interrupt=True): def playSoundIcon(self, soundIcon='', interrupt=True):
if soundIcon == '': if soundIcon == '':
@@ -124,8 +124,8 @@ class outputManager():
try: try:
e = self.env['soundIcons'][soundIcon] e = self.env['soundIcons'][soundIcon]
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("SoundIcon doesnt exist: " + soundIcon, debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut('outputManager playSoundIcon: SoundIcon does not exist ' + soundIcon + ': ' + str(e), debug.debugLevel.WARNING)
return False return False
if self.env['runtime']['soundDriver'] == None: if self.env['runtime']['soundDriver'] == None:
@@ -166,8 +166,8 @@ class outputManager():
adjustVolume = 0.0 adjustVolume = 0.0
try: try:
adjustVolume = 1.0 - (frequence / 20000) adjustVolume = 1.0 - (frequence / 20000)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('outputManager playFrequence: Error calculating adjust volume: ' + str(e), debug.debugLevel.ERROR)
if adjustVolume > 9.0: if adjustVolume > 9.0:
adjustVolume = 9.0 adjustVolume = 9.0
@@ -29,14 +29,14 @@ class processManager():
# pass # pass
#except: #except:
# pass # pass
proc.join() proc.join(timeout=5.0) # Timeout to prevent hanging shutdown
for t in self._Threads: for t in self._Threads:
t.join() t.join(timeout=5.0) # Timeout to prevent hanging shutdown
def heartBeatTimer(self, active): def heartBeatTimer(self, active):
try: try:
time.sleep(0.5) time.sleep(0.5)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('processManager heartBeatTimer: Error during sleep: ' + str(e), debug.debugLevel.ERROR)
return time.time() return time.time()
def addCustomEventThread(self, function, pargs = None, multiprocess = False, runOnce = False): def addCustomEventThread(self, function, pargs = None, multiprocess = False, runOnce = False):
eventQueue = self.env['runtime']['eventManager'].getEventQueue() eventQueue = self.env['runtime']['eventManager'].getEventQueue()
@@ -31,7 +31,7 @@ class punctuationManager():
for char in currLevel: for char in currLevel:
try: try:
del currAllPunctNone[ord(char)] del currAllPunctNone[ord(char)]
except: except Exception as e:
pass pass
return text.translate(currAllPunctNone) return text.translate(currAllPunctNone)
def useCustomDict(self, text, customDict, seperator=''): def useCustomDict(self, text, customDict, seperator=''):
@@ -84,7 +84,7 @@ class punctuationManager():
punctList = list(self.env['punctuation']['LEVELDICT'].keys()) punctList = list(self.env['punctuation']['LEVELDICT'].keys())
try: try:
currIndex = punctList.index(self.env['runtime']['settingsManager'].getSetting('general', 'punctuationLevel').lower()) # curr punctuation currIndex = punctList.index(self.env['runtime']['settingsManager'].getSetting('general', 'punctuationLevel').lower()) # curr punctuation
except: except Exception as e:
return False return False
currIndex += 1 currIndex += 1
if currIndex >= len(punctList): if currIndex >= len(punctList):
@@ -30,7 +30,8 @@ class quickMenuManager():
continue continue
try: try:
t = self.settings[entry[0]][entry[1]] t = self.settings[entry[0]][entry[1]]
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('quickMenuManager loadMenu: Setting not found ' + str(entry) + ': ' + str(e), debug.debugLevel.ERROR)
print(entry[0],entry[1], 'not found') print(entry[0],entry[1], 'not found')
continue continue
self.quickMenu.append({'section': entry[0], 'setting': entry[1]}) self.quickMenu.append({'section': entry[0], 'setting': entry[1]})
@@ -56,7 +57,8 @@ class quickMenuManager():
valueString = '' valueString = ''
try: try:
valueString = self.env['runtime']['settingsManager'].getSetting(section, setting) valueString = self.env['runtime']['settingsManager'].getSetting(section, setting)
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('quickMenuManager nextValue: Error getting setting value: ' + str(e), debug.debugLevel.ERROR)
return False return False
try: try:
@@ -89,7 +91,8 @@ class quickMenuManager():
valueString = '' valueString = ''
try: try:
valueString = self.env['runtime']['settingsManager'].getSetting(section, setting) valueString = self.env['runtime']['settingsManager'].getSetting(section, setting)
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('quickMenuManager prevValue: Error getting setting value: ' + str(e), debug.debugLevel.ERROR)
return False return False
try: try:
if isinstance(self.settings[section][setting], str): if isinstance(self.settings[section][setting], str):
@@ -120,12 +123,14 @@ class quickMenuManager():
return '' return ''
try: try:
return _(self.quickMenu[self.position]['section']) + ' ' + _(self.quickMenu[self.position]['setting']) return _(self.quickMenu[self.position]['section']) + ' ' + _(self.quickMenu[self.position]['setting'])
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('quickMenuManager getCurrentEntry: Error formatting entry: ' + str(e), debug.debugLevel.ERROR)
return _('setting invalid') return _('setting invalid')
def getCurrentValue(self): def getCurrentValue(self):
if len(self.quickMenu) == 0: if len(self.quickMenu) == 0:
return '' return ''
try: try:
return self.env['runtime']['settingsManager'].getSetting(self.quickMenu[self.position]['section'], self.quickMenu[self.position]['setting']) return self.env['runtime']['settingsManager'].getSetting(self.quickMenu[self.position]['section'], self.quickMenu[self.position]['setting'])
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('quickMenuManager getCurrentValue: Error getting setting value: ' + str(e), debug.debugLevel.ERROR)
return _('setting value invalid') return _('setting value invalid')
+1 -1
View File
@@ -252,7 +252,7 @@ class remoteManager():
else: else:
clipboardFile.write('') clipboardFile.write('')
clipboardFile.close() clipboardFile.close()
os.chmod(clipboardFilePath, 0o666) os.chmod(clipboardFilePath, 0o644)
self.env['runtime']['outputManager'].presentText(_('clipboard exported to file'), interrupt=True) self.env['runtime']['outputManager'].presentText(_('clipboard exported to file'), interrupt=True)
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('export_clipboard_to_file:run: Filepath:'+ clipboardFile +' trace:' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('export_clipboard_to_file:run: Filepath:'+ clipboardFile +' trace:' + str(e),debug.debugLevel.ERROR)
+4 -4
View File
@@ -41,13 +41,13 @@ class screenManager():
def getCurrScreen(self): def getCurrScreen(self):
try: try:
self.env['runtime']['screenDriver'].getCurrScreen() self.env['runtime']['screenDriver'].getCurrScreen()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('screenManager getCurrScreen: Error getting current screen: ' + str(e), debug.debugLevel.ERROR)
def getSessionInformation(self): def getSessionInformation(self):
try: try:
self.env['runtime']['screenDriver'].getSessionInformation() self.env['runtime']['screenDriver'].getSessionInformation()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('screenManager getSessionInformation: Error getting session info: ' + str(e), debug.debugLevel.ERROR)
def shutdown(self): def shutdown(self):
self.env['runtime']['settingsManager'].shutdownDriver('screenDriver') self.env['runtime']['settingsManager'].shutdownDriver('screenDriver')
def isCurrScreenIgnoredChanged(self): def isCurrScreenIgnoredChanged(self):
+13 -11
View File
@@ -94,7 +94,8 @@ class settingsManager():
self.env['settings'] = ConfigParser() self.env['settings'] = ConfigParser()
try: try:
self.env['settings'].read(settingConfigPath) self.env['settings'].read(settingConfigPath)
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('settingsManager loadSettings: Error reading config file: ' + str(e), debug.debugLevel.ERROR)
return False return False
self.setSettingsFile(settingConfigPath) self.setSettingsFile(settingConfigPath)
return True return True
@@ -112,7 +113,7 @@ class settingsManager():
configFile = open(settingConfigPath, 'w') configFile = open(settingConfigPath, 'w')
self.env['settings'].write(configFile) self.env['settings'].write(configFile)
configFile.close() configFile.close()
os.chmod(settingConfigPath, 0o666) os.chmod(settingConfigPath, 0o644)
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('saveSettings: save settingsfile:' + settingConfigPath + 'failed. Error:' + str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('saveSettings: save settingsfile:' + settingConfigPath + 'failed. Error:' + str(e), debug.debugLevel.ERROR)
def setSetting(self, section, setting, value): def setSetting(self, section, setting, value):
@@ -124,11 +125,11 @@ class settingsManager():
try: try:
value = self.settingArgDict[section][setting] value = self.settingArgDict[section][setting]
return value return value
except: except Exception as e:
pass pass
try: try:
value = self.env['settings'].get(section, setting) value = self.env['settings'].get(section, setting)
except: except Exception as e:
value = str(self.settings[section][setting]) value = str(self.settings[section][setting])
return value return value
@@ -141,7 +142,7 @@ class settingsManager():
pass pass
try: try:
value = self.env['settings'].getint(section, setting) value = self.env['settings'].getint(section, setting)
except: except Exception as e:
value = self.settings[section][setting] value = self.settings[section][setting]
return value return value
@@ -154,7 +155,7 @@ class settingsManager():
pass pass
try: try:
value = self.env['settings'].getfloat(section, setting) value = self.env['settings'].getfloat(section, setting)
except: except Exception as e:
value = self.settings[section][setting] value = self.settings[section][setting]
return value return value
@@ -167,15 +168,16 @@ class settingsManager():
pass pass
try: try:
value = self.env['settings'].getboolean(section, setting) value = self.env['settings'].getboolean(section, setting)
except: except Exception as e:
value = self.settings[section][setting] value = self.settings[section][setting]
return value return value
def loadDriver(self, driverName, driverType): def loadDriver(self, driverName, driverType):
try: try:
self.env['runtime'][driverType].shutdown(self.env) if self.env['runtime'][driverType] is not None:
except: self.env['runtime'][driverType].shutdown(self.env)
pass except Exception as e:
self.env['runtime']['debug'].writeDebugOut('settingsManager loadDriver: Error shutting down driver: ' + str(e), debug.debugLevel.ERROR)
try: try:
driver_mod = module_utils.importModule(driverName, driver_mod = module_utils.importModule(driverName,
fenrirPath + "/" + driverType + '/' + driverName + '.py') fenrirPath + "/" + driverType + '/' + driverName + '.py')
@@ -223,7 +225,7 @@ class settingsManager():
self.settingArgDict[section] = {} self.settingArgDict[section] = {}
try: try:
t = self.settings[section][setting] t = self.settings[section][setting]
except: except Exception as e:
print(section,setting, 'not found') print(section,setting, 'not found')
return return
try: try:
+11 -6
View File
@@ -147,8 +147,8 @@ class vmenuManager():
try: try:
self.currIndex = None self.currIndex = None
self.env['bindings'] = self.env['runtime']['settingsManager'].getBindingBackup() self.env['bindings'] = self.env['runtime']['settingsManager'].getBindingBackup()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vmenuManager setActive: Error loading binding backup: ' + str(e), debug.debugLevel.ERROR)
def createMenuTree(self, resetIndex = True): def createMenuTree(self, resetIndex = True):
if resetIndex: if resetIndex:
self.currIndex = None self.currIndex = None
@@ -169,7 +169,8 @@ class vmenuManager():
r = self.getValueByPath(self.menuDict, self.currIndex) r = self.getValueByPath(self.menuDict, self.currIndex)
if r == {}: if r == {}:
self.currIndex = None self.currIndex = None
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('vmenuManager createMenuTree: Error checking menu index validity: ' + str(e), debug.debugLevel.ERROR)
self.currIndex = None self.currIndex = None
def executeMenu(self): def executeMenu(self):
if self.currIndex == None: if self.currIndex == None:
@@ -185,8 +186,8 @@ class vmenuManager():
self.incLevel() self.incLevel()
text = self.getCurrentEntry() text = self.getCurrentEntry()
self.env['runtime']['outputManager'].presentText(text, interrupt=True) self.env['runtime']['outputManager'].presentText(text, interrupt=True)
except: except Exception as ex:
print(e) self.env['runtime']['debug'].writeDebugOut('vmenuManager executeMenu: Error presenting menu text: ' + str(ex), debug.debugLevel.ERROR)
def incLevel(self): def incLevel(self):
if self.currIndex == None: if self.currIndex == None:
@@ -195,7 +196,8 @@ class vmenuManager():
r = self.getValueByPath(self.menuDict, self.currIndex +[0]) r = self.getValueByPath(self.menuDict, self.currIndex +[0])
if r == {}: if r == {}:
return False return False
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('vmenuManager incLevel: Error accessing menu path: ' + str(e), debug.debugLevel.ERROR)
return False return False
self.currIndex.append(0) self.currIndex.append(0)
return True return True
@@ -267,6 +269,9 @@ class vmenuManager():
fileName = fileName.split('/')[-1] fileName = fileName.split('/')[-1]
if fileName.startswith('__'): if fileName.startswith('__'):
continue continue
# Skip base classes that shouldn't be loaded as commands
if fileName.endswith('_base'):
continue
command = self.env['runtime']['commandManager'].loadFile(root + '/' + f) command = self.env['runtime']['commandManager'].loadFile(root + '/' + f)
tree.update({fileName + ' ' + _('Action'): command}) tree.update({fileName + ' ' + _('Action'): command})
except Exception as e: except Exception as e:
+1 -1
View File
@@ -4,5 +4,5 @@
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributers.
version = "2025.06.20" version = "2025.06.27"
codeName = "master" codeName = "master"
@@ -126,7 +126,8 @@ class driver(inputDriver):
try: try:
event = device.read_one() event = device.read_one()
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('evdevDriver handleInputEvent: Error reading event: ' + str(e), debug.debugLevel.ERROR)
self.removeDevice(fd) self.removeDevice(fd)
continue continue
while(event): while(event):
@@ -166,8 +167,8 @@ class driver(inputDriver):
if uDevice: if uDevice:
if self.gDevices[iDevice.fd]: if self.gDevices[iDevice.fd]:
self.writeUInput(uDevice, event) self.writeUInput(uDevice, event)
except Exception: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver writeEventBuffer: Error writing event: ' + str(e), debug.debugLevel.ERROR)
def writeUInput(self, uDevice, event): def writeUInput(self, uDevice, event):
if not self._initialized: if not self._initialized:
@@ -211,14 +212,16 @@ class driver(inputDriver):
try: try:
with open(deviceFile) as f: with open(deviceFile) as f:
pass pass
except Exception: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('evdevDriver updateInputDevices: Error opening device file: ' + str(e), debug.debugLevel.ERROR)
continue continue
# 3 pos absolute # 3 pos absolute
# 2 pos relative # 2 pos relative
# 1 Keys # 1 Keys
try: try:
currDevice = evdev.InputDevice(deviceFile) currDevice = evdev.InputDevice(deviceFile)
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('evdevDriver createDeviceType: Error creating device: ' + str(e), debug.debugLevel.ERROR)
continue continue
try: try:
# FIX: Check if attributes exist before accessing them # FIX: Check if attributes exist before accessing them
@@ -228,8 +231,8 @@ class driver(inputDriver):
continue continue
if hasattr(currDevice, 'name') and currDevice.name and 'BRLTTY' in currDevice.name.upper(): if hasattr(currDevice, 'name') and currDevice.name and 'BRLTTY' in currDevice.name.upper():
continue continue
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver: Error checking device capabilities: ' + str(e), debug.debugLevel.ERROR)
cap = currDevice.capabilities() cap = currDevice.capabilities()
if mode in ['ALL', 'NOMICE']: if mode in ['ALL', 'NOMICE']:
if eventType.EV_KEY in cap: if eventType.EV_KEY in cap:
@@ -257,8 +260,8 @@ class driver(inputDriver):
try: try:
device_name = currDevice.name if hasattr(currDevice, 'name') else "unknown" device_name = currDevice.name if hasattr(currDevice, 'name') else "unknown"
self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + device_name + ' ' + str(e), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + device_name + ' ' + str(e), debug.debugLevel.INFO)
except: except Exception as ex:
self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + str(e), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + str(ex), debug.debugLevel.INFO)
self.iDeviceNo = len(evdev.list_devices()) self.iDeviceNo = len(evdev.list_devices())
self.updateMPiDevicesFD() self.updateMPiDevicesFD()
@@ -270,8 +273,8 @@ class driver(inputDriver):
for fd in self.iDevicesFD: for fd in self.iDevicesFD:
if not fd in self.iDevices: if not fd in self.iDevices:
self.iDevicesFD.remove(fd) self.iDevicesFD.remove(fd)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver updateMPiDevicesFD: Error updating device file descriptors: ' + str(e), debug.debugLevel.ERROR)
def mapEvent(self, event): def mapEvent(self, event):
if not self._initialized: if not self._initialized:
@@ -294,7 +297,7 @@ class driver(inputDriver):
mEvent['EventState'] = event.value mEvent['EventState'] = event.value
mEvent['EventType'] = event.type mEvent['EventType'] = event.type
return mEvent return mEvent
except Exception: except Exception as e:
return None return None
def getLedState(self, led=0): def getLedState(self, led=0):
@@ -379,16 +382,16 @@ class driver(inputDriver):
# if it doesnt work clean up # if it doesnt work clean up
try: try:
del(self.iDevices[newDevice.fd]) del(self.iDevices[newDevice.fd])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error removing iDevice: ' + str(e), debug.debugLevel.ERROR)
try: try:
del(self.uDevices[newDevice.fd]) del(self.uDevices[newDevice.fd])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error removing uDevice: ' + str(e), debug.debugLevel.ERROR)
try: try:
del(self.gDevices[newDevice.fd]) del(self.gDevices[newDevice.fd])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver addDevice: Error cleaning up gDevice: ' + str(e), debug.debugLevel.ERROR)
def grabDevice(self, fd): def grabDevice(self, fd):
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
@@ -447,33 +450,33 @@ class driver(inputDriver):
with self._deviceLock: with self._deviceLock:
try: try:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO)
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' Error: ' + str(e), debug.debugLevel.INFO)
self.clearEventBuffer() self.clearEventBuffer()
try: try:
self.ungrabDevice(fd) self.ungrabDevice(fd)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error ungrabbing device ' + str(fd) + ': ' + str(e), debug.debugLevel.ERROR)
try: try:
self.iDevices[fd].close() self.iDevices[fd].close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error closing iDevice ' + str(fd) + ': ' + str(e), debug.debugLevel.ERROR)
try: try:
self.uDevices[fd].close() self.uDevices[fd].close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error closing uDevice ' + str(fd) + ': ' + str(e), debug.debugLevel.ERROR)
try: try:
del(self.iDevices[fd]) del(self.iDevices[fd])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error deleting iDevice ' + str(fd) + ': ' + str(e), debug.debugLevel.ERROR)
try: try:
del(self.uDevices[fd]) del(self.uDevices[fd])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error deleting uDevice ' + str(fd) + ': ' + str(e), debug.debugLevel.ERROR)
try: try:
del(self.gDevices[fd]) del(self.gDevices[fd])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver removeDevice: Error deleting gDevice ' + str(fd) + ': ' + str(e), debug.debugLevel.ERROR)
self.updateMPiDevicesFD() self.updateMPiDevicesFD()
def hasIDevices(self): def hasIDevices(self):
@@ -491,8 +494,8 @@ class driver(inputDriver):
try: try:
self.UInputinject.write(e.EV_KEY, e.ecodes[key], state) self.UInputinject.write(e.EV_KEY, e.ecodes[key], state)
self.UInputinject.syn() self.UInputinject.syn()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('evdevDriver sendKey: Error sending key ' + str(key) + ': ' + str(e), debug.debugLevel.ERROR)
def removeAllDevices(self): def removeAllDevices(self):
if not self.hasIDevices(): if not self.hasIDevices():
@@ -35,19 +35,19 @@ class driver(remoteDriver):
client_sock, client_addr = self.fenrirSock.accept() client_sock, client_addr = self.fenrirSock.accept()
try: try:
rawdata = client_sock.recv(8129) rawdata = client_sock.recv(8129)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('tcpDriver watchDog: Error receiving data from client: ' + str(e), debug.debugLevel.ERROR)
try: try:
data = rawdata.decode("utf-8").rstrip().lstrip() data = rawdata.decode("utf-8").rstrip().lstrip()
eventQueue.put({"Type":fenrirEventType.RemoteIncomming, eventQueue.put({"Type":fenrirEventType.RemoteIncomming,
"Data": data "Data": data
}) })
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('tcpDriver watchDog: Error decoding/queuing data: ' + str(e), debug.debugLevel.ERROR)
try: try:
client_sock.close() client_sock.close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('tcpDriver watchDog: Error closing client socket: ' + str(e), debug.debugLevel.ERROR)
if self.fenrirSock: if self.fenrirSock:
self.fenrirSock.close() self.fenrirSock.close()
self.fenrirSock = None self.fenrirSock = None
@@ -22,8 +22,8 @@ class driver(remoteDriver):
socketFile = '' socketFile = ''
try: try:
socketFile = self.env['runtime']['settingsManager'].getSetting('remote', 'socketFile') socketFile = self.env['runtime']['settingsManager'].getSetting('remote', 'socketFile')
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('unixDriver watchDog: Error getting socket file setting: ' + str(e), debug.debugLevel.ERROR)
if socketFile == '': if socketFile == '':
if self.env['runtime']['settingsManager'].getSetting('screen', 'driver') =='vcsaDriver': if self.env['runtime']['settingsManager'].getSetting('screen', 'driver') =='vcsaDriver':
socketFile = '/tmp/fenrirscreenreader-deamon.sock' socketFile = '/tmp/fenrirscreenreader-deamon.sock'
@@ -45,21 +45,27 @@ class driver(remoteDriver):
continue continue
if self.fenrirSock in r: if self.fenrirSock in r:
client_sock, client_addr = self.fenrirSock.accept() client_sock, client_addr = self.fenrirSock.accept()
try: # Ensure client socket is always closed to prevent resource leaks
rawdata = client_sock.recv(8129) try:
except: try:
pass rawdata = client_sock.recv(8129)
try: except Exception as e:
data = rawdata.decode("utf-8").rstrip().lstrip() self.env['runtime']['debug'].writeDebugOut('unixDriver watchDog: Error receiving data from client: ' + str(e), debug.debugLevel.ERROR)
eventQueue.put({"Type":fenrirEventType.RemoteIncomming, rawdata = b'' # Set default empty data if recv fails
"Data": data
}) try:
except: data = rawdata.decode("utf-8").rstrip().lstrip()
pass eventQueue.put({"Type":fenrirEventType.RemoteIncomming,
try: "Data": data
client_sock.close() })
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('unixDriver watchDog: Error decoding/queuing data: ' + str(e), debug.debugLevel.ERROR)
finally:
# Always close client socket, even if data processing fails
try:
client_sock.close()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('unixDriver watchDog: Error closing client socket: ' + str(e), debug.debugLevel.ERROR)
if self.fenrirSock: if self.fenrirSock:
self.fenrirSock.close() self.fenrirSock.close()
self.fenrirSock = None self.fenrirSock = None
@@ -40,7 +40,9 @@ class Terminal:
for y in lines: for y in lines:
try: try:
t = self.attributes[y] t = self.attributes[y]
except: except Exception as e:
# Terminal class doesn't have access to env, use fallback logging
print(f'ptyDriver Terminal updateAttributes: Error accessing attributes: {e}')
self.attributes.append([]) self.attributes.append([])
self.attributes[y] = [list(attribute[1:]) + [False, 'default', 'default'] for attribute in (buffer[y].values())] self.attributes[y] = [list(attribute[1:]) + [False, 'default', 'default'] for attribute in (buffer[y].values())]
@@ -135,7 +137,9 @@ class driver(screenDriver):
try: try:
if env["TERM"] == '': if env["TERM"] == '':
env["TERM"] = 'linux' env["TERM"] = 'linux'
except: except Exception as e:
# Child process doesn't have access to env, use fallback logging
print(f'ptyDriver spawnTerminal: Error checking TERM environment: {e}')
env["TERM"] = 'linux' env["TERM"] = 'linux'
os.execvpe(argv[0], argv, env) os.execvpe(argv[0], argv, env)
# File-like object for I/O with the child process aka command. # File-like object for I/O with the child process aka command.
@@ -184,7 +188,8 @@ class driver(screenDriver):
if self.shortcutType == 'KEY': if self.shortcutType == 'KEY':
try: try:
self.injectTextToScreen(msgBytes) self.injectTextToScreen(msgBytes)
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ptyDriver getInputData: Error injecting text to screen: ' + str(e), debug.debugLevel.ERROR)
eventQueue.put({"Type":fenrirEventType.StopMainLoop,"Data":None}) eventQueue.put({"Type":fenrirEventType.StopMainLoop,"Data":None})
break break
else: else:
@@ -38,8 +38,8 @@ class driver(screenDriver):
# set workaround for paste clipboard -> injectTextToScreen # set workaround for paste clipboard -> injectTextToScreen
subprocess.run(['sysctl', 'dev.tty.legacy_tiocsti=1'], subprocess.run(['sysctl', 'dev.tty.legacy_tiocsti=1'],
check=False, capture_output=True, timeout=5) check=False, capture_output=True, timeout=5)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver shutdown: Error running fgconsole: ' + str(e), debug.debugLevel.ERROR)
def initialize(self, environment): def initialize(self, environment):
self.env = environment self.env = environment
self.env['runtime']['attributeManager'].appendDefaultAttributes([ self.env['runtime']['attributeManager'].appendDefaultAttributes([
@@ -105,7 +105,8 @@ class driver(screenDriver):
file.seek(0) file.seek(0)
try: try:
d = file.read() d = file.read()
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('vcsaDriver getScreenText: Error reading file: ' + str(e), debug.debugLevel.ERROR)
file.seek(0) file.seek(0)
while True: while True:
# Read from file # Read from file
@@ -162,19 +163,19 @@ class driver(screenDriver):
if currScreen != oldScreen: if currScreen != oldScreen:
try: try:
watchdog.unregister(vcsa[oldScreen]) watchdog.unregister(vcsa[oldScreen])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver updateWatchdog: Error unregistering watchdog: ' + str(e), debug.debugLevel.ERROR)
try: try:
watchdog.register(vcsa[currScreen], select.POLLPRI | select.POLLERR) watchdog.register(vcsa[currScreen], select.POLLPRI | select.POLLERR)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver updateWatchdog: Error registering watchdog: ' + str(e), debug.debugLevel.ERROR)
self.updateCharMap(currScreen) self.updateCharMap(currScreen)
oldScreen = currScreen oldScreen = currScreen
try: try:
vcsa[currScreen].seek(0) vcsa[currScreen].seek(0)
lastScreenContent = self.readFile(vcsa[currScreen]) lastScreenContent = self.readFile(vcsa[currScreen])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver updateWatchdog: Error reading screen content: ' + str(e), debug.debugLevel.ERROR)
vcsuContent = None vcsuContent = None
if useVCSU: if useVCSU:
vcsu[currScreen].seek(0) vcsu[currScreen].seek(0)
@@ -233,23 +234,23 @@ class driver(screenDriver):
try: try:
if watchdog: if watchdog:
watchdog.close() watchdog.close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver updateWatchdog: Error closing watchdog: ' + str(e), debug.debugLevel.ERROR)
try: try:
if tty: if tty:
tty.close() tty.close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver shutdown: Error closing TTY: ' + str(e), debug.debugLevel.ERROR)
for handle in vcsa.values(): for handle in vcsa.values():
try: try:
handle.close() handle.close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver shutdown: Error closing VCSA handle: ' + str(e), debug.debugLevel.ERROR)
for handle in vcsu.values(): for handle in vcsu.values():
try: try:
handle.close() handle.close()
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver shutdown: Error closing VCSU handle: ' + str(e), debug.debugLevel.ERROR)
def createScreenEventData(self, screen, vcsaContent, vcsuContent = None): def createScreenEventData(self, screen, vcsaContent, vcsuContent = None):
eventData = { eventData = {
@@ -269,15 +270,15 @@ class driver(screenDriver):
try: try:
eventData['text'], eventData['attributes'] =\ eventData['text'], eventData['attributes'] =\
self.autoDecodeVCSA(vcsaContent[4:], eventData['lines'], eventData['columns']) self.autoDecodeVCSA(vcsaContent[4:], eventData['lines'], eventData['columns'])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver createScreenEventData: Error decoding VCSA content: ' + str(e), debug.debugLevel.ERROR)
# VCSU seems to give b' ' instead of b'\x00\x00\x00' (tsp), deactivated until its fixed # VCSU seems to give b' ' instead of b'\x00\x00\x00' (tsp), deactivated until its fixed
if vcsuContent != None: if vcsuContent != None:
try: try:
vcsuContentAsText = vcsuContent.decode('UTF-32') vcsuContentAsText = vcsuContent.decode('UTF-32')
eventData['text'] = screen_utils.insertNewlines(vcsuContentAsText, eventData['columns']) eventData['text'] = screen_utils.insertNewlines(vcsuContentAsText, eventData['columns'])
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver createScreenEventData: Error decoding VCSU content: ' + str(e), debug.debugLevel.ERROR)
return eventData.copy() return eventData.copy()
def updateCharMap(self, screen): def updateCharMap(self, screen):
self.charmap = {} self.charmap = {}
@@ -349,7 +350,8 @@ class driver(screenDriver):
try: try:
if sh & self.hichar: if sh & self.hichar:
ch |= 0x100 ch |= 0x100
except: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('vcsaDriver autoDecodeVCSA: Error processing character: ' + str(e), debug.debugLevel.ERROR)
ch = None ch = None
if self.hichar == 0x100: if self.hichar == 0x100:
attr >>= 1 attr >>= 1
@@ -364,8 +366,8 @@ class driver(screenDriver):
bold = 1 bold = 1
#if (ink != 7) or (paper != 0): #if (ink != 7) or (paper != 0):
# print(ink,paper) # print(ink,paper)
except: except Exception as e:
pass self.env['runtime']['debug'].writeDebugOut('vcsaDriver autoDecodeVCSA: Error processing attributes: ' + str(e), debug.debugLevel.ERROR)
try: try:
lineText += self.charmap[ch] lineText += self.charmap[ch]
except KeyError: except KeyError:
@@ -62,6 +62,18 @@ class driver(soundDriver):
return return
if self.soundType == 'file': if self.soundType == 'file':
self.proc.kill() self.proc.kill()
try:
self.proc.wait(timeout=1.0) # Wait for process to finish to prevent zombies
except subprocess.TimeoutExpired:
pass # Process already terminated
except Exception as e:
pass # Handle any other wait errors
if self.soundType == 'frequence': if self.soundType == 'frequence':
self.proc.kill() self.proc.kill()
try:
self.proc.wait(timeout=1.0) # Wait for process to finish to prevent zombies
except subprocess.TimeoutExpired:
pass # Process already terminated
except Exception as e:
pass # Handle any other wait errors
self.soundType = '' self.soundType = ''
@@ -57,6 +57,9 @@ class driver(soundDriver):
return return
self.cancel() self.cancel()
self.mainloop.quit() self.mainloop.quit()
# Wait for the GLib MainLoop thread to finish to prevent shutdown races
if hasattr(self, 'thread') and self.thread.is_alive():
self.thread.join(timeout=2.0) # 2 second timeout to prevent hanging
def _onPlayerMessage(self, bus, message): def _onPlayerMessage(self, bus, message):
if not self._initialized: if not self._initialized:
@@ -10,6 +10,7 @@ from threading import Thread, Lock
from queue import Queue, Empty from queue import Queue, Empty
import shlex import shlex
from subprocess import Popen from subprocess import Popen
import subprocess
from fenrirscreenreader.core.speechDriver import speechDriver from fenrirscreenreader.core.speechDriver import speechDriver
class speakQueue(Queue): class speakQueue(Queue):
@@ -73,17 +74,28 @@ class driver(speechDriver):
return return
self.clear_buffer() self.clear_buffer()
self.lock.acquire(True) self.lock.acquire(True)
if self.proc: try:
try: if self.proc:
self.proc.terminate()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:Cancel:self.proc.terminate():' + str(e),debug.debugLevel.WARNING)
try: try:
self.proc.kill() self.proc.terminate()
# Wait for process to finish to prevent zombies
try:
self.proc.wait(timeout=1.0)
except subprocess.TimeoutExpired:
# If terminate didn't work, force kill
self.proc.kill()
self.proc.wait(timeout=1.0)
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:Cancel:self.proc.kill():' + str(e),debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut('speechDriver:Cancel:self.proc.terminate():' + str(e),debug.debugLevel.WARNING)
self.proc = None try:
self.lock.release() self.proc.kill()
self.proc.wait(timeout=1.0) # Wait after kill to prevent zombies
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('speechDriver:Cancel:self.proc.kill():' + str(e),debug.debugLevel.WARNING)
self.proc = None
finally:
# Ensure lock is always released, even if process termination fails
self.lock.release()
def setCallback(self, callback): def setCallback(self, callback):
print('SpeechDummyDriver: setCallback') print('SpeechDummyDriver: setCallback')
+2 -1
View File
@@ -117,7 +117,8 @@ def getPhonetic(currChar):
if currChar.isupper(): if currChar.isupper():
phonChar = phonChar[0].upper() + phonChar[1:] phonChar = phonChar[0].upper() + phonChar[1:]
return phonChar return phonChar
except: except Exception as e:
# Utility function, no env access - return fallback
return currChar return currChar
def presentCharForReview(env, char, interrupt=True, announceCapital=True, flush=False): def presentCharForReview(env, char, interrupt=True, announceCapital=True, flush=False):
+10 -5
View File
@@ -58,8 +58,10 @@ def isValidShell(shell = ''):
return False return False
if not os.access(shell,os.X_OK): if not os.access(shell,os.X_OK):
return False return False
except: except Exception as e:
return False # Utility function, no env access - use fallback logging
print(f'screen_utils isValidShell: Error checking shell {shell}: {e}')
return False
return True return True
def getShell(): def getShell():
@@ -67,13 +69,15 @@ def getShell():
shell = os.environ["FENRIRSHELL"] shell = os.environ["FENRIRSHELL"]
if isValidShell(shell): if isValidShell(shell):
return shell return shell
except: except Exception as e:
# Utility function, no env access - continue silently
pass pass
try: try:
shell = os.environ["SHELL"] shell = os.environ["SHELL"]
if isValidShell(shell): if isValidShell(shell):
return shell return shell
except: except Exception as e:
# Utility function, no env access - continue silently
pass pass
try: try:
if os.access('/etc/passwd', os.R_OK): if os.access('/etc/passwd', os.R_OK):
@@ -85,7 +89,8 @@ def getShell():
if username == getpass.getuser(): if username == getpass.getuser():
if isValidShell(shell): if isValidShell(shell):
return shell return shell
except: except Exception as e:
# Utility function, no env access - continue silently
pass pass
if isValidShell('/bin/bash'): if isValidShell('/bin/bash'):
return '/bin/bash' return '/bin/bash'