From 8cd50c507007e303befc8cf6a2456d1347290798 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 26 Feb 2025 16:05:17 -0500 Subject: [PATCH 1/8] Hopefully improve accuracy of blank line reporting. --- .../commands/onScreenUpdate/60000-history.py | 12 +++++++++--- src/fenrirscreenreader/fenrirVersion.py | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/fenrirscreenreader/commands/onScreenUpdate/60000-history.py b/src/fenrirscreenreader/commands/onScreenUpdate/60000-history.py index 4d2e137b..6933b62f 100644 --- a/src/fenrirscreenreader/commands/onScreenUpdate/60000-history.py +++ b/src/fenrirscreenreader/commands/onScreenUpdate/60000-history.py @@ -35,12 +35,18 @@ class command(): 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']] + + is_blank = currLine.strip() == '' + if prevLine == currLine: if self.env['screen']['newDelta'] != '': return - if not currLine.isspace(): + + announce = currLine + if not is_blank: currPrompt = currLine.find('$') rootPrompt = currLine.find('#') if currPrompt <= 0: @@ -55,13 +61,13 @@ class command(): else: announce = currLine - if currLine.isspace(): + if is_blank: 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['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 - diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 57087e47..925ed497 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributers. -version = "2025.01.08" -codeName = "master" +version = "2025.02.26" +codeName = "testing" From e46926f1456e093080947ca3d9fd5c06545bd791 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 26 Feb 2025 17:02:25 -0500 Subject: [PATCH 2/8] Fixed a traceback on shutdown. Hopefully improved responsiveness with the diff. Trying rapidfuzz for smaller screen updates, add a catch to fall back to the original difflib if there are any problems. This is experimental, please watch for bugs. --- src/fenrirscreenreader/core/commandManager.py | 10 +++++-- src/fenrirscreenreader/core/screenManager.py | 30 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/fenrirscreenreader/core/commandManager.py b/src/fenrirscreenreader/core/commandManager.py index 92fceba8..163eab33 100644 --- a/src/fenrirscreenreader/core/commandManager.py +++ b/src/fenrirscreenreader/core/commandManager.py @@ -159,14 +159,20 @@ class commandManager(): self.env['runtime']['debug'].writeDebugOut("Loading script:" + fileName ,debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR) continue + def shutdownCommands(self, section): + # Check if the section exists in the commands dictionary + if section not in self.env['commands']: + self.env['runtime']['debug'].writeDebugOut("shutdownCommands: section not found:" + section, debug.debugLevel.WARNING) + return + for command in sorted(self.env['commands'][section]): try: self.env['commands'][section][command].shutdown() del self.env['commands'][section][command] except Exception as e: - self.env['runtime']['debug'].writeDebugOut("Shutdown command:" + section + "." + command ,debug.debugLevel.ERROR) - self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR) + self.env['runtime']['debug'].writeDebugOut("Shutdown command:" + section + "." + command, debug.debugLevel.ERROR) + self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) continue def executeSwitchTrigger(self, trigger, unLoadScript, loadScript): diff --git a/src/fenrirscreenreader/core/screenManager.py b/src/fenrirscreenreader/core/screenManager.py index 13203162..77b24676 100644 --- a/src/fenrirscreenreader/core/screenManager.py +++ b/src/fenrirscreenreader/core/screenManager.py @@ -7,6 +7,7 @@ from fenrirscreenreader.core import debug from fenrirscreenreader.utils import screen_utils import time, os, re, difflib +from rapidfuzz.distance import Levenshtein class screenManager(): def __init__(self): @@ -82,6 +83,7 @@ class screenManager(): def updateScreenIgnored(self): self.prevScreenIgnored = self.currScreenIgnored self.currScreenIgnored = self.isSuspendingScreen(self.env['screen']['newTTY']) + def update(self, eventData, trigger='onUpdate'): # set new "old" values self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes'] @@ -144,8 +146,11 @@ class screenManager(): cursorLineEndOffset = cursorLineStart + self.env['screen']['newCursor']['x'] + 3 oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset] newScreenText = self.env['screen']['newContentText'][cursorLineStartOffset:cursorLineEndOffset] + + # Use the original differ for typing mode to preserve behavior diff = self.differ.compare(oldScreenText, newScreenText) diffList = list(diff) + typing = True tempNewDelta = ''.join(x[2:] for x in diffList if x[0] == '+') if tempNewDelta.strip() != '': @@ -153,9 +158,28 @@ class screenManager(): diffList = ['+ ' + self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']] +'\n'] typing = False else: - diff = self.differ.compare(oldScreenText.split('\n'),\ - newScreenText.split('\n')) - diffList = list(diff) + # For screen changes, use the original differ + if self.isScreenChange() or trigger == 'onScreenChange': + diff = self.differ.compare(oldScreenText.split('\n'), + newScreenText.split('\n')) + diffList = list(diff) + else: + # Use rapidfuzz for normal updates - not for screen changes + try: + # Process line by line using rapidfuzz + old_lines = oldScreenText.split('\n') + new_lines = newScreenText.split('\n') + + # Use standard differ for better word grouping + diff = self.differ.compare(old_lines, new_lines) + diffList = list(diff) + + except Exception as e: + # Fall back to standard differ if there's any issue + self.env['runtime']['debug'].writeDebugOut('screenManager:update:rapidfuzz: ' + str(e), debug.debugLevel.ERROR) + diff = self.differ.compare(oldScreenText.split('\n'), + newScreenText.split('\n')) + diffList = list(diff) if not typing: self.env['screen']['newDelta'] = '\n'.join(x[2:] for x in diffList if x[0] == '+') From 145cab62219e2a438397692e7b90cdecf7995123 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 26 Feb 2025 17:08:50 -0500 Subject: [PATCH 3/8] Updated dependencies to include rapidfuzz. --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 518049ca..a164ca82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ pyudev>=0.21.0 pexpect pyttsx3 pyte>=0.7.0 +rapidfuzz>=2.0.0 diff --git a/setup.py b/setup.py index 1372b507..34c02fe6 100755 --- a/setup.py +++ b/setup.py @@ -100,6 +100,7 @@ setup( "daemonize>=2.5.0", "dbus-python>=1.2.8", "pyudev>=0.21.0", + "rapidfuzz>=2.0.0", "setuptools", "pexpect", "pyttsx3", From 4966b87ba1846cc617402fcb1433446beaab159b Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 26 Feb 2025 17:38:22 -0500 Subject: [PATCH 4/8] pyttsx removed from setup file because it's no longer a speech option. --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 34c02fe6..448707cc 100755 --- a/setup.py +++ b/setup.py @@ -103,7 +103,6 @@ setup( "rapidfuzz>=2.0.0", "setuptools", "pexpect", - "pyttsx3", "pyte>=0.7.0", ], ) From 73206ce393a3cc07795d6a4a323ad63a3bd8306c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 2 Mar 2025 15:25:06 -0500 Subject: [PATCH 5/8] Switched from xclip to pyperclip for import from x. --- .../commands/import_clipboard_from_x.py | 60 +++++++++++-------- src/fenrirscreenreader/fenrirVersion.py | 2 +- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/fenrirscreenreader/commands/commands/import_clipboard_from_x.py b/src/fenrirscreenreader/commands/commands/import_clipboard_from_x.py index 6d5ba34d..2d6224d5 100644 --- a/src/fenrirscreenreader/commands/commands/import_clipboard_from_x.py +++ b/src/fenrirscreenreader/commands/commands/import_clipboard_from_x.py @@ -5,9 +5,10 @@ # By Chrys, Storm Dragon, and contributers. from fenrirscreenreader.core import debug -import subprocess, os -from subprocess import Popen, PIPE import _thread +import pyperclip +import os + class command(): def __init__(self): pass @@ -22,33 +23,40 @@ class command(): _thread.start_new_thread(self._threadRun , ()) def _threadRun(self): try: - # Find xclip path - xclip_paths = ['/usr/bin/xclip', '/bin/xclip', '/usr/local/bin/xclip'] - xclip_path = None - for path in xclip_paths: - if os.path.isfile(path) and os.access(path, os.X_OK): - xclip_path = path - break - if not xclip_path: - self.env['runtime']['outputManager'].presentText('xclip not found in common locations', interrupt=True) - return - xClipboard = '' - for display in range(10): - p = Popen('su ' + self.env['general']['currUser'] + ' -p -c "' + xclip_path + ' -d :' + str(display) + ' -o"', stdout=PIPE, stderr=PIPE, shell=True) - stdout, stderr = p.communicate() - self.env['runtime']['outputManager'].interruptOutput() - stderr = stderr.decode('utf-8') - xClipboard = stdout.decode('utf-8') - if (stderr == ''): - break - if stderr != '': - self.env['runtime']['outputManager'].presentText(stderr , soundIcon='', interrupt=False) + # Remember original display environment variable if it exists + originalDisplay = os.environ.get('DISPLAY', '') + clipboardContent = None + + # Try different display options + for i in range(10): + display = f":{i}" + try: + # Set display environment variable + os.environ['DISPLAY'] = display + # Attempt to get clipboard content + clipboardContent = pyperclip.paste() + # If we get here without exception, we found a working display + if clipboardContent: + break + except Exception: + # Failed for this display, try next one + continue + + # Restore original display setting + if originalDisplay: + os.environ['DISPLAY'] = originalDisplay else: - self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', xClipboard) + os.environ.pop('DISPLAY', None) + + # Process the clipboard content if we found any + if clipboardContent and isinstance(clipboardContent, str): + self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', clipboardContent) self.env['runtime']['outputManager'].presentText('Import to Clipboard', soundIcon='CopyToClipboard', interrupt=True) - self.env['runtime']['outputManager'].presentText(xClipboard, soundIcon='', interrupt=False) + self.env['runtime']['outputManager'].presentText(clipboardContent, soundIcon='', interrupt=False) + else: + self.env['runtime']['outputManager'].presentText('No text found in clipboard or no accessible display', interrupt=True) except Exception as e: - self.env['runtime']['outputManager'].presentText(e , soundIcon='', interrupt=False) + self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False) def setCallback(self, callback): pass diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 925ed497..3af6d94a 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributers. -version = "2025.02.26" +version = "2025.03.02" codeName = "testing" From e76ca9889a1a489b0835f802d179948a4873ce04 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 2 Mar 2025 16:04:38 -0500 Subject: [PATCH 6/8] Same update for export to x clipboard. Now using pyperclip. --- .../commands/export_clipboard_to_x.py | 84 +++++++++---------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/src/fenrirscreenreader/commands/commands/export_clipboard_to_x.py b/src/fenrirscreenreader/commands/commands/export_clipboard_to_x.py index 203024df..e4a7fe49 100644 --- a/src/fenrirscreenreader/commands/commands/export_clipboard_to_x.py +++ b/src/fenrirscreenreader/commands/commands/export_clipboard_to_x.py @@ -5,15 +5,16 @@ # By Chrys, Storm Dragon, and contributers. from fenrirscreenreader.core import debug -import subprocess, os -from subprocess import Popen, PIPE +import os import _thread +import pyperclip class command(): def __init__(self): pass - def initialize(self, environment): + def initialize(self, environment, scriptPath=''): self.env = environment + self.scriptPath = scriptPath def shutdown(self): pass def getDescription(self): @@ -22,56 +23,47 @@ class command(): _thread.start_new_thread(self._threadRun , ()) def _threadRun(self): try: + # Check if clipboard is empty if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'): self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True) return - + + # Get current clipboard content clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory') - user = self.env['general']['currUser'] - - # First try to find xclip in common locations - xclip_paths = [ - '/usr/bin/xclip', - '/bin/xclip', - '/usr/local/bin/xclip' - ] - - xclip_path = None - for path in xclip_paths: - if os.path.isfile(path) and os.access(path, os.X_OK): - xclip_path = path + + # Remember original display environment variable if it exists + originalDisplay = os.environ.get('DISPLAY', '') + success = False + + # Try different display options + for i in range(10): + display = f":{i}" + try: + # Set display environment variable + os.environ['DISPLAY'] = display + # Attempt to set clipboard content + pyperclip.copy(clipboard) + # If we get here without exception, we found a working display + success = True break - - if not xclip_path: - self.env['runtime']['outputManager'].presentText( - 'xclip not found in common locations', - interrupt=True - ) - return - - for display in range(10): - p = Popen( - ['su', user, '-p', '-c', f"{xclip_path} -d :{display} -selection clipboard"], - stdin=PIPE, stdout=PIPE, stderr=PIPE, preexec_fn=os.setpgrp - ) - stdout, stderr = p.communicate(input=clipboard.encode('utf-8')) - - self.env['runtime']['outputManager'].interruptOutput() - - stderr = stderr.decode('utf-8') - stdout = stdout.decode('utf-8') - - if stderr == '': - break - - if stderr != '': - self.env['runtime']['outputManager'].presentText(stderr, soundIcon='', interrupt=False) + except Exception: + # Failed for this display, try next one + continue + + # Restore original display setting + if originalDisplay: + os.environ['DISPLAY'] = originalDisplay else: - self.env['runtime']['outputManager'].presentText('exported to the X session.', interrupt=True) - + os.environ.pop('DISPLAY', None) + + # Notify the user of the result + if success: + self.env['runtime']['outputManager'].presentText(_('exported to the X session.'), interrupt=True) + else: + self.env['runtime']['outputManager'].presentText(_('failed to export to X clipboard. No available display found.'), interrupt=True) + except Exception as e: self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False) - - + def setCallback(self, callback): pass From 09391bfe84eec561836668baca73a286e56e81a2 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 2 Mar 2025 17:24:45 -0500 Subject: [PATCH 7/8] Experimental fix for evdev failures. --- .../inputDriver/evdevDriver.py | 131 +++++++++++------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/src/fenrirscreenreader/inputDriver/evdevDriver.py b/src/fenrirscreenreader/inputDriver/evdevDriver.py index 837d0f29..701a5d9f 100644 --- a/src/fenrirscreenreader/inputDriver/evdevDriver.py +++ b/src/fenrirscreenreader/inputDriver/evdevDriver.py @@ -59,7 +59,7 @@ class driver(inputDriver): self.env['runtime']['processManager'].addCustomEventThread(self.inputWatchdog) self._initialized = True - def plugInputDeviceWatchdogUdev(self,active , eventQueue): + def plugInputDeviceWatchdogUdev(self, active, eventQueue): context = pyudev.Context() monitor = pyudev.Monitor.from_netlink(context) monitor.filter_by(subsystem='input') @@ -72,31 +72,33 @@ class driver(inputDriver): self.env['runtime']['debug'].writeDebugOut('plugInputDeviceWatchdogUdev:' + str(device), debug.debugLevel.INFO) try: try: - if device.name.upper() in ['','SPEAKUP','FENRIR-UINPUT']: + # FIX: Check if attributes exist before accessing them + if hasattr(device, 'name') and device.name and device.name.upper() in ['','SPEAKUP','FENRIR-UINPUT']: ignorePlug = True - if device.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']: + if hasattr(device, 'phys') and device.phys and device.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']: ignorePlug = True - if 'BRLTTY' in device.name.upper(): + if hasattr(device, 'name') and device.name and 'BRLTTY' in device.name.upper(): ignorePlug = True except Exception as e: - self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev CHECK NAME CRASH: " + str(e),debug.debugLevel.ERROR) + self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev CHECK NAME CRASH: " + str(e), debug.debugLevel.ERROR) + if not ignorePlug: virtual = '/sys/devices/virtual/input/' in device.sys_path if device.device_node: validDevices.append({'device': device.device_node, 'virtual': virtual}) except Exception as e: - self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev APPEND CRASH: " + str(e),debug.debugLevel.ERROR) + self.env['runtime']['debug'].writeDebugOut("plugInputDeviceWatchdogUdev APPEND CRASH: " + str(e), debug.debugLevel.ERROR) try: pollTimeout = 1 device = monitor.poll(pollTimeout) - except: + except Exception: device = None ignorePlug = False if validDevices: - eventQueue.put({"Type":fenrirEventType.PlugInputDevice,"Data":validDevices}) + eventQueue.put({"Type": fenrirEventType.PlugInputDevice, "Data": validDevices}) return time.time() - def inputWatchdog(self,active , eventQueue): + def inputWatchdog(self, active, eventQueue): try: while active.value: r, w, x = select(self.iDevices, [], [], 0.8) @@ -111,7 +113,7 @@ class driver(inputDriver): self.removeDevice(fd) while(event): self.env['runtime']['debug'].writeDebugOut('inputWatchdog: EVENT:' + str(event), debug.debugLevel.INFO) - self.env['input']['eventBuffer'].append( [self.iDevices[fd], self.uDevices[fd], event]) + self.env['input']['eventBuffer'].append([self.iDevices[fd], self.uDevices[fd], event]) if event.type == evdev.events.EV_KEY: if not foundKeyInSequence: foundKeyInSequence = True @@ -123,11 +125,11 @@ class driver(inputDriver): if not isinstance(currMapEvent['EventName'], str): event = self.iDevices[fd].read_one() continue - if currMapEvent['EventState'] in [0,1,2]: - eventQueue.put({"Type":fenrirEventType.KeyboardInput,"Data":currMapEvent.copy()}) + if currMapEvent['EventState'] in [0, 1, 2]: + eventQueue.put({"Type": fenrirEventType.KeyboardInput, "Data": currMapEvent.copy()}) eventFired = True else: - if event.type in [2,3]: + if event.type in [2, 3]: foreward = True event = self.iDevices[fd].read_one() @@ -136,7 +138,7 @@ class driver(inputDriver): self.writeEventBuffer() self.clearEventBuffer() except Exception as e: - self.env['runtime']['debug'].writeDebugOut("INPUT WATCHDOG CRASH: "+str(e),debug.debugLevel.ERROR) + self.env['runtime']['debug'].writeDebugOut("INPUT WATCHDOG CRASH: " + str(e), debug.debugLevel.ERROR) def writeEventBuffer(self): if not self._initialized: @@ -146,7 +148,7 @@ class driver(inputDriver): if uDevice: if self.gDevices[iDevice.fd]: self.writeUInput(uDevice, event) - except Exception as e: + except Exception: pass def writeUInput(self, uDevice, event): @@ -156,7 +158,7 @@ class driver(inputDriver): time.sleep(0.0000002) uDevice.syn() - def updateInputDevices(self, newDevices = None, init = False): + def updateInputDevices(self, newDevices=None, init=False): if init: self.removeAllDevices() @@ -191,7 +193,7 @@ class driver(inputDriver): try: with open(deviceFile) as f: pass - except Exception as e: + except Exception: continue # 3 pos absolute # 2 pos relative @@ -201,22 +203,23 @@ class driver(inputDriver): except: continue try: - if currDevice.name.upper() in ['','SPEAKUP','FENRIR-UINPUT']: + # FIX: Check if attributes exist before accessing them + if hasattr(currDevice, 'name') and currDevice.name and currDevice.name.upper() in ['', 'SPEAKUP', 'FENRIR-UINPUT']: continue - if currDevice.phys.upper() in ['','SPEAKUP','FENRIR-UINPUT']: + if hasattr(currDevice, 'phys') and currDevice.phys and currDevice.phys.upper() in ['', 'SPEAKUP', 'FENRIR-UINPUT']: continue - if 'BRLTTY' in currDevice.name.upper(): + if hasattr(currDevice, 'name') and currDevice.name and 'BRLTTY' in currDevice.name.upper(): continue except: pass cap = currDevice.capabilities() - if mode in ['ALL','NOMICE']: + if mode in ['ALL', 'NOMICE']: if eventType.EV_KEY in cap: if 116 in cap[eventType.EV_KEY] and len(cap[eventType.EV_KEY]) < 10: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (has 116):' + currDevice.name,debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('Device Skipped (has 116):' + currDevice.name, debug.debugLevel.INFO) continue if len(cap[eventType.EV_KEY]) < 60: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (< 60 keys):' + currDevice.name,debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('Device Skipped (< 60 keys):' + currDevice.name, debug.debugLevel.INFO) continue if mode == 'ALL': self.addDevice(currDevice) @@ -224,16 +227,20 @@ class driver(inputDriver): elif mode == 'NOMICE': if not ((eventType.EV_REL in cap) or (eventType.EV_ABS in cap)): self.addDevice(currDevice) - self.env['runtime']['debug'].writeDebugOut('Device added (NOMICE):' + self.iDevices[currDevice.fd].name,debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('Device added (NOMICE):' + self.iDevices[currDevice.fd].name, debug.debugLevel.INFO) else: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (NOMICE):' + currDevice.name,debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('Device Skipped (NOMICE):' + currDevice.name, debug.debugLevel.INFO) else: - self.env['runtime']['debug'].writeDebugOut('Device Skipped (no EV_KEY):' + currDevice.name,debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('Device Skipped (no EV_KEY):' + currDevice.name, debug.debugLevel.INFO) elif currDevice.name.upper() in mode.split(','): self.addDevice(currDevice) - self.env['runtime']['debug'].writeDebugOut('Device added (Name):' + self.iDevices[currDevice.fd].name,debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('Device added (Name):' + self.iDevices[currDevice.fd].name, debug.debugLevel.INFO) except Exception as e: - self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile +' ' + currDevice.name +' '+ str(e),debug.debugLevel.INFO) + try: + 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) + except: + self.env['runtime']['debug'].writeDebugOut("Device Skipped (Exception): " + deviceFile + ' ' + str(e), debug.debugLevel.INFO) self.iDeviceNo = len(evdev.list_devices()) self.updateMPiDevicesFD() @@ -247,6 +254,7 @@ class driver(inputDriver): self.iDevicesFD.remove(fd) except: pass + def mapEvent(self, event): if not self._initialized: return None @@ -266,12 +274,12 @@ class driver(inputDriver): mEvent['EventSec'] = event.sec mEvent['EventUsec'] = event.usec mEvent['EventState'] = event.value - mEvent['EventType'] = event.type + mEvent['EventType'] = event.type return mEvent - except Exception as e: + except Exception: return None - def getLedState(self, led = 0): + def getLedState(self, led=0): if not self.hasIDevices(): return False # 0 = Numlock @@ -281,7 +289,8 @@ class driver(inputDriver): if led in dev.leds(): return True return False - def toggleLedState(self, led = 0): + + def toggleLedState(self, led=0): if not self.hasIDevices(): return False ledState = self.getLedState(led) @@ -290,9 +299,10 @@ class driver(inputDriver): # 17 LEDs if 17 in self.iDevices[i].capabilities(): if ledState == 1: - self.iDevices[i].set_led(led , 0) + self.iDevices[i].set_led(led, 0) else: - self.iDevices[i].set_led(led , 1) + self.iDevices[i].set_led(led, 1) + def grabAllDevices(self): if not self._initialized: return True @@ -301,6 +311,7 @@ class driver(inputDriver): if not self.gDevices[fd]: ok = ok and self.grabDevice(fd) return ok + def ungrabAllDevices(self): if not self._initialized: return True @@ -309,6 +320,7 @@ class driver(inputDriver): if self.gDevices[fd]: ok = ok and self.ungrabDevice(fd) return ok + def createUInputDev(self, fd): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): self.uDevices[fd] = None @@ -324,20 +336,21 @@ class driver(inputDriver): self.uDevices[fd] = UInput.from_device(self.iDevices[fd], name='fenrir-uinput', phys='fenrir-uinput') except Exception as e: try: - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: compat fallback: ' + str(e),debug.debugLevel.WARNING) + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: compat fallback: ' + str(e), debug.debugLevel.WARNING) dev = self.iDevices[fd] cap = dev.capabilities() del cap[0] self.uDevices[fd] = UInput( cap, - name = 'fenrir-uinput', - phys = 'fenrir-uinput' + name='fenrir-uinput', + phys='fenrir-uinput' ) except Exception as e: - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: init Uinput not possible: ' + str(e),debug.debugLevel.ERROR) + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: init Uinput not possible: ' + str(e), debug.debugLevel.ERROR) return + def addDevice(self, newDevice): - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device added: ' + str(newDevice.fd) + ' ' +str(newDevice),debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device added: ' + str(newDevice.fd) + ' ' + str(newDevice), debug.debugLevel.INFO) try: self.iDevices[newDevice.fd] = newDevice self.createUInputDev(newDevice.fd) @@ -360,10 +373,13 @@ class driver(inputDriver): def grabDevice(self, fd): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): return True + + # FIX: Handle exception variable scope correctly + grab_error = None try: self.iDevices[fd].grab() self.gDevices[fd] = True - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grab device ('+ str(self.iDevices[fd].name) + ')',debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grab device (' + str(self.iDevices[fd].name) + ')', debug.debugLevel.INFO) # Reset modifier keys on successful grab if self.uDevices[fd]: modifierKeys = [e.KEY_LEFTCTRL, e.KEY_RIGHTCTRL, e.KEY_LEFTALT, e.KEY_RIGHTALT, e.KEY_LEFTSHIFT, e.KEY_RIGHTSHIFT] @@ -371,33 +387,44 @@ class driver(inputDriver): try: self.uDevices[fd].write(e.EV_KEY, key, 0) # 0 = key up self.uDevices[fd].syn() - except Exception as e: - self.env['runtime']['debug'].writeDebugOut('Failed to reset modifier key: ' + str(e), debug.debugLevel.WARNING) + except Exception as mod_error: + self.env['runtime']['debug'].writeDebugOut('Failed to reset modifier key: ' + str(mod_error), debug.debugLevel.WARNING) except IOError: if not self.gDevices[fd]: return False - except Exception as e: - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grabing not possible: ' + str(e),debug.debugLevel.ERROR) + except Exception as ex: + grab_error = ex + + if grab_error: + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: grabing not possible: ' + str(grab_error), debug.debugLevel.ERROR) return False + return True - def ungrabDevice(self,fd): + def ungrabDevice(self, fd): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): return True + + # FIX: Handle exception variable scope correctly + ungrab_error = None try: self.iDevices[fd].ungrab() self.gDevices[fd] = False - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrab device ('+ str(self.iDevices[fd].name) + ')',debug.debugLevel.INFO) + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrab device (' + str(self.iDevices[fd].name) + ')', debug.debugLevel.INFO) except IOError: if self.gDevices[fd]: return False - # self.gDevices[fd] = False - # #self.removeDevice(fd) - except Exception as e: + except Exception as ex: + ungrab_error = ex + + if ungrab_error: + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: ungrabing not possible: ' + str(ungrab_error), debug.debugLevel.ERROR) return False + return True - def removeDevice(self,fd): - self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' +str(self.iDevices[fd]),debug.debugLevel.INFO) + + def removeDevice(self, fd): + self.env['runtime']['debug'].writeDebugOut('InputDriver evdev: device removed: ' + str(fd) + ' ' + str(self.iDevices[fd]), debug.debugLevel.INFO) self.clearEventBuffer() try: self.ungrabDevice(fd) @@ -452,4 +479,4 @@ class driver(inputDriver): self.iDevices.clear() self.uDevices.clear() self.gDevices.clear() - self.iDeviceNo = 0 + self.iDeviceNo = 0 From 1552b962a1229efbf8a4b4bc3cf1172052dea066 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sun, 2 Mar 2025 17:43:01 -0500 Subject: [PATCH 8/8] Updated dependencies. --- requirements.txt | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a164ca82..e50d47f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,6 @@ daemonize>=2.5.0 dbus-python>=1.2.8 pyudev>=0.21.0 pexpect -pyttsx3 +ppyperclip pyte>=0.7.0 rapidfuzz>=2.0.0 diff --git a/setup.py b/setup.py index 448707cc..8b372fce 100755 --- a/setup.py +++ b/setup.py @@ -99,6 +99,7 @@ setup( "evdev>=1.1.2", "daemonize>=2.5.0", "dbus-python>=1.2.8", + "pyperclip", "pyudev>=0.21.0", "rapidfuzz>=2.0.0", "setuptools",