15 Commits

Author SHA1 Message Date
4672592dba Latest merge from testing. 2025-04-28 15:41:14 -04:00
6b84a8e9bc Rename suspendingScreen to ignoreScreen. Requires edit to your settings file, find suspending and change it to ignore. This may break stuff, please let me know. 2025-04-23 01:36:03 -04:00
3212962a5b Adjusted a few of the defaults in punctuation and in settings file. Logs are now in a more screen reader friendly format with timestamps and level coming after the actual information. 2025-04-21 14:04:57 -04:00
9e9cb883e7 Updated requirements.txt. 2025-04-17 01:04:38 -04:00
7a12992b88 latest release. 2025-04-17 00:36:26 -04:00
26a8c8cf86 Added setproctitle to dependencies. 2025-04-17 00:35:02 -04:00
bbfd2790a9 Attempt to fix import/export GUI clipboard for some distros. 2025-04-16 21:50:37 -04:00
6edb743c23 Removed rapidfuzz from dependencies. 2025-04-16 12:14:30 -04:00
a1fcee9a45 Removed the rapidfuzz dependency. There wasn't much of a gain in performance and it did not work on some distros. 2025-04-16 12:06:54 -04:00
4e6e6e2d17 Fixed a couple of bugs in setup.py. 2025-04-16 00:52:44 -04:00
7a87fb51bb Fixed version for master branch. 2025-04-14 20:04:14 -04:00
676c2b07a9 A couple of small improvements to install.sh. 2025-04-14 20:02:00 -04:00
2dda73ac87 Request to be able to use the numpad if numlock is on and only process fenrir commands if numlock is off. This should work, let me know if anything breaks. 2025-04-14 18:57:10 -04:00
f68a1af223 Fixed a typo in requirements.txt. 2025-04-10 05:46:22 -04:00
5ab66f6978 Attempt to fix the bug where Fenrir freezes the computer solid if it manages to start before sound is ready. It shuld at least release the keyboard now. 2025-03-20 02:55:39 -04:00
26 changed files with 180 additions and 125 deletions

View File

@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions) # the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict] [levelDict]
none:===: none:===:
some:===:-$~+*-/\@# some:===:-$~+*-/\@#_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>; most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~ all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~

View File

@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions) # the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict] [levelDict]
none:===: none:===:
some:===:-$~+*-/\@ some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>; most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~ all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~

View File

@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions) # the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict] [levelDict]
none:===: none:===:
some:===:-$~+*-/\@ some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>; most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~ all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~

View File

@ -15,7 +15,7 @@ theme=default
# Sound volume controls how loud the sounds for your selected soundpack are. # Sound volume controls how loud the sounds for your selected soundpack are.
# 0 is quietest, 1.0 is loudest. # 0 is quietest, 1.0 is loudest.
volume=1.0 volume=0.7
# shell commands for generic sound driver # shell commands for generic sound driver
# the folowing variable are substituted # the folowing variable are substituted
@ -92,8 +92,8 @@ fenrirMaxRate=450
driver=vcsaDriver driver=vcsaDriver
encoding=auto encoding=auto
screenUpdateDelay=0.05 screenUpdateDelay=0.05
suspendingScreen= ignoreScreen=
autodetectSuspendingScreen=True autodetectIgnoreScreen=True
[keyboard] [keyboard]
driver=evdevDriver driver=evdevDriver
@ -131,7 +131,7 @@ punctuationProfile=default
punctuationLevel=some punctuationLevel=some
respectPunctuationPause=True respectPunctuationPause=True
newLinePause=True newLinePause=True
numberOfClipboards=10 numberOfClipboards=50
# used path for "export_clipboard_to_file" # used path for "export_clipboard_to_file"
# $user is replaced by username # $user is replaced by username
#clipboardExportPath=/home/$user/fenrirClipboard #clipboardExportPath=/home/$user/fenrirClipboard
@ -190,7 +190,7 @@ enableSettingsRemote=True
enableCommandRemote=True enableCommandRemote=True
[barrier] [barrier]
enabled=True enabled=False
leftBarriers=│└┌─ leftBarriers=│└┌─
rightBarriers=│┘┐─ rightBarriers=│┘┐─

View File

