To make Fenrir easier to approach for new developer, start code migration to be pep8 compliant.
This commit is contained in:
@ -6,7 +6,11 @@
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
from fenrirscreenreader.utils import screen_utils
|
||||
import time, os, re, difflib
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
import difflib
|
||||
|
||||
|
||||
class screenManager():
|
||||
def __init__(self):
|
||||
@ -18,42 +22,58 @@ class screenManager():
|
||||
self.colums = None
|
||||
self.rows = None
|
||||
# Compile regex once for better performance
|
||||
self._space_normalize_regex = re.compile(' +')
|
||||
self._space_normalize_regex = re.compile(' +')
|
||||
|
||||
def getRows(self):
|
||||
return self.rows
|
||||
|
||||
def getColumns(self):
|
||||
return self.colums
|
||||
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.env['runtime']['settingsManager'].loadDriver(\
|
||||
self.env['runtime']['settingsManager'].getSetting('screen', 'driver'), 'screenDriver')
|
||||
self.env['runtime']['settingsManager'].loadDriver(
|
||||
self.env['runtime']['settingsManager'].getSetting(
|
||||
'screen', 'driver'), 'screenDriver')
|
||||
self.getCurrScreen()
|
||||
self.getCurrScreen()
|
||||
self.getSessionInformation()
|
||||
self.updateScreenIgnored()
|
||||
self.updateScreenIgnored()
|
||||
|
||||
def resetScreenText(self, screenText):
|
||||
self.prevScreenText = ''
|
||||
self.currScreenText = screenText
|
||||
|
||||
def setScreenText(self, screenText):
|
||||
self.prevScreenText = self.currScreenText
|
||||
self.currScreenText = screenText
|
||||
|
||||
def getScreenText(self):
|
||||
return self.currScreenText
|
||||
|
||||
def getCurrScreen(self):
|
||||
try:
|
||||
self.env['runtime']['screenDriver'].getCurrScreen()
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut('screenManager getCurrScreen: Error getting current screen: ' + str(e), debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
'screenManager getCurrScreen: Error getting current screen: ' + str(e),
|
||||
debug.debugLevel.ERROR)
|
||||
|
||||
def getSessionInformation(self):
|
||||
try:
|
||||
self.env['runtime']['screenDriver'].getSessionInformation()
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut('screenManager getSessionInformation: Error getting session info: ' + str(e), debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
'screenManager getSessionInformation: Error getting session info: ' + str(e),
|
||||
debug.debugLevel.ERROR)
|
||||
|
||||
def shutdown(self):
|
||||
self.env['runtime']['settingsManager'].shutdownDriver('screenDriver')
|
||||
|
||||
def isCurrScreenIgnoredChanged(self):
|
||||
return self.getCurrScreenIgnored() != self.getPrevScreenIgnored()
|
||||
|
||||
def handleScreenChange(self, eventData):
|
||||
self.getCurrScreen()
|
||||
self.getSessionInformation()
|
||||
@ -66,9 +86,10 @@ class screenManager():
|
||||
self.env['screen']['lastScreenUpdate'] = time.time()
|
||||
else:
|
||||
self.env['runtime']['outputManager'].interruptOutput()
|
||||
|
||||
def handleScreenUpdate(self, eventData):
|
||||
self.env['screen']['oldApplication'] = self.env['screen']['newApplication']
|
||||
self.updateScreenIgnored()
|
||||
self.env['screen']['oldApplication'] = self.env['screen']['newApplication']
|
||||
self.updateScreenIgnored()
|
||||
if self.isCurrScreenIgnoredChanged():
|
||||
self.env['runtime']['inputManager'].setExecuteDeviceGrab()
|
||||
self.env['runtime']['inputManager'].handleDeviceGrab()
|
||||
@ -77,13 +98,18 @@ class screenManager():
|
||||
self.env['screen']['lastScreenUpdate'] = time.time()
|
||||
elif self.isCurrScreenIgnoredChanged():
|
||||
self.env['runtime']['outputManager'].interruptOutput()
|
||||
|
||||
def getCurrScreenIgnored(self):
|
||||
return self.currScreenIgnored
|
||||
|
||||
def getPrevScreenIgnored(self):
|
||||
return self.prevScreenIgnored
|
||||
|
||||
def updateScreenIgnored(self):
|
||||
self.prevScreenIgnored = self.currScreenIgnored
|
||||
self.currScreenIgnored = self.isIgnoredScreen(self.env['screen']['newTTY'])
|
||||
self.currScreenIgnored = self.isIgnoredScreen(
|
||||
self.env['screen']['newTTY'])
|
||||
|
||||
def update(self, eventData, trigger='onUpdate'):
|
||||
# set new "old" values
|
||||
self.env['screen']['oldContentBytes'] = self.env['screen']['newContentBytes']
|
||||
@ -94,12 +120,14 @@ class screenManager():
|
||||
self.env['screen']['newContentBytes'] = eventData['bytes']
|
||||
|
||||
# get metadata like cursor or screensize
|
||||
self.env['screen']['lines'] = int( eventData['lines'])
|
||||
self.env['screen']['columns'] = int( eventData['columns'])
|
||||
self.colums = int( eventData['columns'])
|
||||
self.rows = int( eventData['lines'])
|
||||
self.env['screen']['newCursor']['x'] = int( eventData['textCursor']['x'])
|
||||
self.env['screen']['newCursor']['y'] = int( eventData['textCursor']['y'])
|
||||
self.env['screen']['lines'] = int(eventData['lines'])
|
||||
self.env['screen']['columns'] = int(eventData['columns'])
|
||||
self.colums = int(eventData['columns'])
|
||||
self.rows = int(eventData['lines'])
|
||||
self.env['screen']['newCursor']['x'] = int(
|
||||
eventData['textCursor']['x'])
|
||||
self.env['screen']['newCursor']['y'] = int(
|
||||
eventData['textCursor']['y'])
|
||||
self.env['screen']['newTTY'] = eventData['screen']
|
||||
self.env['screen']['newContentText'] = eventData['text']
|
||||
|
||||
@ -107,7 +135,8 @@ class screenManager():
|
||||
if self.isScreenChange():
|
||||
self.env['screen']['oldContentBytes'] = b''
|
||||
self.resetScreenText(eventData['text'])
|
||||
self.env['runtime']['attributeManager'].resetAttributes(eventData['attributes'])
|
||||
self.env['runtime']['attributeManager'].resetAttributes(
|
||||
eventData['attributes'])
|
||||
self.env['runtime']['attributeManager'].resetAttributeCursor()
|
||||
self.env['screen']['oldContentText'] = ''
|
||||
self.env['screen']['oldCursor']['x'] = 0
|
||||
@ -116,7 +145,8 @@ class screenManager():
|
||||
self.env['screen']['oldNegativeDelta'] = ''
|
||||
else:
|
||||
self.setScreenText(eventData['text'])
|
||||
self.env['runtime']['attributeManager'].setAttributes(eventData['attributes'])
|
||||
self.env['runtime']['attributeManager'].setAttributes(
|
||||
eventData['attributes'])
|
||||
# initialize current deltas
|
||||
self.env['screen']['newNegativeDelta'] = ''
|
||||
self.env['screen']['newDelta'] = ''
|
||||
@ -125,148 +155,204 @@ class screenManager():
|
||||
# Diff generation - critical for screen reader functionality
|
||||
# This code detects and categorizes screen content changes to provide appropriate
|
||||
# speech feedback (typing echo vs incoming text vs screen updates)
|
||||
|
||||
# Track whether this appears to be typing (user input) vs other screen changes
|
||||
|
||||
# Track whether this appears to be typing (user input) vs other screen
|
||||
# changes
|
||||
typing = False
|
||||
diffList = []
|
||||
|
||||
if (self.env['screen']['oldContentText'] != self.env['screen']['newContentText']):
|
||||
if (self.env['screen']['oldContentText'] !=
|
||||
self.env['screen']['newContentText']):
|
||||
# Special case: Initial screen content (going from empty to populated)
|
||||
# This handles first screen load or TTY switch scenarios
|
||||
if self.env['screen']['newContentText'] != '' and self.env['screen']['oldContentText'] == '':
|
||||
# Pre-process screen text for comparison - collapse multiple spaces to single space
|
||||
# This normalization prevents spurious diffs from spacing inconsistencies
|
||||
oldScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['oldContentText']))
|
||||
newScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['newContentText']))
|
||||
# This normalization prevents spurious diffs from spacing
|
||||
# inconsistencies
|
||||
oldScreenText = self._space_normalize_regex.sub(
|
||||
' ', self.env['runtime']['screenManager'].getWindowAreaInText(
|
||||
self.env['screen']['oldContentText']))
|
||||
newScreenText = self._space_normalize_regex.sub(
|
||||
' ', self.env['runtime']['screenManager'].getWindowAreaInText(
|
||||
self.env['screen']['newContentText']))
|
||||
if oldScreenText == '' and\
|
||||
newScreenText != '':
|
||||
newScreenText != '':
|
||||
self.env['screen']['newDelta'] = newScreenText
|
||||
else:
|
||||
# Calculate byte positions for the current cursor's line in the flat text buffer
|
||||
# Formula: (line_number * columns) + line_number accounts for newlines
|
||||
# Each line contributes 'columns' chars + 1 newline char
|
||||
cursorLineStart = self.env['screen']['newCursor']['y'] * self.env['screen']['columns'] + self.env['screen']['newCursor']['y']
|
||||
cursorLineEnd = cursorLineStart + self.env['screen']['columns']
|
||||
|
||||
cursorLineStart = self.env['screen']['newCursor']['y'] * \
|
||||
self.env['screen']['columns'] + self.env['screen']['newCursor']['y']
|
||||
cursorLineEnd = cursorLineStart + self.env['screen']['columns']
|
||||
|
||||
# TYPING DETECTION ALGORITHM
|
||||
# Determines if this screen change is likely user typing vs other content changes
|
||||
# All conditions must be met for typing detection:
|
||||
if abs(self.env['screen']['oldCursor']['x'] - self.env['screen']['newCursor']['x']) >= 1 and \
|
||||
self.env['screen']['oldCursor']['y'] == self.env['screen']['newCursor']['y'] and \
|
||||
self.env['screen']['newContentText'][:cursorLineStart] == self.env['screen']['oldContentText'][:cursorLineStart] and \
|
||||
self.env['screen']['newContentText'][cursorLineEnd:] == self.env['screen']['oldContentText'][cursorLineEnd:]:
|
||||
self.env['screen']['oldCursor']['y'] == self.env['screen']['newCursor']['y'] and \
|
||||
self.env['screen']['newContentText'][:cursorLineStart] == self.env['screen']['oldContentText'][:cursorLineStart] and \
|
||||
self.env['screen']['newContentText'][cursorLineEnd:] == self.env['screen']['oldContentText'][cursorLineEnd:]:
|
||||
# Condition 1: Cursor moved horizontally by at least 1 position (typical of typing)
|
||||
# Condition 2: Cursor stayed on same line (typing doesn't usually change lines)
|
||||
# Condition 3: Content BEFORE cursor line is unchanged (text above typing area)
|
||||
# Condition 4: Content AFTER cursor line is unchanged (text below typing area)
|
||||
# Together: only the current line changed, cursor moved horizontally = likely typing
|
||||
|
||||
# Optimize diff calculation for typing by focusing on a small window around cursor
|
||||
# Together: only the current line changed, cursor moved
|
||||
# horizontally = likely typing
|
||||
|
||||
# Optimize diff calculation for typing by focusing on a
|
||||
# small window around cursor
|
||||
cursorLineStartOffset = cursorLineStart
|
||||
cursorLineEndOffset = cursorLineEnd
|
||||
|
||||
|
||||
# Limit analysis window to avoid processing entire long lines
|
||||
# +3 provides safety buffer beyond cursor position to catch edge cases
|
||||
if cursorLineEnd > cursorLineStart + self.env['screen']['newCursor']['x'] + 3:
|
||||
cursorLineEndOffset = cursorLineStart + self.env['screen']['newCursor']['x'] + 3
|
||||
|
||||
# Extract just the relevant text sections for character-level diff
|
||||
oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset]
|
||||
if cursorLineEnd > cursorLineStart + \
|
||||
self.env['screen']['newCursor']['x'] + 3:
|
||||
cursorLineEndOffset = cursorLineStart + \
|
||||
self.env['screen']['newCursor']['x'] + 3
|
||||
|
||||
# Extract just the relevant text sections for
|
||||
# character-level diff
|
||||
oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset]
|
||||
newScreenText = self.env['screen']['newContentText'][cursorLineStartOffset:cursorLineEndOffset]
|
||||
|
||||
# Character-level diff for typing detection (not line-level)
|
||||
diff = self.differ.compare(oldScreenText, newScreenText)
|
||||
|
||||
# Character-level diff for typing detection (not
|
||||
# line-level)
|
||||
diff = self.differ.compare(oldScreenText, newScreenText)
|
||||
diffList = list(diff)
|
||||
typing = True
|
||||
|
||||
# Validate typing assumption by checking if detected changes match cursor movement
|
||||
tempNewDelta = ''.join(x[2:] for x in diffList if x[0] == '+')
|
||||
|
||||
# Validate typing assumption by checking if detected
|
||||
# changes match cursor movement
|
||||
tempNewDelta = ''.join(x[2:]
|
||||
for x in diffList if x[0] == '+')
|
||||
if tempNewDelta.strip() != '':
|
||||
# Compare diff result against expected typing pattern
|
||||
# Expected: characters between old and new cursor positions
|
||||
expectedTyping = ''.join(newScreenText[self.env['screen']['oldCursor']['x']:self.env['screen']['newCursor']['x']].rstrip())
|
||||
|
||||
# If diff doesn't match expected typing pattern, treat as general screen change
|
||||
# Expected: characters between old and new cursor
|
||||
# positions
|
||||
expectedTyping = ''.join(
|
||||
newScreenText[self.env['screen']['oldCursor']['x']:self.env['screen']['newCursor']['x']].rstrip())
|
||||
|
||||
# If diff doesn't match expected typing pattern, treat
|
||||
# as general screen change
|
||||
if tempNewDelta != expectedTyping:
|
||||
# Fallback: treat entire current line as new content
|
||||
diffList = ['+ ' + self.env['screen']['newContentText'].split('\n')[self.env['screen']['newCursor']['y']] +'\n']
|
||||
# Fallback: treat entire current line as new
|
||||
# content
|
||||
diffList = [
|
||||
'+ ' +
|
||||
self.env['screen']['newContentText'].split('\n')[
|
||||
self.env['screen']['newCursor']['y']] +
|
||||
'\n']
|
||||
typing = False
|
||||
else:
|
||||
# GENERAL SCREEN CHANGE DETECTION
|
||||
# Not typing - handle as line-by-line content change
|
||||
# This catches: incoming messages, screen updates, application output, etc.
|
||||
|
||||
# This catches: incoming messages, screen updates,
|
||||
# application output, etc.
|
||||
|
||||
# Pre-process screen text for comparison - collapse multiple spaces to single space
|
||||
# This normalization prevents spurious diffs from spacing inconsistencies
|
||||
oldScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['oldContentText']))
|
||||
newScreenText = self._space_normalize_regex.sub(' ',self.env['runtime']['screenManager'].getWindowAreaInText(self.env['screen']['newContentText']))
|
||||
|
||||
diff = self.differ.compare(oldScreenText.split('\n'),\
|
||||
newScreenText.split('\n'))
|
||||
# This normalization prevents spurious diffs from spacing
|
||||
# inconsistencies
|
||||
oldScreenText = self._space_normalize_regex.sub(
|
||||
' ', self.env['runtime']['screenManager'].getWindowAreaInText(
|
||||
self.env['screen']['oldContentText']))
|
||||
newScreenText = self._space_normalize_regex.sub(
|
||||
' ', self.env['runtime']['screenManager'].getWindowAreaInText(
|
||||
self.env['screen']['newContentText']))
|
||||
|
||||
diff = self.differ.compare(oldScreenText.split('\n'),
|
||||
newScreenText.split('\n'))
|
||||
diffList = list(diff)
|
||||
|
||||
# Extract added and removed content from diff results
|
||||
# Output format depends on whether this was detected as typing or general change
|
||||
# Output format depends on whether this was detected as typing
|
||||
# or general change
|
||||
if not typing:
|
||||
# Line-based changes: join with newlines for proper speech cadence
|
||||
self.env['screen']['newDelta'] = '\n'.join(x[2:] for x in diffList if x[0] == '+')
|
||||
# Line-based changes: join with newlines for proper speech
|
||||
# cadence
|
||||
self.env['screen']['newDelta'] = '\n'.join(
|
||||
x[2:] for x in diffList if x[0] == '+')
|
||||
else:
|
||||
# Character-based changes: no newlines for smooth typing echo
|
||||
self.env['screen']['newDelta'] = ''.join(x[2:] for x in diffList if x[0] == '+')
|
||||
|
||||
# Negative delta (removed content) - used for backspace/delete detection
|
||||
self.env['screen']['newNegativeDelta'] = ''.join(x[2:] for x in diffList if x[0] == '-')
|
||||
# Character-based changes: no newlines for smooth typing
|
||||
# echo
|
||||
self.env['screen']['newDelta'] = ''.join(
|
||||
x[2:] for x in diffList if x[0] == '+')
|
||||
|
||||
# Negative delta (removed content) - used for backspace/delete
|
||||
# detection
|
||||
self.env['screen']['newNegativeDelta'] = ''.join(
|
||||
x[2:] for x in diffList if x[0] == '-')
|
||||
|
||||
# track highlighted
|
||||
try:
|
||||
if self.env['runtime']['attributeManager'].isAttributeChange():
|
||||
if self.env['runtime']['settingsManager'].getSettingAsBool('focus', 'highlight'):
|
||||
attributeDelta, attributeCursor = self.env['runtime']['attributeManager'].trackHighlights()
|
||||
if self.env['runtime']['settingsManager'].getSettingAsBool(
|
||||
'focus', 'highlight'):
|
||||
attributeDelta, attributeCursor = self.env['runtime']['attributeManager'].trackHighlights(
|
||||
)
|
||||
if attributeCursor:
|
||||
self.env['runtime']['attributeManager'].setAttributeCursor(attributeCursor)
|
||||
self.env['runtime']['attributeManager'].setAttributeDelta(attributeDelta)
|
||||
self.env['runtime']['attributeManager'].setAttributeCursor(
|
||||
attributeCursor)
|
||||
self.env['runtime']['attributeManager'].setAttributeDelta(
|
||||
attributeDelta)
|
||||
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 isIgnoredScreen(self, screen = None):
|
||||
if screen == None:
|
||||
def isIgnoredScreen(self, screen=None):
|
||||
if screen is None:
|
||||
screen = self.env['screen']['newTTY']
|
||||
# Check if force all screens flag is set
|
||||
if self.env['runtime'].get('force_all_screens', False):
|
||||
return False
|
||||
ignoreScreens = []
|
||||
fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting('screen', 'ignoreScreen')
|
||||
fixIgnoreScreens = self.env['runtime']['settingsManager'].getSetting(
|
||||
'screen', 'ignoreScreen')
|
||||
if fixIgnoreScreens != '':
|
||||
ignoreScreens.extend(fixIgnoreScreens.split(','))
|
||||
if self.env['runtime']['settingsManager'].getSettingAsBool('screen', 'autodetectIgnoreScreen'):
|
||||
if self.env['runtime']['settingsManager'].getSettingAsBool(
|
||||
'screen', 'autodetectIgnoreScreen'):
|
||||
ignoreScreens.extend(self.env['screen']['autoIgnoreScreens'])
|
||||
self.env['runtime']['debug'].writeDebugOut('screenManager:isIgnoredScreen 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)
|
||||
|
||||
|
||||
def isScreenChange(self):
|
||||
if not self.env['screen']['oldTTY']:
|
||||
return False
|
||||
return self.env['screen']['newTTY'] != self.env['screen']['oldTTY']
|
||||
|
||||
def isDelta(self, ignoreSpace=False):
|
||||
newDelta = self.env['screen']['newDelta']
|
||||
if ignoreSpace:
|
||||
newDelta = newDelta.strip()
|
||||
return newDelta != ''
|
||||
|
||||
def isNegativeDelta(self):
|
||||
return self.env['screen']['newNegativeDelta'] != ''
|
||||
|
||||
def getWindowAreaInText(self, text):
|
||||
if not self.env['runtime']['cursorManager'].isApplicationWindowSet():
|
||||
return text
|
||||
windowText = ''
|
||||
windowList = text.split('\n')
|
||||
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
|
||||
windowList = windowList[self.env['commandBuffer']['windowArea'][currApp]['1']['y']:self.env['commandBuffer']['windowArea'][currApp]['2']['y'] + 1]
|
||||
windowList = windowList[self.env['commandBuffer']['windowArea'][currApp][
|
||||
'1']['y']:self.env['commandBuffer']['windowArea'][currApp]['2']['y'] + 1]
|
||||
for line in windowList:
|
||||
windowText += line[self.env['commandBuffer']['windowArea'][currApp]['1']['x']:self.env['commandBuffer']['windowArea'][currApp]['2']['x'] + 1] + '\n'
|
||||
windowText += line[self.env['commandBuffer']['windowArea'][currApp]['1']['x']
|
||||
:self.env['commandBuffer']['windowArea'][currApp]['2']['x'] + 1] + '\n'
|
||||
return windowText
|
||||
|
||||
def injectTextToScreen(self, text, screen = None):
|
||||
def injectTextToScreen(self, text, screen=None):
|
||||
try:
|
||||
self.env['runtime']['screenDriver'].injectTextToScreen(text, screen)
|
||||
self.env['runtime']['screenDriver'].injectTextToScreen(
|
||||
text, screen)
|
||||
except Exception as e:
|
||||
self.env['runtime']['debug'].writeDebugOut('screenManager:injectTextToScreen ' + str(e),debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(
|
||||
'screenManager:injectTextToScreen ' + str(e), debug.debugLevel.ERROR)
|
||||
|
Reference in New Issue
Block a user