To make Fenrir easier to approach for new developer, start code migration to be pep8 compliant.

This commit is contained in:
Storm Dragon
2025-07-01 22:23:50 -04:00
parent 4bcf82178e
commit 7408951152
345 changed files with 8688 additions and 3852 deletions

View File

@ -5,8 +5,10 @@
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from collections import Counter
class attributeManager():
def __init__(self):
self.currAttributes = None
@ -20,44 +22,59 @@ class attributeManager():
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def setLastCursorAttribute(self, lastCursorAttribute):
self.prevLastCursorAttribute = self.currLastCursorAttribute
self.currLastCursorAttribute = lastCursorAttribute
def resetLastCursorAttribute(self):
self.prevLastCursorAttribute = None
self.currLastCursorAttribute = None
def isLastCursorAttributeChange(self):
if self.prevLastCursorAttribute == None:
if self.prevLastCursorAttribute is None:
return False
return self.prevLastCursorAttribute != self.currLastCursorAttribute
def getCurrAttributeCursor(self):
return self.currAttributeCursor
def isAttributeCursorActive(self):
return self.currAttributeCursor != None
return self.currAttributeCursor is not None
def isAttributeChange(self):
if not self.prevAttributes:
if not self.prevAttributes:
return False
return self.currAttributes != self.prevAttributes
def resetAttributeAll(self):
self.resetAttributeDelta()
self.resetAttributeCursor()
def getAttributeDelta(self):
return self.currAttributeDelta
def resetAttributeDelta(self):
self.currAttributeDelta = ''
def setAttributeDelta(self, currAttributeDelta):
self.currAttributeDelta = currAttributeDelta
def resetAttributeCursor(self):
self.currAttributeCursor = None
self.prefAttributeCursor = None
def setAttributeCursor(self, currAttributeCursor):
self.prefAttributeCursor = self.currAttributeCursor
self.currAttributeCursor = currAttributeCursor.copy()
def resetAttributes(self, currAttributes):
self.prevAttributes = None
self.currAttributes = currAttributes
def setAttributes(self, currAttributes):
self.prevAttributes = self.currAttributes
self.currAttributes = currAttributes.copy()
@ -75,36 +92,43 @@ class attributeManager():
try:
return self.defaultAttributes[0]
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('attributeManager getAttributeByXY: Error accessing default attributes: ' + str(e), debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(
'attributeManager getAttributeByXY: Error accessing default attributes: ' +
str(e),
debug.debugLevel.ERROR)
return None
def appendDefaultAttributes(self, attribute):
if not attribute:
return
if len(attribute) != 10:
return
self.defaultAttributes.append(attribute)
def initDefaultAttributes(self):
self.defaultAttributes = [None]
self.defaultAttributes.append([
'default', # fg
'default', # bg
False, # bold
False, # italics
False, # underscore
False, # strikethrough
False, # reverse
False, # blink
'default', # fontsize
'default' # fontfamily
]) #end attribute
def isDefaultAttribute(self,attribute):
'default', # fg
'default', # bg
False, # bold
False, # italics
False, # underscore
False, # strikethrough
False, # reverse
False, # blink
'default', # fontsize
'default' # fontfamily
]) # end attribute
def isDefaultAttribute(self, attribute):
return attribute in self.defaultAttributes
def hasAttributes(self, cursor, update=True):
if not cursor:
return False
cursorPos = cursor.copy()
try:
attribute = self.getAttributeByXY( cursorPos['x'], cursorPos['y'])
attribute = self.getAttributeByXY(cursorPos['x'], cursorPos['y'])
if update:
self.setLastCursorAttribute(attribute)
@ -118,7 +142,7 @@ class attributeManager():
return False
return True
def formatAttributes(self, attribute, attributeFormatString = ''):
def formatAttributes(self, attribute, attributeFormatString=''):
# "black",
# "red",
# "green",
@ -140,7 +164,8 @@ class attributeManager():
# "fontsieze"
# "fontfamily"
if attributeFormatString == '':
attributeFormatString = self.env['runtime']['settingsManager'].getSetting('general', 'attributeFormatString')
attributeFormatString = self.env['runtime']['settingsManager'].getSetting(
'general', 'attributeFormatString')
if not attributeFormatString:
return ''
if attributeFormatString == '':
@ -152,20 +177,25 @@ class attributeManager():
# 0 FG color (name)
try:
attributeFormatString = attributeFormatString.replace('fenrirFGColor', _(attribute[0]))
attributeFormatString = attributeFormatString.replace(
'fenrirFGColor', _(attribute[0]))
except Exception as e:
attributeFormatString = attributeFormatString.replace('fenrirFGColor', '')
attributeFormatString = attributeFormatString.replace(
'fenrirFGColor', '')
# 1 BG color (name)
try:
attributeFormatString = attributeFormatString.replace('fenrirBGColor', _(attribute[1]))
attributeFormatString = attributeFormatString.replace(
'fenrirBGColor', _(attribute[1]))
except Exception as e:
attributeFormatString = attributeFormatString.replace('fenrirBGColor', '')
attributeFormatString = attributeFormatString.replace(
'fenrirBGColor', '')
# 2 bold (True/ False)
try:
if attribute[2]:
attributeFormatString = attributeFormatString.replace('fenrirBold', _('bold'))
attributeFormatString = attributeFormatString.replace(
'fenrirBold', _('bold'))
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirBold', '')
@ -173,72 +203,94 @@ class attributeManager():
# 3 italics (True/ False)
try:
if attribute[3]:
attributeFormatString = attributeFormatString.replace('fenrirItalics', _('italic'))
attributeFormatString = attributeFormatString.replace(
'fenrirItalics', _('italic'))
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirItalics', '')
attributeFormatString = attributeFormatString.replace(
'fenrirItalics', '')
# 4 underline (True/ False)
try:
if attribute[4]:
attributeFormatString = attributeFormatString.replace('fenrirUnderline', _('underline'))
attributeFormatString = attributeFormatString.replace(
'fenrirUnderline', _('underline'))
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirUnderline', '')
attributeFormatString = attributeFormatString.replace(
'fenrirUnderline', '')
# 5 strikethrough (True/ False)
try:
if attribute[5]:
attributeFormatString = attributeFormatString.replace('fenrirStrikethrough', _('strikethrough'))
attributeFormatString = attributeFormatString.replace(
'fenrirStrikethrough', _('strikethrough'))
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirStrikethrough', '')
attributeFormatString = attributeFormatString.replace(
'fenrirStrikethrough', '')
# 6 reverse (True/ False)
try:
if attribute[6]:
attributeFormatString = attributeFormatString.replace('fenrirReverse', _('reverse'))
attributeFormatString = attributeFormatString.replace(
'fenrirReverse', _('reverse'))
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirReverse', '')
attributeFormatString = attributeFormatString.replace(
'fenrirReverse', '')
# 7 blink (True/ False)
try:
if attribute[7]:
attributeFormatString = attributeFormatString.replace('fenrirBlink', _('blink'))
attributeFormatString = attributeFormatString.replace(
'fenrirBlink', _('blink'))
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirBlink', '')
attributeFormatString = attributeFormatString.replace(
'fenrirBlink', '')
# 8 font size (int/ string)
try:
try:
attributeFormatString = attributeFormatString.replace('fenrirFontSize', int(attribute[8]))
attributeFormatString = attributeFormatString.replace(
'fenrirFontSize', int(attribute[8]))
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('attributeManager formatAttributeToString: Error formatting font size as int: ' + str(e), debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(
'attributeManager formatAttributeToString: Error formatting font size as int: ' +
str(e),
debug.debugLevel.ERROR)
try:
attributeFormatString = attributeFormatString.replace('fenrirFontSize', str(attribute[8]))
attributeFormatString = attributeFormatString.replace(
'fenrirFontSize', str(attribute[8]))
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('attributeManager formatAttributeToString: Error formatting font size as string: ' + str(e), debug.debugLevel.ERROR)
self.env['runtime']['debug'].writeDebugOut(
'attributeManager formatAttributeToString: Error formatting font size as string: ' +
str(e),
debug.debugLevel.ERROR)
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirFontSize', _('default'))
attributeFormatString = attributeFormatString.replace(
'fenrirFontSize', _('default'))
# 9 font family (string)
try:
attributeFormatString = attributeFormatString.replace('fenrirFont', attribute[9])
attributeFormatString = attributeFormatString.replace(
'fenrirFont', attribute[9])
except Exception as e:
pass
attributeFormatString = attributeFormatString.replace('fenrirFont', _('default'))
attributeFormatString = attributeFormatString.replace(
'fenrirFont', _('default'))
return attributeFormatString
def trackHighlights(self):
"""
Detects text with changed attributes (highlighting) between screen updates.
This is crucial for screen readers to announce when text becomes highlighted,
selected, or changes visual emphasis (bold, reverse video, color changes, etc.)
Returns:
tuple: (highlighted_text, cursor_position)
- highlighted_text: string of characters that gained highlighting
@ -246,60 +298,73 @@ class attributeManager():
"""
result = ''
currCursor = None
# Early exit conditions - no attribute comparison possible
if self.prevAttributes == None:
if self.prevAttributes is None:
# First screen load - no previous attributes to compare against
return result, currCursor
return result, currCursor
if self.prevAttributes == self.currAttributes:
# No attribute changes detected
return result, currCursor
if self.currAttributes == None:
return result, currCursor
if self.currAttributes is None:
# Error condition - current attributes missing
return result, currCursor
return result, currCursor
if len(self.currAttributes) == 0:
# Special case for PTY environments with no text content
return result, currCursor
return result, currCursor
# Get current screen text to correlate with attribute changes
text = self.env['runtime']['screenManager'].getScreenText()
textLines = text.split('\n')
# Sanity check: text lines must match attribute array dimensions
if len(textLines) != len(self.currAttributes):
return result, currCursor
return result, currCursor
# Compare attributes line by line, character by character
for line in range(len(self.prevAttributes)):
if self.prevAttributes[line] != self.currAttributes[line]:
# This line has attribute changes - examine each character position
# This line has attribute changes - examine each character
# position
for column in range(len(self.prevAttributes[line])):
if self.prevAttributes[line][column] == self.currAttributes[line][column]:
# No change at this position
continue
# Attribute changed at this position - check if it's worth announcing
if self.isUsefulForTracking(line, column, self.currAttributes, self.prevAttributes):
# First highlighted character becomes cursor position for navigation
# Attribute changed at this position - check if it's worth
# announcing
if self.isUsefulForTracking(
line, column, self.currAttributes, self.prevAttributes):
# First highlighted character becomes cursor position
# for navigation
if not currCursor:
currCursor = {'x': column, 'y': line}
# Accumulate highlighted characters
result += textLines[line][column]
# Add space between lines of highlighted text for speech clarity
# Add space between lines of highlighted text for speech
# clarity
result += ' '
return result, currCursor
def isUsefulForTracking(self, line, column, currAttributes, prevAttributes, attribute=1 , mode = 'zaxe'):
def isUsefulForTracking(
self,
line,
column,
currAttributes,
prevAttributes,
attribute=1,
mode='zaxe'):
"""
Determines if an attribute change at a specific position is worth announcing.
This prevents announcing every minor attribute change and focuses on meaningful
highlighting that indicates user selections, focus changes, or important emphasis.
Args:
line, column: Position of the attribute change
currAttributes, prevAttributes: Current and previous attribute arrays
attribute: Which attribute to examine (1=background color by default)
mode: Detection algorithm ('zaxe', 'default', 'barrier')
Returns:
bool: True if this attribute change should be announced to user
"""
@ -307,37 +372,37 @@ class attributeManager():
if len(currAttributes) <= 3:
return False
if line < 0:
return False
return False
if line > len(currAttributes):
return False
useful = False
if mode == 'default':
# Simple mode: announce any non-default attribute
useful = not self.isDefaultAttribute(currAttributes[line][column])
elif (mode == 'zaxe') or (mode == ''):
# Context-aware mode: only announce attributes that stand out from surroundings
# This prevents announcing entire blocks of highlighted text character by character
# by checking if the attribute differs from adjacent lines
if line == 0:
# Top line: compare against lines below
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and \
(currAttributes[line][column][attribute] != currAttributes[line + 2][column][attribute])
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and (
currAttributes[line][column][attribute] != currAttributes[line + 2][column][attribute])
elif line >= len(prevAttributes):
# Bottom line: compare against lines above
useful = (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute]) and \
(currAttributes[line][column][attribute] != currAttributes[line - 2][column][attribute])
# Bottom line: compare against lines above
useful = (currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute]) and (
currAttributes[line][column][attribute] != currAttributes[line - 2][column][attribute])
else:
# Middle lines: compare against both directions
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and \
(currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute])
useful = (currAttributes[line][column][attribute] != currAttributes[line + 1][column][attribute]) and (
currAttributes[line][column][attribute] != currAttributes[line - 1][column][attribute])
elif mode == 'barrier':
# Barrier mode: future enhancement for detecting screen boundaries/separators
# Barrier mode: future enhancement for detecting screen
# boundaries/separators
useful = True
return useful