@ -1,12 +1,11 @@
#!/bin/bash #!/usr/bin/env bash
#Basic install script for Fenrir. #Basic install script for Fenrir.
read -p "This will install Fenrir. Press ctrl+C to cancel, or enter to continue." continue read -rp "This will install Fenrir. Press ctrl+C to cancel, or enter to continue."
# Fenrir main application # Fenrir main application
install -m755 -d /opt/fenrirscreenreader install -m755 -d /opt/fenrirscreenreader
cp -af src/* /opt/fenrirscreenreader cp -af src/* /opt/fenrirscreenreader
ln -fs /opt/fenrirscreenreader/fenrir-daemon /usr/bin/fenrir-daemon
ln -fs /opt/fenrirscreenreader/fenrir /usr/bin/fenrir ln -fs /opt/fenrirscreenreader/fenrir /usr/bin/fenrir
# tools # tools
install -m755 -d /usr/share/fenrirscreenreader/tools install -m755 -d /usr/share/fenrirscreenreader/tools
@ -33,8 +32,9 @@ cp -af config/sound/template /usr/share/sounds/fenrirscreenreader/template
# config # config
if [ -f "/etc/fenrirscreenreader/settings/settings.conf" ]; then if [ -f "/etc/fenrirscreenreader/settings/settings.conf" ]; then
echo "Do you want to overwrite your current global settings? (y/n)" echo "Do you want to overwrite your current global settings? (y/n)"
read yn read -r yn
if [ $yn = "Y" -o $yn = "y" ]; then yn="${yn:0:1}"
if [[ "${yn^}" == "Y" ]]; then
mv /etc/fenrirscreenreader/settings/settings.conf /etc/fenrirscreenreader/settings/settings.conf.bak mv /etc/fenrirscreenreader/settings/settings.conf /etc/fenrirscreenreader/settings/settings.conf.bak
echo "Your old settings.conf has been backed up to settings.conf.bak." echo "Your old settings.conf has been backed up to settings.conf.bak."
install -m644 -D "config/settings/settings.conf" /etc/fenrirscreenreader/settings/settings.conf install -m644 -D "config/settings/settings.conf" /etc/fenrirscreenreader/settings/settings.conf

View File

@ -1,8 +1,9 @@
evdev>=1.1.2 daemonize
daemonize>=2.5.0 evdev
dbus-python>=1.2.8
pyudev>=0.21.0
pexpect pexpect
ppyperclip pyenchant
pyte>=0.7.0 pyperclip
rapidfuzz>=2.0.0 pyte
pyudev
pyxdg
setproctitle

View File

@ -101,8 +101,8 @@ setup(
"dbus-python>=1.2.8", "dbus-python>=1.2.8",
"pyperclip", "pyperclip",
"pyudev>=0.21.0", "pyudev>=0.21.0",
"rapidfuzz>=2.0.0",
"setuptools", "setuptools",
"setproctitle",
"pexpect", "pexpect",
"pyte>=0.7.0", "pyte>=0.7.0",
], ],
@ -113,10 +113,10 @@ if not forceSettingsFlag:
# create settings file from example if not exist # create settings file from example if not exist
if not os.path.isfile('/etc/fenrirscreenreader/settings/settings.conf'): if not os.path.isfile('/etc/fenrirscreenreader/settings/settings.conf'):
try: try:
copyfile('/etc/fenrirscreenreader/settings/settings.conf.example', '/etc/fenrirscreenreader/settings/settings.conf') copyfile('config/fenrirscreenreader/settings/settings.conf', '/etc/fenrirscreenreader/settings/settings.conf')
print('create settings file in /etc/fenrirscreenreader/settings/settings.conf') print('create settings file in /etc/fenrirscreenreader/settings/settings.conf')
except: except OSError as e:
pass print(f"Could not copy settings file to destination: {e}")
else: else:
print('settings.conf file found. It is not overwritten automatical') print('settings.conf file found. It is not overwritten automatical')

View File

@ -6,6 +6,7 @@
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
import os import os
import importlib
import _thread import _thread
import pyperclip import pyperclip
@ -42,6 +43,7 @@ class command():
# Set display environment variable # Set display environment variable
os.environ['DISPLAY'] = display os.environ['DISPLAY'] = display
# Attempt to set clipboard content # Attempt to set clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
pyperclip.copy(clipboard) pyperclip.copy(clipboard)
# If we get here without exception, we found a working display # If we get here without exception, we found a working display
success = True success = True

View File

@ -5,6 +5,7 @@
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
import importlib
import _thread import _thread
import pyperclip import pyperclip
import os import os
@ -34,6 +35,7 @@ class command():
# Set display environment variable # Set display environment variable
os.environ['DISPLAY'] = display os.environ['DISPLAY'] = display
# Attempt to get clipboard content # Attempt to get clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
clipboardContent = pyperclip.paste() clipboardContent = pyperclip.paste()
# If we get here without exception, we found a working display # If we get here without exception, we found a working display
if clipboardContent: if clipboardContent:

View File

@ -105,7 +105,7 @@ class attributeManager():
cursorPos = cursor.copy() cursorPos = cursor.copy()
try: try:
attribute = self.getAttributeByXY( cursorPos['x'], cursorPos['y']) attribute = self.getAttributeByXY( cursorPos['x'], cursorPos['y'])
if update: if update:
self.setLastCursorAttribute(attribute) self.setLastCursorAttribute(attribute)
if not self.isLastCursorAttributeChange(): if not self.isLastCursorAttributeChange():
@ -155,13 +155,13 @@ class attributeManager():
attributeFormatString = attributeFormatString.replace('fenrirFGColor', _(attribute[0])) attributeFormatString = attributeFormatString.replace('fenrirFGColor', _(attribute[0]))
except Exception as e: except Exception as e:
attributeFormatString = attributeFormatString.replace('fenrirFGColor', '') attributeFormatString = attributeFormatString.replace('fenrirFGColor', '')
# 1 BG color (name) # 1 BG color (name)
try: try:
attributeFormatString = attributeFormatString.replace('fenrirBGColor', _(attribute[1])) attributeFormatString = attributeFormatString.replace('fenrirBGColor', _(attribute[1]))
except Exception as e: except Exception as e:
attributeFormatString = attributeFormatString.replace('fenrirBGColor', '') attributeFormatString = attributeFormatString.replace('fenrirBGColor', '')
# 2 bold (True/ False) # 2 bold (True/ False)
try: try:
if attribute[2]: if attribute[2]:
@ -169,7 +169,7 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirBold', '') attributeFormatString = attributeFormatString.replace('fenrirBold', '')
# 3 italics (True/ False) # 3 italics (True/ False)
try: try:
if attribute[3]: if attribute[3]:
@ -177,7 +177,7 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirItalics', '') attributeFormatString = attributeFormatString.replace('fenrirItalics', '')
# 4 underline (True/ False) # 4 underline (True/ False)
try: try:
if attribute[4]: if attribute[4]:
@ -185,7 +185,7 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirUnderline', '') attributeFormatString = attributeFormatString.replace('fenrirUnderline', '')
# 5 strikethrough (True/ False) # 5 strikethrough (True/ False)
try: try:
if attribute[5]: if attribute[5]:
@ -193,7 +193,7 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirStrikethrough', '') attributeFormatString = attributeFormatString.replace('fenrirStrikethrough', '')
# 6 reverse (True/ False) # 6 reverse (True/ False)
try: try:
if attribute[6]: if attribute[6]:
@ -201,7 +201,7 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirReverse', '') attributeFormatString = attributeFormatString.replace('fenrirReverse', '')
# 7 blink (True/ False) # 7 blink (True/ False)
try: try:
if attribute[7]: if attribute[7]:
@ -209,7 +209,7 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirBlink', '') attributeFormatString = attributeFormatString.replace('fenrirBlink', '')
# 8 font size (int/ string) # 8 font size (int/ string)
try: try:
try: try:
@ -223,14 +223,14 @@ class attributeManager():
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirFontSize', _('default')) attributeFormatString = attributeFormatString.replace('fenrirFontSize', _('default'))
# 9 font family (string) # 9 font family (string)
try: try:
attributeFormatString = attributeFormatString.replace('fenrirFont', attribute[9]) attributeFormatString = attributeFormatString.replace('fenrirFont', attribute[9])
except Exception as e: except Exception as e:
pass pass
attributeFormatString = attributeFormatString.replace('fenrirFont', _('default')) attributeFormatString = attributeFormatString.replace('fenrirFont', _('default'))
return attributeFormatString return attributeFormatString
def trackHighlights(self): def trackHighlights(self):
result = '' result = ''
@ -287,4 +287,4 @@ class attributeManager():
useful = True useful = True
return useful return useful

View File

@ -18,7 +18,7 @@ class barrierManager():
def updateBarrierChange(self, isBarrier): def updateBarrierChange(self, isBarrier):
self.prefIsBarrier = self.currIsBarrier self.prefIsBarrier = self.currIsBarrier
self.currIsBarrier = isBarrier self.currIsBarrier = isBarrier
def resetBarrierChange(self): def resetBarrierChange(self):
self.currIsBarrier = False self.currIsBarrier = False
self.prefIsBarrier = False self.prefIsBarrier = False
@ -38,7 +38,7 @@ class barrierManager():
self.env['runtime']['outputManager'].playSoundIcon(soundIcon='BarrierStart', interrupt=doInterrupt) self.env['runtime']['outputManager'].playSoundIcon(soundIcon='BarrierStart', interrupt=doInterrupt)
else: else:
self.env['runtime']['outputManager'].playSoundIcon(soundIcon='BarrierEnd', interrupt=doInterrupt) self.env['runtime']['outputManager'].playSoundIcon(soundIcon='BarrierEnd', interrupt=doInterrupt)
if not isBarrier: if not isBarrier:
sayLine = '' sayLine = ''
return isBarrier, sayLine return isBarrier, sayLine

View File

@ -27,7 +27,7 @@ class commandManager():
# scripts for scriptKey # scripts for scriptKey
self.env['runtime']['commandManager'].loadScriptCommands() self.env['runtime']['commandManager'].loadScriptCommands()
def shutdown(self): def shutdown(self):
for commandFolder in self.env['general']['commandFolderList']: for commandFolder in self.env['general']['commandFolderList']:
self.env['runtime']['commandManager'].shutdownCommands(commandFolder) self.env['runtime']['commandManager'].shutdownCommands(commandFolder)
@ -99,7 +99,7 @@ class commandManager():
self.env['runtime']['debug'].writeDebugOut("loadCommands: Loading command:" + command ,debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("loadCommands: Loading command:" + command ,debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)
continue continue
def loadScriptCommands(self, section='commands', scriptPath=''): def loadScriptCommands(self, section='commands', scriptPath=''):
if scriptPath =='': if scriptPath =='':
scriptPath = self.env['runtime']['settingsManager'].getSetting('general', 'scriptPath') scriptPath = self.env['runtime']['settingsManager'].getSetting('general', 'scriptPath')
@ -165,7 +165,7 @@ class commandManager():
if section not in self.env['commands']: if section not in self.env['commands']:
self.env['runtime']['debug'].writeDebugOut("shutdownCommands: section not found:" + section, debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut("shutdownCommands: section not found:" + section, debug.debugLevel.WARNING)
return return
for command in sorted(self.env['commands'][section]): for command in sorted(self.env['commands'][section]):
try: try:
self.env['commands'][section][command].shutdown() self.env['commands'][section][command].shutdown()
@ -176,7 +176,7 @@ class commandManager():
continue continue
def executeSwitchTrigger(self, trigger, unLoadScript, loadScript): def executeSwitchTrigger(self, trigger, unLoadScript, loadScript):
if self.env['runtime']['screenManager'].isSuspendingScreen(): if self.env['runtime']['screenManager'].isIgnoredScreen():
return return
#unload #unload
oldScript = unLoadScript oldScript = unLoadScript
@ -199,7 +199,7 @@ class commandManager():
def executeDefaultTrigger(self, trigger, force=False): def executeDefaultTrigger(self, trigger, force=False):
if not force: if not force:
if self.env['runtime']['screenManager'].isSuspendingScreen(): if self.env['runtime']['screenManager'].isIgnoredScreen():
return return
for command in sorted(self.env['commands'][trigger]): for command in sorted(self.env['commands'][trigger]):
if self.commandExists(command, trigger): if self.commandExists(command, trigger):
@ -214,7 +214,7 @@ class commandManager():
self.env['runtime']['debug'].writeDebugOut("Executing trigger:" + trigger + "." + command + str(e) ,debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("Executing trigger:" + trigger + "." + command + str(e) ,debug.debugLevel.ERROR)
def executeCommand(self, command, section = 'commands'): def executeCommand(self, command, section = 'commands'):
if self.env['runtime']['screenManager'].isSuspendingScreen(): if self.env['runtime']['screenManager'].isIgnoredScreen():
return return
if self.commandExists(command, section): if self.commandExists(command, section):
try: try:
@ -228,7 +228,7 @@ class commandManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("Executing command:" + section + "." + command +' ' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("Executing command:" + section + "." + command +' ' + str(e),debug.debugLevel.ERROR)
def runCommand(self, command, section = 'commands'): def runCommand(self, command, section = 'commands'):
if self.commandExists(command, section): if self.commandExists(command, section):
try: try:
@ -237,7 +237,7 @@ class commandManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("runCommand command:" + section + "." + command +' ' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("runCommand command:" + section + "." + command +' ' + str(e),debug.debugLevel.ERROR)
self.env['commandInfo']['lastCommandExecutionTime'] = time.time() self.env['commandInfo']['lastCommandExecutionTime'] = time.time()
def getCommandDescription(self, command, section = 'commands'): def getCommandDescription(self, command, section = 'commands'):
if self.commandExists(command, section): if self.commandExists(command, section):
try: try:
@ -245,7 +245,7 @@ class commandManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('commandManager.getCommandDescription:' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('commandManager.getCommandDescription:' + str(e),debug.debugLevel.ERROR)
self.env['commandInfo']['lastCommandExecutionTime'] = time.time() self.env['commandInfo']['lastCommandExecutionTime'] = time.time()
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])

View File

@ -11,6 +11,14 @@ class cursorManager():
pass pass
def initialize(self, environment): def initialize(self, environment):
self.env = environment self.env = environment
def shouldProcessNumpadCommands(self):
"""
Check if numpad commands should be processed based on numlock state
Return True if numlock is OFF (commands should work)
Return False if numlock is ON (let keys type numbers)
"""
# Return False if numlock is ON
return not self.env['input']['newNumLock']
def shutdown(self): def shutdown(self):
pass pass
def clearMarks(self): def clearMarks(self):
@ -47,7 +55,7 @@ class cursorManager():
return return
self.env['screen']['oldCursorReview'] = None self.env['screen']['oldCursorReview'] = None
self.env['screen']['newCursorReview'] = None self.env['screen']['newCursorReview'] = None
def isCursorHorizontalMove(self): def isCursorHorizontalMove(self):
return self.env['screen']['newCursor']['x'] != self.env['screen']['oldCursor']['x'] return self.env['screen']['newCursor']['x'] != self.env['screen']['oldCursor']['x']
@ -56,7 +64,7 @@ class cursorManager():
def isReviewMode(self): def isReviewMode(self):
return self.env['screen']['newCursorReview'] != None return self.env['screen']['newCursorReview'] != None
def enterReviewModeCurrTextCursor(self, overwrite=False): def enterReviewModeCurrTextCursor(self, overwrite=False):
if self.isReviewMode() and not overwrite: if self.isReviewMode() and not overwrite:
return return
@ -73,7 +81,7 @@ class cursorManager():
self.env['screen']['oldCursorReview'] = self.env['screen']['newCursorReview'] self.env['screen']['oldCursorReview'] = self.env['screen']['newCursorReview']
self.env['screen']['newCursorReview']['x'] = x self.env['screen']['newCursorReview']['x'] = x
self.env['screen']['newCursorReview']['y'] = y self.env['screen']['newCursorReview']['y'] = y
def isApplicationWindowSet(self): def isApplicationWindowSet(self):
try: try:
currApp = self.env['runtime']['applicationManager'].getCurrentApplication() currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
@ -108,7 +116,7 @@ class cursorManager():
currApp = self.env['runtime']['applicationManager'].getCurrentApplication() currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
self.env['commandBuffer']['windowArea'][currApp] = {} self.env['commandBuffer']['windowArea'][currApp] = {}
if x1 * y1 <= \ if x1 * y1 <= \
x2 * y2: x2 * y2:
self.env['commandBuffer']['windowArea'][currApp]['1'] = {'x':x1, 'y':y1} self.env['commandBuffer']['windowArea'][currApp]['1'] = {'x':x1, 'y':y1}

View File

@ -36,14 +36,14 @@ class debugManager():
except Exception as e: except Exception as e:
print(e) print(e)
def writeDebugOut(self, text, level = debug.debugLevel.DEACTIVE, onAnyLevel=False): def writeDebugOut(self, text, level = debug.debugLevel.DEACTIVE, onAnyLevel=False):
mode = self.env['runtime']['settingsManager'].getSetting('general','debugMode') mode = self.env['runtime']['settingsManager'].getSetting('general','debugMode')
if mode == '': if mode == '':
mode = 'FILE' mode = 'FILE'
mode = mode.upper().split(',') mode = mode.upper().split(',')
fileMode = 'FILE' in mode fileMode = 'FILE' in mode
printMode = 'PRINT' in mode printMode = 'PRINT' in mode
if (self.env['runtime']['settingsManager'].getSettingAsInt('general','debugLevel') < int(level)) and \ if (self.env['runtime']['settingsManager'].getSettingAsInt('general','debugLevel') < int(level)) and \
not (onAnyLevel and self.env['runtime']['settingsManager'].getSettingAsInt('general','debugLevel') > int(debug.debugLevel.DEACTIVE)) : not (onAnyLevel and self.env['runtime']['settingsManager'].getSettingAsInt('general','debugLevel') > int(debug.debugLevel.DEACTIVE)) :
if self._fileOpened: if self._fileOpened:
@ -52,12 +52,15 @@ class debugManager():
else: else:
if not self._fileOpened and fileMode: if not self._fileOpened and fileMode:
self.openDebugFile() self.openDebugFile()
timestamp = str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f'))
if onAnyLevel: if onAnyLevel:
msg = 'ANY '+ str(level) + ' ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')) levelInfo = 'INFO ANY'
else: else:
msg = str(level) +' ' + str(datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') levelInfo = str(level)
)
msg += ': ' + text # Changed order: text comes first, then level and timestamp
msg = text + ' - ' + levelInfo + ' ' + timestamp
if printMode: if printMode:
print(msg) print(msg)
if fileMode: if fileMode:

View File

@ -21,7 +21,7 @@ class eventManager():
self.env = environment self.env = environment
def shutdown(self): def shutdown(self):
self.cleanEventQueue() self.cleanEventQueue()
def proceedEventLoop(self): def proceedEventLoop(self):
event = self._eventQueue.get() event = self._eventQueue.get()
st = time.time() st = time.time()

View File

@ -23,11 +23,11 @@ class fenrirManager():
raise RuntimeError('Cannot Initialize. Maybe the configfile is not available or not parseable') raise RuntimeError('Cannot Initialize. Maybe the configfile is not available or not parseable')
except RuntimeError: except RuntimeError:
raise raise
self.environment['runtime']['outputManager'].presentText(_("Start Fenrir"), soundIcon='ScreenReaderOn', interrupt=True) self.environment['runtime']['outputManager'].presentText(_("Start Fenrir"), soundIcon='ScreenReaderOn', interrupt=True)
signal.signal(signal.SIGINT, self.captureSignal) signal.signal(signal.SIGINT, self.captureSignal)
signal.signal(signal.SIGTERM, self.captureSignal) signal.signal(signal.SIGTERM, self.captureSignal)
self.isInitialized = True self.isInitialized = True
self.modifierInput = False self.modifierInput = False
self.singleKeyCommand = False self.singleKeyCommand = False
@ -42,10 +42,10 @@ class fenrirManager():
def handleInput(self, event): def handleInput(self, event):
self.environment['runtime']['debug'].writeDebugOut('DEBUG INPUT fenrirMan:' + str(event), debug.debugLevel.INFO) self.environment['runtime']['debug'].writeDebugOut('DEBUG INPUT fenrirMan:' + str(event), debug.debugLevel.INFO)
if not event['Data']: if not event['Data']:
event['Data'] = self.environment['runtime']['inputManager'].getInputEvent() event['Data'] = self.environment['runtime']['inputManager'].getInputEvent()
if event['Data']: if event['Data']:
event['Data']['EventName'] = self.environment['runtime']['inputManager'].convertEventName(event['Data']['EventName']) event['Data']['EventName'] = self.environment['runtime']['inputManager'].convertEventName(event['Data']['EventName'])
self.environment['runtime']['inputManager'].handleInputEvent(event['Data']) self.environment['runtime']['inputManager'].handleInputEvent(event['Data'])
@ -54,8 +54,8 @@ class fenrirManager():
if self.environment['runtime']['inputManager'].noKeyPressed(): if self.environment['runtime']['inputManager'].noKeyPressed():
self.environment['runtime']['inputManager'].clearLastDeepInput() self.environment['runtime']['inputManager'].clearLastDeepInput()
if self.environment['runtime']['screenManager'].isSuspendingScreen(): if self.environment['runtime']['screenManager'].isIgnoredScreen():
self.environment['runtime']['inputManager'].writeEventBuffer() self.environment['runtime']['inputManager'].writeEventBuffer()
else: else:
if self.environment['runtime']['helpManager'].isTutorialMode(): if self.environment['runtime']['helpManager'].isTutorialMode():
@ -74,7 +74,7 @@ class fenrirManager():
self.environment['runtime']['inputManager'].clearEventBuffer() self.environment['runtime']['inputManager'].clearEventBuffer()
else: else:
self.environment['runtime']['inputManager'].writeEventBuffer() self.environment['runtime']['inputManager'].writeEventBuffer()
if self.environment['runtime']['inputManager'].noKeyPressed(): if self.environment['runtime']['inputManager'].noKeyPressed():
self.modifierInput = False self.modifierInput = False
self.singleKeyCommand = False self.singleKeyCommand = False
@ -83,7 +83,7 @@ class fenrirManager():
if self.environment['input']['keyForeward'] > 0: if self.environment['input']['keyForeward'] > 0:
self.environment['input']['keyForeward'] -= 1 self.environment['input']['keyForeward'] -= 1
self.environment['runtime']['commandManager'].executeDefaultTrigger('onKeyInput') self.environment['runtime']['commandManager'].executeDefaultTrigger('onKeyInput')
def handleByteInput(self, event): def handleByteInput(self, event):
@ -124,14 +124,14 @@ class fenrirManager():
def handleScreenUpdate(self, event): def handleScreenUpdate(self, event):
self.environment['runtime']['screenManager'].handleScreenUpdate(event['Data']) self.environment['runtime']['screenManager'].handleScreenUpdate(event['Data'])
if time.time() - self.environment['runtime']['inputManager'].getLastInputTime() >= 0.3: if time.time() - self.environment['runtime']['inputManager'].getLastInputTime() >= 0.3:
self.environment['runtime']['inputManager'].clearLastDeepInput() self.environment['runtime']['inputManager'].clearLastDeepInput()
if (self.environment['runtime']['cursorManager'].isCursorVerticalMove() or if (self.environment['runtime']['cursorManager'].isCursorVerticalMove() or
self.environment['runtime']['cursorManager'].isCursorHorizontalMove()): self.environment['runtime']['cursorManager'].isCursorHorizontalMove()):
self.environment['runtime']['commandManager'].executeDefaultTrigger('onCursorChange') self.environment['runtime']['commandManager'].executeDefaultTrigger('onCursorChange')
self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenUpdate') self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenUpdate')
self.environment['runtime']['inputManager'].clearLastDeepInput() self.environment['runtime']['inputManager'].clearLastDeepInput()
@ -150,17 +150,17 @@ class fenrirManager():
def detectShortcutCommand(self): def detectShortcutCommand(self):
if self.environment['input']['keyForeward'] > 0: if self.environment['input']['keyForeward'] > 0:
return return
if len(self.environment['input']['prevInput']) > len(self.environment['input']['currInput']): if len(self.environment['input']['prevInput']) > len(self.environment['input']['currInput']):
return return
if self.environment['runtime']['inputManager'].isKeyPress(): if self.environment['runtime']['inputManager'].isKeyPress():
self.modifierInput = self.environment['runtime']['inputManager'].currKeyIsModifier() self.modifierInput = self.environment['runtime']['inputManager'].currKeyIsModifier()
else: else:
if not self.environment['runtime']['inputManager'].noKeyPressed(): if not self.environment['runtime']['inputManager'].noKeyPressed():
if self.singleKeyCommand: if self.singleKeyCommand:
self.singleKeyCommand = len(self.environment['input']['currInput']) == 1 self.singleKeyCommand = len(self.environment['input']['currInput']) == 1
if not(self.singleKeyCommand and self.environment['runtime']['inputManager'].noKeyPressed()): if not(self.singleKeyCommand and self.environment['runtime']['inputManager'].noKeyPressed()):
currentShortcut = self.environment['runtime']['inputManager'].getCurrShortcut() currentShortcut = self.environment['runtime']['inputManager'].getCurrShortcut()
self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(currentShortcut) self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(currentShortcut)
@ -220,7 +220,7 @@ class fenrirManager():
self.environment['runtime']['outputManager'].presentText(_("Quit Fenrir"), soundIcon='ScreenReaderOff', interrupt=True) self.environment['runtime']['outputManager'].presentText(_("Quit Fenrir"), soundIcon='ScreenReaderOff', interrupt=True)
self.environment['runtime']['eventManager'].cleanEventQueue() self.environment['runtime']['eventManager'].cleanEventQueue()
time.sleep(0.6) time.sleep(0.6)
for currentManager in self.environment['general']['managerList']: for currentManager in self.environment['general']['managerList']:
if self.environment['runtime'][currentManager]: if self.environment['runtime'][currentManager]:
self.environment['runtime'][currentManager].shutdown() self.environment['runtime'][currentManager].shutdown()

View File

@ -42,6 +42,25 @@ class inputDriver():
if not self._initialized: if not self._initialized:
return True return True
return True return True
def forceUngrab(self):
"""Emergency method to release grabbed devices in case of failure"""
if not self._initialized:
return True
try:
# Try standard ungrab first
return self.ungrabAllDevices()
except Exception as e:
# Just log the failure and inform the user
if hasattr(self, 'env') and 'runtime' in self.env and 'debug' in self.env['runtime']:
self.env['runtime']['debug'].writeDebugOut(
f"Emergency device release failed: {str(e)}",
debug.debugLevel.ERROR
)
else:
print(f"Emergency device release failed: {str(e)}")
return False
def hasIDevices(self): def hasIDevices(self):
if not self._initialized: if not self._initialized:
return False return False

View File

@ -49,6 +49,7 @@ class inputManager():
return event return event
def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True): def setExecuteDeviceGrab(self, newExecuteDeviceGrab = True):
self.executeDeviceGrab = newExecuteDeviceGrab self.executeDeviceGrab = newExecuteDeviceGrab
def handleDeviceGrab(self, force = False): def handleDeviceGrab(self, force = False):
if force: if force:
self.setExecuteDeviceGrab() self.setExecuteDeviceGrab()
@ -61,17 +62,38 @@ class inputManager():
if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'): if not self.env['runtime']['settingsManager'].getSettingAsBool('keyboard', 'grabDevices'):
self.executeDeviceGrab = False self.executeDeviceGrab = False
return return
# Add maximum retries to prevent infinite loops
maxRetries = 5
retryCount = 0
grabTimeout = 3 # Timeout in seconds
startTime = time.time()
if self.env['runtime']['screenManager'].getCurrScreenIgnored(): if self.env['runtime']['screenManager'].getCurrScreenIgnored():
while not self.ungrabAllDevices(): while not self.ungrabAllDevices():
retryCount += 1
if retryCount >= maxRetries or (time.time() - startTime) > grabTimeout:
self.env['runtime']['debug'].writeDebugOut("Failed to ungrab devices after multiple attempts", debug.debugLevel.ERROR)
# Force a release of devices if possible through alternative means
try:
self.env['runtime']['inputDriver'].forceUngrab()
except:
pass
break
time.sleep(0.25) time.sleep(0.25)
self.env['runtime']['debug'].writeDebugOut("retry ungrabAllDevices " ,debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut(f"retry ungrabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut("All devices ungrabbed" ,debug.debugLevel.INFO)
else: else:
while not self.grabAllDevices(): while not self.grabAllDevices():
retryCount += 1
if retryCount >= maxRetries or (time.time() - startTime) > grabTimeout:
self.env['runtime']['debug'].writeDebugOut("Failed to grab devices after multiple attempts", debug.debugLevel.ERROR)
# Continue without grabbing input - limited functionality but not locked
break
time.sleep(0.25) time.sleep(0.25)
self.env['runtime']['debug'].writeDebugOut("retry grabAllDevices" ,debug.debugLevel.WARNING) self.env['runtime']['debug'].writeDebugOut(f"retry grabAllDevices {retryCount}/{maxRetries}", debug.debugLevel.WARNING)
self.env['runtime']['debug'].writeDebugOut("All devices grabbed" ,debug.debugLevel.INFO)
self.executeDeviceGrab = False self.executeDeviceGrab = False
def sendKeys(self, keyMacro): def sendKeys(self, keyMacro):
for e in keyMacro: for e in keyMacro:
key = '' key = ''
@ -252,17 +274,39 @@ class inputManager():
def getCurrShortcut(self, inputSequence = None): def getCurrShortcut(self, inputSequence = None):
shortcut = [] shortcut = []
shortcut.append(self.env['input']['shortcutRepeat']) shortcut.append(self.env['input']['shortcutRepeat'])
numpadKeys = ['KEY_KP0', 'KEY_KP1', 'KEY_KP2', 'KEY_KP3', 'KEY_KP4',
'KEY_KP5', 'KEY_KP6', 'KEY_KP7', 'KEY_KP8', 'KEY_KP9',
'KEY_KPDOT', 'KEY_KPPLUS', 'KEY_KPMINUS', 'KEY_KPASTERISK',
'KEY_KPSLASH', 'KEY_KPENTER', 'KEY_KPEQUAL']
if inputSequence: if inputSequence:
# Check if any key in the sequence is a numpad key and numlock is ON
# If numlock is ON and any key in the sequence is a numpad key, return an empty shortcut
if not self.env['runtime']['cursorManager'].shouldProcessNumpadCommands():
for key in inputSequence:
if key in numpadKeys:
# Return an empty/invalid shortcut that won't match any command
return "[]"
shortcut.append(inputSequence) shortcut.append(inputSequence)
else: else:
# Same check for current input
if not self.env['runtime']['cursorManager'].shouldProcessNumpadCommands():
for key in self.env['input']['currInput']:
if key in numpadKeys:
# Return an empty/invalid shortcut that won't match any command
return "[]"
shortcut.append(self.env['input']['currInput']) shortcut.append(self.env['input']['currInput'])
if len(self.env['input']['prevInput']) < len(self.env['input']['currInput']): if len(self.env['input']['prevInput']) < len(self.env['input']['currInput']):
if self.env['input']['shortcutRepeat'] > 1 and not self.shortcutExists(str(shortcut)): if self.env['input']['shortcutRepeat'] > 1 and not self.shortcutExists(str(shortcut)):
shortcut = [] shortcut = []
self.env['input']['shortcutRepeat'] = 1 self.env['input']['shortcutRepeat'] = 1
shortcut.append(self.env['input']['shortcutRepeat']) shortcut.append(self.env['input']['shortcutRepeat'])
shortcut.append(self.env['input']['currInput']) shortcut.append(self.env['input']['currInput'])
self.env['runtime']['debug'].writeDebugOut("currShortcut " + str(shortcut) ,debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("currShortcut " + str(shortcut), debug.debugLevel.INFO)
return str(shortcut) return str(shortcut)
def currKeyIsModifier(self): def currKeyIsModifier(self):

View File

@ -22,7 +22,7 @@ class outputManager():
def shutdown(self): def shutdown(self):
self.env['runtime']['settingsManager'].shutdownDriver('soundDriver') self.env['runtime']['settingsManager'].shutdownDriver('soundDriver')
self.env['runtime']['settingsManager'].shutdownDriver('speechDriver') self.env['runtime']['settingsManager'].shutdownDriver('speechDriver')
def presentText(self, text, interrupt=True, soundIcon='', ignorePunctuation=False, announceCapital=False, flush=True): def presentText(self, text, interrupt=True, soundIcon='', ignorePunctuation=False, announceCapital=False, flush=True):
if text == '': if text == '':
return return
@ -58,13 +58,13 @@ class outputManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("setting speech language in outputManager.speakText", debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("setting speech language in outputManager.speakText", debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
try: try:
self.env['runtime']['speechDriver'].setVoice(self.env['runtime']['settingsManager'].getSetting('speech', 'voice')) self.env['runtime']['speechDriver'].setVoice(self.env['runtime']['settingsManager'].getSetting('speech', 'voice'))
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("Error while setting speech voice in outputManager.speakText", debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("Error while setting speech voice in outputManager.speakText", debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
try: try:
if announceCapital: if announceCapital:
self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'capitalPitch')) self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'capitalPitch'))
@ -73,13 +73,13 @@ class outputManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("setting speech pitch in outputManager.speakText", debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("setting speech pitch in outputManager.speakText", debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
try: try:
self.env['runtime']['speechDriver'].setRate(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate')) self.env['runtime']['speechDriver'].setRate(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate'))
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("setting speech rate in outputManager.speakText", debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("setting speech rate in outputManager.speakText", debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
try: try:
self.env['runtime']['speechDriver'].setModule(self.env['runtime']['settingsManager'].getSetting('speech', 'module')) self.env['runtime']['speechDriver'].setModule(self.env['runtime']['settingsManager'].getSetting('speech', 'module'))
except Exception as e: except Exception as e:
@ -91,7 +91,7 @@ class outputManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("setting speech volume in outputManager.speakText ", debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("setting speech volume in outputManager.speakText ", debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
try: try:
if self.env['runtime']['settingsManager'].getSettingAsBool('general', 'newLinePause'): if self.env['runtime']['settingsManager'].getSettingAsBool('general', 'newLinePause'):
cleanText = text.replace('\n', ' , ') cleanText = text.replace('\n', ' , ')

View File

@ -20,7 +20,7 @@ class processManager():
self.addSimpleEventThread(fenrirEventType.HeartBeat, self.heartBeatTimer, multiprocess=True) self.addSimpleEventThread(fenrirEventType.HeartBeat, self.heartBeatTimer, multiprocess=True)
def shutdown(self): def shutdown(self):
self.terminateAllProcesses() self.terminateAllProcesses()
def terminateAllProcesses(self): def terminateAllProcesses(self):
for proc in self._Processes: for proc in self._Processes:
#try: #try:

View File

@ -172,7 +172,7 @@ class remoteManager():
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)
def saveSettings(self, settingConfigPath = None): def saveSettings(self, settingConfigPath = None):
if not settingConfigPath: if not settingConfigPath:
settingConfigPath = self.env['runtime']['settingsManager'].getSettingsFile() settingConfigPath = self.env['runtime']['settingsManager'].getSettingsFile()

View File

@ -7,7 +7,6 @@
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import screen_utils from fenrirscreenreader.utils import screen_utils
import time, os, re, difflib import time, os, re, difflib
from rapidfuzz.distance import Levenshtein
class screenManager(): class screenManager():
def __init__(self): def __init__(self):
@ -60,7 +59,7 @@ class screenManager():
if self.isCurrScreenIgnoredChanged(): if self.isCurrScreenIgnoredChanged():
self.env['runtime']['inputManager'].setExecuteDeviceGrab() self.env['runtime']['inputManager'].setExecuteDeviceGrab()
self.env['runtime']['inputManager'].handleDeviceGrab() self.env['runtime']['inputManager'].handleDeviceGrab()
if not self.isSuspendingScreen(self.env['screen']['newTTY']): if not self.isIgnoredScreen(self.env['screen']['newTTY']):
self.update(eventData, 'onScreenChange') self.update(eventData, 'onScreenChange')
self.env['screen']['lastScreenUpdate'] = time.time() self.env['screen']['lastScreenUpdate'] = time.time()
else: else:
@ -82,8 +81,7 @@ class screenManager():
return self.prevScreenIgnored return self.prevScreenIgnored
def updateScreenIgnored(self): def updateScreenIgnored(self):
self.prevScreenIgnored = self.currScreenIgnored self.prevScreenIgnored = self.currScreenIgnored
self.currScreenIgnored = self.isSuspendingScreen(self.env['screen']['newTTY']) self.currScreenIgnored = self.isIgnoredScreen(self.env['screen']['newTTY'])
def update(self, eventData, trigger='onUpdate'): def update(self, eventData, trigger='onUpdate'):
# set new "old" values # set new "old" values
self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes'] self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes']
@ -146,11 +144,8 @@ class screenManager():
cursorLineEndOffset = cursorLineStart + self.env['screen']['newCursor']['x'] + 3 cursorLineEndOffset = cursorLineStart + self.env['screen']['newCursor']['x'] + 3
oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset] oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset]
newScreenText = self.env['screen']['newContentText'][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) diff = self.differ.compare(oldScreenText, newScreenText)
diffList = list(diff) diffList = list(diff)
typing = True typing = True
tempNewDelta = ''.join(x[2:] for x in diffList if x[0] == '+') tempNewDelta = ''.join(x[2:] for x in diffList if x[0] == '+')
if tempNewDelta.strip() != '': if tempNewDelta.strip() != '':
@ -158,28 +153,9 @@ class screenManager():
diffList = ['+ ' + self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']] +'\n'] diffList = ['+ ' + self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']] +'\n']
typing = False typing = False
else: else:
# For screen changes, use the original differ diff = self.differ.compare(oldScreenText.split('\n'),\
if self.isScreenChange() or trigger == 'onScreenChange': newScreenText.split('\n'))
diff = self.differ.compare(oldScreenText.split('\n'), diffList = list(diff)
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: if not typing:
self.env['screen']['newDelta'] = '\n'.join(x[2:] for x in diffList if x[0] == '+') self.env['screen']['newDelta'] = '\n'.join(x[2:] for x in diffList if x[0] == '+')
@ -198,16 +174,16 @@ class screenManager():
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut('screenManager:update:highlight: ' + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut('screenManager:update:highlight: ' + str(e),debug.debugLevel.ERROR)
def isSuspendingScreen(self, screen = None): def isIgnoredScreen(self, screen = None):
if screen == None: if screen == None:
screen = self.env['screen']['newTTY'] screen = self.env['screen']['newTTY']
ignoreScreens = [] ignoreScreens = []
fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'suspendingScreen') fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'ignoreScreen')
if fixIgnoreScreens != '': if fixIgnoreScreens != '':
ignoreScreens.extend(fixIgnoreScreens.split(',')) ignoreScreens.extend(fixIgnoreScreens.split(','))
if self.env['runtime']['settingsManager'].getSettingAsBool('screen', 'autodetectSuspendingScreen'): if self.env['runtime']['settingsManager'].getSettingAsBool('screen', 'autodetectIgnoreScreen'):
ignoreScreens.extend(self.env['screen']['autoIgnoreScreens']) ignoreScreens.extend(self.env['screen']['autoIgnoreScreens'])
self.env['runtime']['debug'].writeDebugOut('screenManager:isSuspendingScreen ignore:' + str(ignoreScreens) + ' current:'+ str(screen ), debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut('screenManager:isIgnoredScreen ignore:' + str(ignoreScreens) + ' current:'+ str(screen ), debug.debugLevel.INFO)
return (screen in ignoreScreens) return (screen in ignoreScreens)
def isScreenChange(self): def isScreenChange(self):

View File

@ -40,8 +40,8 @@ settingsData = {
'driver': 'vcsaDriver', 'driver': 'vcsaDriver',
'encoding': 'auto', 'encoding': 'auto',
'screenUpdateDelay': 0.1, 'screenUpdateDelay': 0.1,
'suspendingScreen': '', 'ignoreScreen': '',
'autodetectSuspendingScreen': False, 'autodetectIgnoreScreen': False,
}, },
'general':{ 'general':{
'debugLevel': debug.debugLevel.DEACTIVE, 'debugLevel': debug.debugLevel.DEACTIVE,

View File

@ -29,7 +29,7 @@ class speechDriver():
return return
if not queueable: if not queueable:
self.cancel() self.cancel()
def cancel(self): def cancel(self):
if not self._isInitialized: if not self._isInitialized:
return return

View File

@ -34,7 +34,7 @@ class tableManager():
return '' return ''
def setRowColumnSep(self, columnSep = ''): def setRowColumnSep(self, columnSep = ''):
self.rowColumnSep = columnSep self.rowColumnSep = columnSep
def setHeadLine(self, headLine = ''): def setHeadLine(self, headLine = ''):
self.setHeadColumnSep() self.setHeadColumnSep()
self.setRowColumnSep() self.setRowColumnSep()

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.03.02" version = "2025.04.28"
codeName = "master" codeName = "master"