diff --git a/src/fenrirscreenreader/commands/vmenu-navigation/search_a.py b/src/fenrirscreenreader/commands/vmenu-navigation/search_a.py index d4708630..7ef8987e 100644 --- a/src/fenrirscreenreader/commands/vmenu-navigation/search_a.py +++ b/src/fenrirscreenreader/commands/vmenu-navigation/search_a.py @@ -4,7 +4,12 @@ # Fenrir TTY screen reader # 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): def __init__(self): diff --git a/src/fenrirscreenreader/core/commandManager.py b/src/fenrirscreenreader/core/commandManager.py index ca832176..588fb996 100644 --- a/src/fenrirscreenreader/core/commandManager.py +++ b/src/fenrirscreenreader/core/commandManager.py @@ -84,11 +84,9 @@ class commandManager(): fileName = fileName.split('/')[-1] if fileName.startswith('__'): continue - try: - if self.env['commands'][section][fileName.upper()] != None: - continue - except Exception as e: - pass + # Check if command already exists to prevent duplicate loading + if fileName.upper() in self.env['commands'][section] and self.env['commands'][section][fileName.upper()] is not None: + continue if fileExtension.lower() == '.py': command_mod = module_utils.importModule(fileName, command) self.env['commands'][section][fileName.upper()] = command_mod.command() diff --git a/src/fenrirscreenreader/core/eventManager.py b/src/fenrirscreenreader/core/eventManager.py index 2d866b3e..94ae82cc 100644 --- a/src/fenrirscreenreader/core/eventManager.py +++ b/src/fenrirscreenreader/core/eventManager.py @@ -15,7 +15,7 @@ from ctypes import c_bool class eventManager(): def __init__(self): self.running = Value(c_bool, True) - self._eventQueue = Queue() # multiprocessing.Queue() + self._eventQueue = Queue(maxsize=100) # Bounded queue to prevent memory exhaustion self.cleanEventQueue() def initialize(self, environment): self.env = environment @@ -85,8 +85,16 @@ class eventManager(): return False if event == fenrirEventType.Ignore: return False - if self.getEventQueueSize() > 50: - if not event in [fenrirEventType.ScreenUpdate, fenrirEventType.HeartBeat]: - self.cleanEventQueue() - self._eventQueue.put({"Type":event,"Data":data}) + # Use bounded queue - if full, this will block briefly or drop older events + try: + self._eventQueue.put({"Type":event,"Data":data}, timeout=0.1) + 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 diff --git a/src/fenrirscreenreader/core/memoryManager.py b/src/fenrirscreenreader/core/memoryManager.py index 26a1a9c3..b6c21f5e 100644 --- a/src/fenrirscreenreader/core/memoryManager.py +++ b/src/fenrirscreenreader/core/memoryManager.py @@ -30,11 +30,12 @@ class memoryManager(): if not self.listStorageValid(name): return 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: self.listStorage[name]['list'] = [value] + self.listStorage[name]['list'][:self.listStorage[name]['maxLength'] -1] 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): currIndex = 0 self.listStorage[name] = {'list': currList, 'index': currIndex, 'maxLength': maxLength} diff --git a/src/fenrirscreenreader/core/processManager.py b/src/fenrirscreenreader/core/processManager.py index 78312f11..c75eee78 100644 --- a/src/fenrirscreenreader/core/processManager.py +++ b/src/fenrirscreenreader/core/processManager.py @@ -29,9 +29,9 @@ class processManager(): # pass #except: # pass - proc.join() + proc.join(timeout=5.0) # Timeout to prevent hanging shutdown for t in self._Threads: - t.join() + t.join(timeout=5.0) # Timeout to prevent hanging shutdown def heartBeatTimer(self, active): try: time.sleep(0.5) diff --git a/src/fenrirscreenreader/soundDriver/genericDriver.py b/src/fenrirscreenreader/soundDriver/genericDriver.py index c8badc5a..4d8e6200 100644 --- a/src/fenrirscreenreader/soundDriver/genericDriver.py +++ b/src/fenrirscreenreader/soundDriver/genericDriver.py @@ -62,6 +62,18 @@ class driver(soundDriver): return if self.soundType == 'file': 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': 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 = '' diff --git a/src/fenrirscreenreader/speechDriver/genericDriver.py b/src/fenrirscreenreader/speechDriver/genericDriver.py index e841218a..2fce657a 100644 --- a/src/fenrirscreenreader/speechDriver/genericDriver.py +++ b/src/fenrirscreenreader/speechDriver/genericDriver.py @@ -10,6 +10,7 @@ from threading import Thread, Lock from queue import Queue, Empty import shlex from subprocess import Popen +import subprocess from fenrirscreenreader.core.speechDriver import speechDriver class speakQueue(Queue): @@ -77,10 +78,18 @@ class driver(speechDriver): if self.proc: try: 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: self.env['runtime']['debug'].writeDebugOut('speechDriver:Cancel:self.proc.terminate():' + str(e),debug.debugLevel.WARNING) try: 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