Resolved version conflict. I need to automate this somehow.

This commit is contained in:
Storm Dragon 2024-12-09 12:59:55 -05:00
commit f6d3baebc1
28 changed files with 221 additions and 838 deletions

View File

@ -16,7 +16,7 @@ This software is licensed under the LGPL v3.
# Core Requirements # Core Requirements
- python3 >= 3.3 - python3 >= 3.3
- screen, input, speech, sound or braille drivers dependencies see "Features, Drivers, Extras". - screen, input, speech, sound drivers dependencies see "Features, Drivers, Extras".
# Features, Drivers, Extras, Dependencies # Features, Drivers, Extras, Dependencies
@ -59,13 +59,6 @@ This software is licensed under the LGPL v3.
- emacspeak - emacspeak
# Braille Drivers:
1. "BrlttyDriver" braille driver (WIP):
- brltty (configured and running)
- python-brlapi
# Sound Drivers: # Sound Drivers:
1. "genericDriver" (default) sound driver for sound as subprocess: 1. "genericDriver" (default) sound driver for sound as subprocess:
@ -113,7 +106,6 @@ Take care to use drivers from the config matching your installed drivers.
By default it uses: By default it uses:
- sound driver: genericDriver (via sox, could configured in settings.conf) - sound driver: genericDriver (via sox, could configured in settings.conf)
- speech driver: genericDriver (via espeak or espeak-ng, could configured in settings.conf) - speech driver: genericDriver (via espeak or espeak-ng, could configured in settings.conf)
- braille driver: brlttyDriver (WIP)
- input driver: evdevDriver - input driver: evdevDriver

View File

@ -8,7 +8,7 @@ from typing import List, Optional
@dataclass @dataclass
class Dependency: class Dependency:
name: str name: str
depType: str # screen, braille, input, sound, speech, core depType: str # screen, input, sound, speech, core
moduleName: str moduleName: str
checkCommands: Optional[List[str]] = None # Command-line tools to check checkCommands: Optional[List[str]] = None # Command-line tools to check
pythonImports: Optional[List[str]] = None # Python packages to check pythonImports: Optional[List[str]] = None # Python packages to check
@ -60,12 +60,6 @@ dependencyList = [
Dependency('PTY', 'screen', 'ptyDriver', Dependency('PTY', 'screen', 'ptyDriver',
pythonImports=['pyte']), pythonImports=['pyte']),
# Braille drivers
Dependency('DummyBraille', 'braille', 'dummyDriver'),
Dependency('DebugBraille', 'braille', 'debugDriver'),
Dependency('BRLAPI', 'braille', 'brlapiDriver',
pythonImports=['brlapi']),
# Input drivers # Input drivers
Dependency('DummyInput', 'input', 'dummyDriver'), Dependency('DummyInput', 'input', 'dummyDriver'),
Dependency('DebugInput', 'input', 'debugDriver'), Dependency('DebugInput', 'input', 'debugDriver'),
@ -94,7 +88,6 @@ dependencyList = [
defaultModules = { defaultModules = {
'FenrirCore', 'FenrirCore',
'VCSA', 'VCSA',
'DummyBraille',
'Evdev', 'Evdev',
'GenericSpeech', 'GenericSpeech',
'GenericSound' 'GenericSound'
@ -105,7 +98,7 @@ def check_all_dependencies():
availableModules = [] availableModules = []
# Group dependencies by type for organized output # Group dependencies by type for organized output
for depType in ['core', 'screen', 'braille', 'input', 'sound', 'speech']: for depType in ['core', 'screen', 'input', 'sound', 'speech']:
print(f'{depType.upper()} DRIVERS') print(f'{depType.upper()} DRIVERS')
print('-' * 20) print('-' * 20)

View File

@ -91,37 +91,6 @@ fenrirMaxPitch=99
fenrirMinRate=80 fenrirMinRate=80
fenrirMaxRate=450 fenrirMaxRate=450
[braille]
enabled=False
driver=dummyDriver
layout=en
# to what should the flush timeout relate to
# word = flush after (number of words to display) * seconds
# char = flush after (number of chars to display) * seconds
# fix = flush after X seconds
# none = no automatic flush (manual via shortcut)
flushMode=word
# seconds to flush or
# -1 = no automatic flush (manual via shortcut)
flushTimeout=3
# how should the cursor be focused?
# page = if cursor cross the border move to next page and start at beginn
# fixCell = ajust the cursor on an special cell where it is always placed. the display scroll here more smooth.
cursorFocusMode=page
# define the cell on the Braille device where fenrir should scroll and keep the cursor
# 0 = first cell on device
# -1 = last cell on device
# >0 = fix cell number
fixCursorOnCell=-1
#How should the braille follow the focus
# none = no automatic toggle command used
# review = priority to review
# last = follow last used cursor
cursorFollowMode=review
# number of cells in panning (horizontal)
# 0 = display size, >0 number of cells
panSizeHorizontal=0
[screen] [screen]
driver=vcsaDriver driver=vcsaDriver
encoding=auto encoding=auto

View File

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# needs pandoc and php installed # needs pandoc and php installed
# remove old files # remove old files

View File

@ -76,7 +76,7 @@ setup(
# Packages # Packages
packages=find_packages('src/'), packages=find_packages('src/'),
package_dir={'': 'src/'}, package_dir={'': 'src/'},
scripts=['src/fenrir','src/fenrir-daemon'], scripts=['src/fenrir'],
# Include additional files into the package # Include additional files into the package
include_package_data=True, include_package_data=True,
@ -119,5 +119,4 @@ print('once as their user account and once as root to configure Pulseaudio.')
print('Please install the following packages manually:') print('Please install the following packages manually:')
print('- Speech-dispatcher: for the default speech driver') print('- Speech-dispatcher: for the default speech driver')
print('- Espeak: as basic TTS engine') print('- Espeak: as basic TTS engine')
print('- BrlTTY: for Braille')
print('- sox: is a player for the generic sound driver') print('- sox: is a player for the generic sound driver')

View File

@ -2,20 +2,115 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributors.
import os, sys, inspect import os
import sys
import inspect
import argparse
from daemonize import Daemonize
# Get the fenrir installation path
fenrirPath = os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe())))) fenrirPath = os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe()))))
if not fenrirPath in sys.path: if not fenrirPath in sys.path:
sys.path.append(fenrirPath) sys.path.append(fenrirPath)
from fenrirscreenreader.core import fenrirManager from fenrirscreenreader.core import fenrirManager
from fenrirscreenreader import fenrirVersion
def create_argument_parser():
"""Create and return the argument parser for Fenrir"""
argumentParser = argparse.ArgumentParser(
description="Fenrir - A console screen reader for Linux",
formatter_class=argparse.RawDescriptionHelpFormatter
)
argumentParser.add_argument(
'-v', '--version',
action='version',
version=f'Fenrir screen reader version {fenrirVersion.version}-{fenrirVersion.codeName}',
help='Show version information and exit'
)
argumentParser.add_argument(
'-f', '--foreground',
action='store_true',
help='Run Fenrir in the foreground (default: run as daemon)'
)
argumentParser.add_argument(
'-s', '--setting',
metavar='SETTING-FILE',
default='/etc/fenrir/settings/settings.conf',
help='Path to custom settings file'
)
argumentParser.add_argument(
'-o', '--options',
metavar='SECTION#SETTING=VALUE;..',
default='',
help='Override settings file options. Format: SECTION#SETTING=VALUE;... (case sensitive)'
)
argumentParser.add_argument(
'-d', '--debug',
action='store_true',
help='Enable debug mode'
)
argumentParser.add_argument(
'-p', '--print',
action='store_true',
help='Print debug messages to screen'
)
argumentParser.add_argument(
'-e', '--emulated-pty',
action='store_true',
help='Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)'
)
argumentParser.add_argument(
'-E', '--emulated-evdev',
action='store_true',
help='Use PTY emulation with evdev for input (single instance)'
)
return argumentParser
def validate_arguments(cliArgs):
"""Validate command line arguments"""
if cliArgs.options:
for option in cliArgs.options.split(';'):
if option and ('#' not in option or '=' not in option):
return False, f"Invalid option format: {option}\nExpected format: SECTION#SETTING=VALUE"
if cliArgs.emulated_pty and cliArgs.emulated_evdev:
return False, "Cannot use both --emulated-pty and --emulated-evdev simultaneously"
return True, None
def run_fenrir():
"""Main function that runs Fenrir"""
fenrirApp = fenrirManager.fenrirManager(cliArgs)
fenrirApp.proceed()
del fenrirApp
def main(): def main():
app = fenrirManager.fenrirManager() global cliArgs
app.proceed() argumentParser = create_argument_parser()
del app cliArgs = argumentParser.parse_args()
# Validate arguments
isValid, errorMsg = validate_arguments(cliArgs)
if not isValid:
argumentParser.error(errorMsg)
sys.exit(1)
if cliArgs.foreground:
# Run directly in foreground
run_fenrir()
else:
# Run as daemon
pidFile = "/run/fenrir.pid"
daemonProcess = Daemonize(
app="fenrir",
pid=pidFile,
action=run_fenrir,
chdir=fenrirPath
)
daemonProcess.start()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,28 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import os, sys, inspect
fenrirPath = os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe()))))
if not fenrirPath in sys.path:
sys.path.append(fenrirPath)
from fenrirscreenreader.core import fenrirManager
from daemonize import Daemonize
pidFile = "/run/fenrir.pid"
def main():
app = fenrirManager.fenrirManager()
app.proceed()
del app
if __name__ == "__main__":
# for debug in foreground
#daemon = Daemonize(app="fenrir-daemon", pid=pidFile, action=main, foreground=True,chdir=fenrirPath)
daemon = Daemonize(app="fenrir-daemon", pid=pidFile, action=main, chdir=fenrirPath)
daemon.start()

View File

@ -1,28 +0,0 @@
#!/usr/bin/env pypy3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import os, sys, inspect
fenrirPath = os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe()))))
if not fenrirPath in sys.path:
sys.path.append(fenrirPath)
from fenrirscreenreader.core import fenrirManager
from daemonize import Daemonize
pidFile = "/run/fenrir.pid"
def main():
app = fenrirManager.fenrirManager()
app.proceed()
del app
if __name__ == "__main__":
# for debug in foreground
#daemon = Daemonize(app="fenrir-daemon", pid=pidFile, action=main, foreground=True,chdir=os.path.dirname(os.path.realpath(fenrirVersion.__file__)))
daemon = Daemonize(app="fenrir-daemon", pid=pidFile, action=main, chdir=fenrirPath)
daemon.start()

View File

@ -1,21 +0,0 @@
#!/usr/bin/env pypy3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import os, sys, inspect
fenrirPath = os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe()))))
if not fenrirPath in sys.path:
sys.path.append(fenrirPath)
from fenrirscreenreader.core import fenrirManager
def main():
app = fenrirManager.fenrirManager()
app.proceed()
del app
if __name__ == "__main__":
main()

View File

@ -1,66 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.brailleDriver import brailleDriver
class driver(brailleDriver):
def __init__(self):
brailleDriver.__init__(self)
self._brl = None
def initialize(self, environment):
self.env = environment
try:
import brlapi
self._brl = brlapi.Connection()
self._deviceSize = self._brl.displaySize
except Exception as e:
print(e)
self.env['runtime']['debug'].writeDebugOut(str(e),debug.debugLevel.ERROR)
return
self._isInitialized = True
def getDeviceSize(self):
if not self._isInitialized:
return (0,0)
if not self._deviceSize:
return (0,0)
return self._deviceSize
def flush(self):
if not self._isInitialized:
return
try:
self._brl.writeText('',0)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.flush '+str(e),debug.debugLevel.ERROR)
def writeText(self,text):
if not self._isInitialized:
return
try:
self._brl.writeText(text)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('BRAILLE.writeText '+str(e),debug.debugLevel.ERROR)
def connectDevice(self):
self._brl = brlapi.Connection()
def enterScreen(self, screen):
if not self._isInitialized:
return
self._brl.enterTtyMode(int(screen))
def leveScreen(self):
if not self._isInitialized:
return
self._brl.leaveTtyMode()
def shutdown(self):
if not self._isInitialized:
return
self.leveScreen()

View File

@ -1,49 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.brailleDriver import brailleDriver
class driver(brailleDriver):
def __init__(self):
brailleDriver.__init__(self)
def initialize(self, environment):
self.env = environment
self._isInitialized = True
self.deviceSize = (40,0)
print('Braille Debug Driver: Initialized')
def getDeviceSize(self):
if not self._isInitialized:
return (0,0)
print('Braille Debug Driver: getDeviceSize ' + str(self.deviceSize))
return self.deviceSize
def writeText(self,text):
if not self._isInitialized:
return
print('Braille Debug Driver: writeText:' + str(text))
print('Braille Debug Driver: -----------------------------------')
def connectDevice(self):
print('Braille Debug Driver: connectDevice')
def enterScreen(self, screen):
if not self._isInitialized:
return
print('Braille Debug Driver: enterScreen')
def leveScreen(self):
if not self._isInitialized:
return
print('Braille Debug Driver: leveScreen')
def shutdown(self):
if self._isInitialized:
self.leveScreen()
self._isInitialized = False
print('Braille Debug Driver: Shutdown')

View File

@ -1,12 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.brailleDriver import brailleDriver
class driver(brailleDriver):
def __init__(self):
brailleDriver.__init__(self)

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Clear the Braille device if it is displaying a message')
def run(self):
self.env['runtime']['outputManager'].clearFlushTime()
def setCallback(self, callback):
pass

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Move braille view to the left.')
def run(self):
panned = self.env['runtime']['outputManager'].setPanLeft()
def setCallback(self, callback):
pass

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Move braille view to the right.')
def run(self):
panned = self.env['runtime']['outputManager'].setPanRight()
def setCallback(self, callback):
pass

View File

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Set the braille view back to cursor.')
def run(self):
self.env['runtime']['outputManager'].removePanning()
def setCallback(self, callback):
pass

View File

@ -1,27 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Enables and disables Braille output')
def run(self):
if self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
self.env['runtime']['outputManager'].presentText(_('braille disabled'), soundIcon='BrailleOff', interrupt=True)
self.env['runtime']['settingsManager'].setSetting('braille', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled')))
if self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
self.env['runtime']['outputManager'].presentText(_('braille enabled'), soundIcon='BrailleOn', interrupt=True)
def setCallback(self, callback):
pass

View File

@ -18,16 +18,13 @@ class command():
def run(self): def run(self):
if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled') or \ if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled') or \
self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled') or \ self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
self.env['runtime']['outputManager'].presentText(_('Fenrir muted'), soundIcon='Accept', interrupt=True) self.env['runtime']['outputManager'].presentText(_('Fenrir muted'), soundIcon='Accept', interrupt=True)
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled','False') self.env['runtime']['settingsManager'].setSetting('speech', 'enabled','False')
self.env['runtime']['settingsManager'].setSetting('sound', 'enabled','False') self.env['runtime']['settingsManager'].setSetting('sound', 'enabled','False')
self.env['runtime']['settingsManager'].setSetting('braille', 'enabled','False')
else: else:
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled','True') self.env['runtime']['settingsManager'].setSetting('speech', 'enabled','True')
self.env['runtime']['settingsManager'].setSetting('sound', 'enabled','True') self.env['runtime']['settingsManager'].setSetting('sound', 'enabled','True')
self.env['runtime']['settingsManager'].setSetting('braille', 'enabled','True')
self.env['runtime']['outputManager'].presentText(_('Fenrir unmuted'), soundIcon='Cancel', interrupt=True) self.env['runtime']['outputManager'].presentText(_('Fenrir unmuted'), soundIcon='Cancel', interrupt=True)
def setCallback(self, callback): def setCallback(self, callback):

View File

@ -1,82 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import brlapi
from fenrirscreenreader.core import debug
class brailleDriver():
def __init__(self):
self._isInitialized = False
self._brl = None
self.deviceSize = None
def initialize(self, environment):
"""Initialize the BRLTTY connection."""
self.env = environment
try:
self._brl = brlapi.Connection()
self._brl.enterTtyMode()
self.deviceSize = self._brl.displaySize
self._isInitialized = True
return True
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Initializing braille failed:' + str(e),debug.debugLevel.ERROR)
return False
def getDeviceSize(self):
"""Get the size of the braille display."""
if not self._isInitialized:
return (0, 0)
return self.deviceSize if self.deviceSize else (0, 0)
def writeText(self, text):
"""Write text to the braille display."""
if not self._isInitialized:
return
try:
self._brl.writeText(text)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Writing braille failed:' + str(e),debug.debugLevel.ERROR)
def readKeypress(self):
"""Read a keypress from the braille display."""
if not self._isInitialized:
return None
try:
return self._brl.readKey(wait=0)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Reading key failed:' + str(e),debug.debugLevel.ERROR)
return None
def enterScreen(self, screen):
"""Enter a new screen context."""
if not self._isInitialized:
return
try:
self._brl.enterTtyModeWithPath(screen)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Entering screen failed:' + str(e),debug.debugLevel.ERROR)
def leaveScreen(self):
"""Leave the current screen context."""
if not self._isInitialized:
return
try:
self._brl.leaveTtyMode()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Leaving screen failed:' + str(e),debug.debugLevel.ERROR)
def shutdown(self):
"""Shutdown the braille driver."""
if not self._isInitialized:
return
try:
self.leaveScreen()
if self._brl:
self._brl.closeConnection()
self._brl = None
self._isInitialized = False
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('ERROR: Shutting down braille failed:' + str(e),debug.debugLevel.ERROR)

View File

@ -12,14 +12,12 @@ class fenrirEventType(Enum):
StopMainLoop = 1 StopMainLoop = 1
ScreenUpdate = 2 ScreenUpdate = 2
KeyboardInput = 3 KeyboardInput = 3
BrailleInput = 4 PlugInputDevice = 4
PlugInputDevice = 5 ScreenChanged = 5
BrailleFlush = 6 HeartBeat = 6
ScreenChanged = 7 ExecuteCommand = 7
HeartBeat = 8 # for time based scheduling ByteInput = 8
ExecuteCommand = 9 RemoteIncomming = 9
ByteInput = 10
RemoteIncomming = 11
def __int__(self): def __int__(self):
return self.value return self.value
def __str__(self): def __str__(self):

View File

@ -42,12 +42,8 @@ class eventManager():
self.env['runtime']['fenrirManager'].handleScreenUpdate(event) self.env['runtime']['fenrirManager'].handleScreenUpdate(event)
elif event['Type'] == fenrirEventType.KeyboardInput: elif event['Type'] == fenrirEventType.KeyboardInput:
self.env['runtime']['fenrirManager'].handleInput(event) self.env['runtime']['fenrirManager'].handleInput(event)
elif event['Type'] == fenrirEventType.BrailleInput:
pass
elif event['Type'] == fenrirEventType.PlugInputDevice: elif event['Type'] == fenrirEventType.PlugInputDevice:
self.env['runtime']['fenrirManager'].handlePlugInputDevice(event) self.env['runtime']['fenrirManager'].handlePlugInputDevice(event)
elif event['Type'] == fenrirEventType.BrailleFlush:
pass
elif event['Type'] == fenrirEventType.ScreenChanged: elif event['Type'] == fenrirEventType.ScreenChanged:
self.env['runtime']['fenrirManager'].handleScreenChange(event) self.env['runtime']['fenrirManager'].handleScreenChange(event)
elif event['Type'] == fenrirEventType.HeartBeat: elif event['Type'] == fenrirEventType.HeartBeat:

View File

@ -2,109 +2,50 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributors.
import signal, time, argparse, os, sys import signal
import time
import os
import sys
from fenrirscreenreader.core import i18n from fenrirscreenreader.core import i18n
from fenrirscreenreader.core import settingsManager from fenrirscreenreader.core import settingsManager
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
from fenrirscreenreader.core.eventData import fenrirEventType from fenrirscreenreader.core.eventData import fenrirEventType
from fenrirscreenreader import fenrirVersion
class fenrirManager(): class fenrirManager():
def __init__(self): def __init__(self, cliArgs):
self.initialized = False self.isInitialized = False
cliArgs = self.handleArgs()
if not cliArgs:
return
try: try:
self.environment = settingsManager.settingsManager().initFenrirConfig(cliArgs, self) self.environment = settingsManager.settingsManager().initFenrirConfig(cliArgs, self)
if not self.environment: if not self.environment:
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.initialized = True
self.isInitialized = True
self.modifierInput = False self.modifierInput = False
self.singleKeyCommand = False self.singleKeyCommand = False
self.command = '' self.command = ''
self.setProcessName() self.setProcessName()
def handleArgs(self):
"""
Parse and handle command line arguments for Fenrir.
Returns:
argparse.Namespace: Parsed command line arguments
None: If argument parsing fails
"""
parser = argparse.ArgumentParser(
description="Fenrir - A console screen reader for Linux",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'-v', '--version',
action='version',
version=f'Fenrir screen reader version {fenrirVersion.version}-{fenrirVersion.codeName}',
help='Show version information and exit'
)
parser.add_argument(
'-s', '--setting',
metavar='SETTING-FILE',
default='/etc/fenrir/settings/settings.conf',
help='Path to custom settings file'
)
parser.add_argument(
'-o', '--options',
metavar='SECTION#SETTING=VALUE;..',
default='',
help='Override settings file options. Format: SECTION#SETTING=VALUE;... (case sensitive)'
)
parser.add_argument(
'-d', '--debug',
action='store_true',
help='Enable debug mode'
)
parser.add_argument(
'-p', '--print',
action='store_true',
help='Print debug messages to screen'
)
parser.add_argument(
'-e', '--emulated-pty',
action='store_true',
help='Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)'
)
parser.add_argument(
'-E', '--emulated-evdev',
action='store_true',
help='Use PTY emulation with evdev for input (single instance)'
)
try:
args = parser.parse_args()
# Only do format validation, let file existence be handled by the config initialization
if args.options:
for option in args.options.split(';'):
if option and ('#' not in option or '=' not in option):
parser.error(f"Invalid option format: {option}\nExpected format: SECTION#SETTING=VALUE")
if args.emulated_pty and args.emulated_evdev:
parser.error("Cannot use both --emulated-pty and --emulated-evdev simultaneously")
return args
except Exception as e:
print(f"Error parsing arguments: {str(e)}", file=sys.stderr)
return None
def proceed(self): def proceed(self):
if not self.initialized: if not self.isInitialized:
return return
self.environment['runtime']['eventManager'].startMainEventLoop() self.environment['runtime']['eventManager'].startMainEventLoop()
self.shutdown() self.shutdown()
def handleInput(self, event): def handleInput(self, event):
#startTime = time.time() 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'])
@ -113,6 +54,7 @@ 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'].isSuspendingScreen():
self.environment['runtime']['inputManager'].writeEventBuffer() self.environment['runtime']['inputManager'].writeEventBuffer()
else: else:
@ -132,6 +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
@ -139,73 +82,59 @@ class fenrirManager():
self.environment['runtime']['inputManager'].handleDeviceGrab() self.environment['runtime']['inputManager'].handleDeviceGrab()
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')
#print('handleInput:',time.time() - startTime)
def handleByteInput(self, event): def handleByteInput(self, event):
if not event['Data']: if not event['Data'] or event['Data'] == b'':
return
if event['Data'] == b'':
return return
self.environment['runtime']['byteManager'].handleByteInput(event['Data']) self.environment['runtime']['byteManager'].handleByteInput(event['Data'])
self.environment['runtime']['commandManager'].executeDefaultTrigger('onByteInput') self.environment['runtime']['commandManager'].executeDefaultTrigger('onByteInput')
def handleExecuteCommand(self, event):
if not event['Data']: def handleExecuteCommand(self, event):
if not event['Data'] or event['Data'] == '':
return return
if event['Data'] == '': currentCommand = event['Data']
return
command = event['Data']
# special modes # special modes
if self.environment['runtime']['helpManager'].isTutorialMode(): if self.environment['runtime']['helpManager'].isTutorialMode():
if self.environment['runtime']['commandManager'].commandExists( command, 'help'): if self.environment['runtime']['commandManager'].commandExists(currentCommand, 'help'):
self.environment['runtime']['commandManager'].executeCommand( command, 'help') self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'help')
return return
elif self.environment['runtime']['vmenuManager'].getActive(): elif self.environment['runtime']['vmenuManager'].getActive():
if self.environment['runtime']['commandManager'].commandExists( command, 'vmenu-navigation'): if self.environment['runtime']['commandManager'].commandExists(currentCommand, 'vmenu-navigation'):
self.environment['runtime']['commandManager'].executeCommand( command, 'vmenu-navigation') self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'vmenu-navigation')
return return
# default # default
self.environment['runtime']['commandManager'].executeCommand( command, 'commands') self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'commands')
def handleRemoteIncomming(self, event): def handleRemoteIncomming(self, event):
if not event['Data']: if not event['Data']:
return return
self.environment['runtime']['remoteManager'].handleRemoteIncomming(event['Data']) self.environment['runtime']['remoteManager'].handleRemoteIncomming(event['Data'])
def handleScreenChange(self, event): def handleScreenChange(self, event):
self.environment['runtime']['screenManager'].hanldeScreenChange(event['Data']) self.environment['runtime']['screenManager'].hanldeScreenChange(event['Data'])
'''
if self.environment['runtime']['applicationManager'].isApplicationChange():
self.environment['runtime']['commandManager'].executeDefaultTrigger('onApplicationChange')
self.environment['runtime']['commandManager'].executeSwitchTrigger('onSwitchApplicationProfile', \
self.environment['runtime']['applicationManager'].getPrevApplication(), \
self.environment['runtime']['applicationManager'].getCurrentApplication())
'''
if self.environment['runtime']['vmenuManager'].getActive(): if self.environment['runtime']['vmenuManager'].getActive():
return return
self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenChanged') self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenChanged')
self.environment['runtime']['screenDriver'].getCurrScreen() self.environment['runtime']['screenDriver'].getCurrScreen()
def handleScreenUpdate(self, event): def handleScreenUpdate(self, event):
#startTime = time.time()
self.environment['runtime']['screenManager'].handleScreenUpdate(event['Data']) self.environment['runtime']['screenManager'].handleScreenUpdate(event['Data'])
'''
if self.environment['runtime']['applicationManager'].isApplicationChange():
self.environment['runtime']['commandManager'].executeDefaultTrigger('onApplicationChange')
self.environment['runtime']['commandManager'].executeSwitchTrigger('onSwitchApplicationProfile', \
self.environment['runtime']['applicationManager'].getPrevApplication(), \
self.environment['runtime']['applicationManager'].getCurrentApplication())
'''
# timout for the last keypress
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()
# has cursor changed?
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()
#print('handleScreenUpdate:',time.time() - startTime)
def handlePlugInputDevice(self, event): def handlePlugInputDevice(self, event):
try: try:
self.environment['runtime']['inputManager'].setLastDetectedDevices(event['Data']) self.environment['runtime']['inputManager'].setLastDetectedDevices(event['Data'])
@ -214,26 +143,27 @@ class fenrirManager():
self.environment['runtime']['inputManager'].handlePlugInputDevice(event['Data']) self.environment['runtime']['inputManager'].handlePlugInputDevice(event['Data'])
self.environment['runtime']['commandManager'].executeDefaultTrigger('onPlugInputDevice', force=True) self.environment['runtime']['commandManager'].executeDefaultTrigger('onPlugInputDevice', force=True)
self.environment['runtime']['inputManager'].setLastDetectedDevices(None) self.environment['runtime']['inputManager'].setLastDetectedDevices(None)
def handleHeartBeat(self, event):
self.environment['runtime']['commandManager'].executeDefaultTrigger('onHeartBeat',force=True)
#self.environment['runtime']['outputManager'].brailleText(flush=False)
def handleHeartBeat(self, event):
self.environment['runtime']['commandManager'].executeDefaultTrigger('onHeartBeat', force=True)
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
# key is already released. we need the old one
if not( self.singleKeyCommand and self.environment['runtime']['inputManager'].noKeyPressed()): if not(self.singleKeyCommand and self.environment['runtime']['inputManager'].noKeyPressed()):
shortcut = self.environment['runtime']['inputManager'].getCurrShortcut() currentShortcut = self.environment['runtime']['inputManager'].getCurrShortcut()
self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(shortcut) self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(currentShortcut)
if not self.modifierInput: if not self.modifierInput:
if self.environment['runtime']['inputManager'].isKeyPress(): if self.environment['runtime']['inputManager'].isKeyPress():
@ -252,7 +182,8 @@ class fenrirManager():
if self.singleKeyCommand: if self.singleKeyCommand:
self.environment['runtime']['eventManager'].putToEventQueue(fenrirEventType.ExecuteCommand, self.command) self.environment['runtime']['eventManager'].putToEventQueue(fenrirEventType.ExecuteCommand, self.command)
self.command = '' self.command = ''
def setProcessName(self, name = 'fenrir'):
def setProcessName(self, name='fenrir'):
"""Attempts to set the process name to 'fenrir'.""" """Attempts to set the process name to 'fenrir'."""
try: try:
from setproctitle import setproctitle from setproctitle import setproctitle
@ -273,12 +204,14 @@ class fenrirManager():
pass pass
return False return False
def shutdownRequest(self): def shutdownRequest(self):
try: try:
self.environment['runtime']['eventManager'].stopMainEventLoop() self.environment['runtime']['eventManager'].stopMainEventLoop()
except: except:
pass pass
def captureSignal(self, siginit, frame):
def captureSignal(self, sigInit, frame):
self.shutdownRequest() self.shutdownRequest()
def shutdown(self): def shutdown(self):
@ -287,10 +220,10 @@ 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 currManager in self.environment['general']['managerList']:
if self.environment['runtime'][currManager]: for currentManager in self.environment['general']['managerList']:
self.environment['runtime'][currManager].shutdown() if self.environment['runtime'][currentManager]:
del self.environment['runtime'][currManager] self.environment['runtime'][currentManager].shutdown()
del self.environment['runtime'][currentManager]
self.environment = None self.environment = None

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers. # By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils from fenrirscreenreader.utils import line_utils
@ -11,27 +11,26 @@ import string, time, re
class outputManager(): class outputManager():
def __init__(self): def __init__(self):
self.lastEcho = '' self.lastEcho = ''
def initialize(self, environment): def initialize(self, environment):
self.env = environment self.env = environment
self.env['runtime']['settingsManager'].loadDriver(\ self.env['runtime']['settingsManager'].loadDriver(
self.env['runtime']['settingsManager'].getSetting('speech', 'driver'), 'speechDriver') self.env['runtime']['settingsManager'].getSetting('speech', 'driver'), 'speechDriver')
self.env['runtime']['settingsManager'].loadDriver(\ self.env['runtime']['settingsManager'].loadDriver(
self.env['runtime']['settingsManager'].getSetting('sound', 'driver'), 'soundDriver') self.env['runtime']['settingsManager'].getSetting('sound', 'driver'), 'soundDriver')
self.env['runtime']['settingsManager'].loadDriver(\
self.env['runtime']['settingsManager'].getSetting('braille', 'driver'), 'brailleDriver')
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')
self.env['runtime']['settingsManager'].shutdownDriver('brailleDriver')
def presentText(self, text, interrupt=True, soundIcon = '', ignorePunctuation=False, announceCapital=False, flush=True, brailleAlternative = ''): def presentText(self, text, interrupt=True, soundIcon='', ignorePunctuation=False, announceCapital=False, flush=True):
if text == '': if text == '':
return return
if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'readNumbersAsDigits') and len(text.strip()) > 1: if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'readNumbersAsDigits') and len(text.strip()) > 1:
text = re.sub(r"(\d)", r"\1 ", text).rstrip() text = re.sub(r"(\d)", r"\1 ", text).rstrip()
self.env['runtime']['debug'].writeDebugOut("presentText:\nsoundIcon:'"+soundIcon+"'\nText:\n" + text ,debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("presentText:\nsoundIcon:'"+soundIcon+"'\nText:\n" + text, debug.debugLevel.INFO)
if self.playSoundIcon(soundIcon, interrupt): if self.playSoundIcon(soundIcon, interrupt):
self.env['runtime']['debug'].writeDebugOut("soundIcon found" ,debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("soundIcon found", debug.debugLevel.INFO)
return return
if (len(text) > 1) and (text.strip(string.whitespace) == ''): if (len(text) > 1) and (text.strip(string.whitespace) == ''):
return return
@ -40,35 +39,31 @@ class outputManager():
if self.playSoundIcon('capital', False): if self.playSoundIcon('capital', False):
toAnnounceCapital = False toAnnounceCapital = False
self.lastEcho = text self.lastEcho = text
self.speakText(text, interrupt, ignorePunctuation,toAnnounceCapital) self.speakText(text, interrupt, ignorePunctuation, toAnnounceCapital)
if flush:
if brailleAlternative != '':
brlText = brailleAlternative
else:
brlText = text
self.brailleText(brlText, flush)
def getLastEcho(self): def getLastEcho(self):
return self.lastEcho return self.lastEcho
def speakText(self, text, interrupt=True, ignorePunctuation=False, announceCapital=False): def speakText(self, text, interrupt=True, ignorePunctuation=False, announceCapital=False):
if not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'): if not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'):
self.env['runtime']['debug'].writeDebugOut("Speech disabled in outputManager.speakText",debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Speech disabled in outputManager.speakText", debug.debugLevel.INFO)
return return
if self.env['runtime']['speechDriver'] == None: if self.env['runtime']['speechDriver'] == None:
self.env['runtime']['debug'].writeDebugOut("No speechDriver in outputManager.speakText",debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("No speechDriver in outputManager.speakText", debug.debugLevel.ERROR)
return return
if interrupt: if interrupt:
self.interruptOutput() self.interruptOutput()
try: try:
self.env['runtime']['speechDriver'].setLanguage(self.env['runtime']['settingsManager'].getSetting('speech', 'language')) self.env['runtime']['speechDriver'].setLanguage(self.env['runtime']['settingsManager'].getSetting('speech', 'language'))
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:
@ -76,199 +71,55 @@ class outputManager():
else: else:
self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch')) self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch'))
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:
self.env['runtime']['debug'].writeDebugOut("setting speech module in outputManager.speakText",debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("setting speech module 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'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume')) self.env['runtime']['speechDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume'))
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', ' , ')
else: else:
cleanText = text.replace('\n',' ') cleanText = text.replace('\n', ' ')
cleanText = self.env['runtime']['textManager'].replaceHeadLines(cleanText) cleanText = self.env['runtime']['textManager'].replaceHeadLines(cleanText)
cleanText = self.env['runtime']['punctuationManager'].proceedPunctuation(cleanText, ignorePunctuation) cleanText = self.env['runtime']['punctuationManager'].proceedPunctuation(cleanText, ignorePunctuation)
cleanText = re.sub(' +$',' ', cleanText) cleanText = re.sub(' +$', ' ', cleanText)
self.env['runtime']['speechDriver'].speak(cleanText) self.env['runtime']['speechDriver'].speak(cleanText)
self.env['runtime']['debug'].writeDebugOut("Speak: "+ cleanText,debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Speak: "+ cleanText, debug.debugLevel.INFO)
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("\"speak\" in outputManager.speakText ",debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("\"speak\" 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)
def brailleText(self, text='', flush=True):
if not self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
return
if self.env['runtime']['brailleDriver'] == None:
return
if flush:
self.env['output']['nextFlush'] = time.time() + self.getFlushTime(text)
self.env['output']['messageOffset'] = {'x':0,'y':0}
self.env['output']['messageText'] = text
displayText = self.getBrailleTextWithOffset(self.env['output']['messageText'], self.env['output']['messageOffset'])
self.env['runtime']['brailleDriver'].writeText('flush '+ displayText)
else:
if self.env['output']['nextFlush'] < time.time():
if self.env['output']['messageText'] != '':
self.env['output']['messageText'] = ''
if self.env['output']['messageOffset'] != None:
self.env['output']['messageOffset'] = None
cursor = self.getBrailleCursor()
x, y, self.env['output']['brlText'] = \
line_utils.getCurrentLine(cursor['x'], cursor['y'], self.env['screen']['newContentText'])
displayText = self.getBrailleTextWithOffset(self.env['screen']['newContentText'], self.env['output']['cursorOffset'], cursor)
self.env['runtime']['brailleDriver'].writeText('notflush ' + displayText)
else:
displayText = self.getBrailleTextWithOffset(self.env['output']['messageText'], self.env['output']['messageOffset'])
self.env['runtime']['brailleDriver'].writeText('flush'+displayText)
def resetSpeechDriver(self):
try:
self.env['runtime']['speechDriver'].reset()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut("reset " + str(e),debug.debugLevel.ERROR)
def getBrailleCursor(self):
if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'REVIEW':
return self.env['runtime']['cursorManager'].getReviewOrTextCursor()
if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'MANUAL':
return self.env['runtime']['cursorManager'].getReviewOrTextCursor()
if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'LAST':
return self.env['runtime']['cursorManager'].getReviewOrTextCursor()
return self.env['runtime']['cursorManager'].getReviewOrTextCursor()
def getFixCursorCell(self):
size = self.env['runtime']['brailleDriver'].getDeviceSize()[0]
fixCell = self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'fixCursorOnCell')
if fixCell <= -1:
return size[0]
if fixCell >= size[0]:
return size[0]
return fixCell
def getActiveOffsetAndText(self):
if self.env['output']['messageOffset']:
return self.env['output']['messageOffset'], self.env['output']['messageText']
if not self.env['output']['cursorOffset']:
return self.getBrailleCursor(), self.env['screen']['newContentText']
return self.env['output']['cursorOffset'], self.env['screen']['newContentText']
def getHorizontalPanSize(self):
size = self.env['runtime']['brailleDriver'].getDeviceSize()
if self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'panSizeHorizontal') <= 0:
return size[0]
if self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'panSizeHorizontal') >= size[0]:
return size[0]
return self.env['runtime']['settingsManager'].getSettingAsInt('braille', 'panSizeHorizontal')
def getHorizontalPanLevel(self,offsetChange = 0):
panned = True
panSize = self.getHorizontalPanSize()
offset, text = self.getActiveOffsetAndText()
currline = text.split('\n')[offset['y']]
newOffsetStart = (int(offset['x'] / panSize) + offsetChange) * panSize
if newOffsetStart < 0:
newOffsetStart = 0
panned = False
if newOffsetStart >= len(text):
newOffsetStart = int((len(text) - panSize - 1) / panSize)
panned = False
return newOffsetStart, panned
def setPanLeft(self):
newPan, panned = self.getHorizontalPanLevel(-1)
if self.env['output']['messageOffset']:
self.env['output']['messageOffset'] = newPan.copy()
else:
self.env['output']['cursorOffset'] = newPan.copy()
return panned
def setPanRight(self):
newPan, panned = self.getHorizontalPanLevel(1)
if self.env['output']['messageOffset']:
self.env['output']['messageOffset'] = newPan.copy()
else:
self.env['output']['cursorOffset'] = newPan.copy()
return panned
def removePanning(self):
if self.env['output']['messageOffset']:
self.env['output']['messageOffset'] = None
else:
self.env['output']['cursorOffset'] = None
def getBrailleTextWithOffset(self, text, offset = None, cursor = None):
if text == '':
return ''
size = self.env['runtime']['brailleDriver'].getDeviceSize()
offsetText = text
if cursor and not offset:
if self.env['runtime']['settingsManager'].getSetting('braille', 'cursorFollowMode').upper() == 'FIXCELL':
#fix cell
cursorCell = self.getFixCursorCell()
offsetStart = cursor['x']
if offsetStart < size[0]:
if offsetStart <= cursorCell:
return offsetText[0: size[0]]
offsetStart -= cursorCell
if offsetStart >= len(offsetText):
offsetStart = len(offsetText) - 1
else:
# page and fallback
offsetStart = int(cursor['x'] / size[0]) * size[0]
else:
if not offset:
offset = {'x':0,'y':0}
offsetStart = offset['x']
if offsetStart >= len(offsetText):
offsetStart = len(offsetText) - size[0]
if offsetStart < 0:
offsetStart = 0
offsetEnd = offsetStart + size[0]
offsetText = offsetText[offsetStart: offsetEnd]
return offsetText
def interruptOutput(self): def interruptOutput(self):
try: try:
self.env['runtime']['speechDriver'].cancel() self.env['runtime']['speechDriver'].cancel()
self.env['runtime']['debug'].writeDebugOut("Interrupt speech",debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Interrupt speech", debug.debugLevel.INFO)
except: except:
pass pass
def clearFlushTime(self): def playSoundIcon(self, soundIcon='', interrupt=True):
self.setFlushTime(0.0)
def setFlushTime(self,newTime):
self.env['output']['nextFlush'] = newTime
def getFlushTime(self,text=''):
if self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout') < 0 or \
self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'NONE':
return 999999999999
if self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'FIX':
return self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout')
if self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'CHAR':
return self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout') * len(text)
if self.env['runtime']['settingsManager'].getSetting('braille', 'flushMode').upper() == 'WORD':
wordsList = text.split(' ')
return self.env['runtime']['settingsManager'].getSettingAsFloat('braille', 'flushTimeout') * len( list( filter(None, wordsList) ) )
def playSoundIcon(self, soundIcon = '', interrupt=True):
if soundIcon == '': if soundIcon == '':
return False return False
soundIcon = soundIcon.upper() soundIcon = soundIcon.upper()
if not self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'): if not self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
self.env['runtime']['debug'].writeDebugOut("Sound disabled in outputManager.playSoundIcon",debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Sound disabled in outputManager.playSoundIcon", debug.debugLevel.INFO)
return False return False
try: try:
@ -278,40 +129,40 @@ class outputManager():
return False return False
if self.env['runtime']['soundDriver'] == None: if self.env['runtime']['soundDriver'] == None:
self.env['runtime']['debug'].writeDebugOut("No soundDriver in outputManager.playSoundIcon: soundDriver not loaded",debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("No soundDriver in outputManager.playSoundIcon: soundDriver not loaded", debug.debugLevel.ERROR)
return False return False
try: try:
self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume')) self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume'))
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::setVolume: " + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::setVolume: " + str(e), debug.debugLevel.ERROR)
try: try:
self.env['runtime']['soundDriver'].playSoundFile(self.env['soundIcons'][soundIcon], interrupt) self.env['runtime']['soundDriver'].playSoundFile(self.env['soundIcons'][soundIcon], interrupt)
return True return True
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::playSoundFile: " + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::playSoundFile: " + str(e), debug.debugLevel.ERROR)
return False return False
return False return False
def playFrequence(self, frequence, duration, interrupt=True): def playFrequence(self, frequence, duration, interrupt=True):
if not self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'): if not self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
self.env['runtime']['debug'].writeDebugOut("Sound disabled in outputManager.playFrequence",debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("Sound disabled in outputManager.playFrequence", debug.debugLevel.INFO)
return False return False
if frequence < 1 or frequence > 20000: if frequence < 1 or frequence > 20000:
self.env['runtime']['debug'].writeDebugOut("outputManager.playFrequence::Filefrequence is out of range:" + str(frequence),debug.debugLevel.INFO) self.env['runtime']['debug'].writeDebugOut("outputManager.playFrequence::Filefrequence is out of range:" + str(frequence), debug.debugLevel.INFO)
return False return False
if self.env['runtime']['soundDriver'] == None: if self.env['runtime']['soundDriver'] == None:
self.env['runtime']['debug'].writeDebugOut("No soundDriver in outputManager.playFrequence: soundDriver not loaded",debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("No soundDriver in outputManager.playFrequence: soundDriver not loaded", debug.debugLevel.ERROR)
return False return False
try: try:
self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume')) self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume'))
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::setVolume: " + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::setVolume: " + str(e), debug.debugLevel.ERROR)
adjustVolume = 0.0 adjustVolume = 0.0
try: try:
adjustVolume = 1.0 - (frequence / 20000) adjustVolume = 1.0 - (frequence / 20000)
@ -324,7 +175,7 @@ class outputManager():
self.env['runtime']['soundDriver'].playFrequence(frequence, duration, adjustVolume, interrupt) self.env['runtime']['soundDriver'].playFrequence(frequence, duration, adjustVolume, interrupt)
return True return True
except Exception as e: except Exception as e:
self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::playSoundFile: " + str(e),debug.debugLevel.ERROR) self.env['runtime']['debug'].writeDebugOut("outputManager.playSoundIcon::playSoundFile: " + str(e), debug.debugLevel.ERROR)
return False return False
return False return False
@ -335,9 +186,9 @@ class outputManager():
self.env['commandBuffer']['enableSpeechOnKeypress'] = True self.env['commandBuffer']['enableSpeechOnKeypress'] = True
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'))) self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled')))
self.interruptOutput() self.interruptOutput()
def announceActiveCursor(self, interrupt_p=False): def announceActiveCursor(self, interrupt_p=False):
if self.env['runtime']['cursorManager'].isReviewMode(): if self.env['runtime']['cursorManager'].isReviewMode():
self.presentText(' review cursor ', interrupt=interrupt_p) self.presentText(' review cursor ', interrupt=interrupt_p)
else: else:
self.presentText(' text cursor ', interrupt=interrupt_p) self.presentText(' text cursor ', interrupt=interrupt_p)

View File

@ -12,7 +12,6 @@ runtimeData = {
'soundDriver': None, 'soundDriver': None,
'inputDriver': None, 'inputDriver': None,
'remoteDriver': None, 'remoteDriver': None,
'brailleDriver': None,
'inputManager': None, 'inputManager': None,
'commandManager': None, 'commandManager': None,
'screenManager': None, 'screenManager': None,

View File

@ -59,8 +59,6 @@ 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 self.isScreenChange():
self.changeBrailleScreen()
if not self.isSuspendingScreen(self.env['screen']['newTTY']): if not self.isSuspendingScreen(self.env['screen']['newTTY']):
self.update(eventData, 'onScreenChange') self.update(eventData, 'onScreenChange')
self.env['screen']['lastScreenUpdate'] = time.time() self.env['screen']['lastScreenUpdate'] = time.time()
@ -74,9 +72,6 @@ class screenManager():
self.env['runtime']['inputManager'].handleDeviceGrab() self.env['runtime']['inputManager'].handleDeviceGrab()
if not self.getCurrScreenIgnored(): if not self.getCurrScreenIgnored():
self.update(eventData, 'onScreenUpdate') self.update(eventData, 'onScreenUpdate')
#if trigger == 'onUpdate' or self.isScreenChange() \
# or len(self.env['screen']['newDelta']) > 6:
# self.env['runtime']['screenDriver'].getCurrApplication()
self.env['screen']['lastScreenUpdate'] = time.time() self.env['screen']['lastScreenUpdate'] = time.time()
elif self.isCurrScreenIgnoredChanged(): elif self.isCurrScreenIgnoredChanged():
self.env['runtime']['outputManager'].interruptOutput() self.env['runtime']['outputManager'].interruptOutput()
@ -145,14 +140,10 @@ class screenManager():
self.env['screen']['newContentText'][cursorLineEnd:] == self.env['screen']['oldContentText'][cursorLineEnd:]: self.env['screen']['newContentText'][cursorLineEnd:] == self.env['screen']['oldContentText'][cursorLineEnd:]:
cursorLineStartOffset = cursorLineStart cursorLineStartOffset = cursorLineStart
cursorLineEndOffset = cursorLineEnd cursorLineEndOffset = cursorLineEnd
#if cursorLineStart < cursorLineStart + self.env['screen']['newCursor']['x'] - 4:
# cursorLineStartOffset = cursorLineStart + self.env['screen']['newCursor']['x'] - 4
if cursorLineEnd > cursorLineStart + self.env['screen']['newCursor']['x'] + 3: if cursorLineEnd > cursorLineStart + self.env['screen']['newCursor']['x'] + 3:
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]
# oldScreenText = re.sub(' +',' ',oldScreenText)
newScreenText = self.env['screen']['newContentText'][cursorLineStartOffset:cursorLineEndOffset] newScreenText = self.env['screen']['newContentText'][cursorLineStartOffset:cursorLineEndOffset]
#newScreenText = re.sub(' +',' ',newScreenText)
diff = self.differ.compare(oldScreenText, newScreenText) diff = self.differ.compare(oldScreenText, newScreenText)
diffList = list(diff) diffList = list(diff)
typing = True typing = True
@ -221,21 +212,4 @@ class screenManager():
try: try:
self.env['runtime']['screenDriver'].injectTextToScreen(text, screen) self.env['runtime']['screenDriver'].injectTextToScreen(text, screen)
except Exception as e: 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)
def changeBrailleScreen(self):
if not self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
return
if not self.env['runtime']['brailleDriver']:
return
if self.env['screen']['oldTTY']:
if not self.isSuspendingScreen(self.env['screen']['oldTTY']):
try:
self.env['runtime']['brailleDriver'].leveScreen()
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('screenManager:changeBrailleScreen:leveScreen ' + str(e),debug.debugLevel.ERROR)
if not self.isSuspendingScreen():
try:
self.env['runtime']['brailleDriver'].enterScreen(self.env['screen']['newTTY'])
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('screenManager:changeBrailleScreen:enterScreen ' + str(e),debug.debugLevel.ERROR)

View File

@ -36,17 +36,6 @@ settingsData = {
'fenrirMinRate':80, 'fenrirMinRate':80,
'fenrirMaxRate':450, 'fenrirMaxRate':450,
}, },
'braille':{
'enabled': False,
'driver':'brlapiDriver',
'layout': 'en',
'flushMode': 'word', #NONE,FIX,CHAR,WORD
'flushTimeout': 3,
'cursorFocusMode':'page', # page,fixCell
'fixCursorOnCell': -1,
'cursorFollowMode': 'review', # none, review, last, text
'panSizeHorizontal': 0 # 0 = display size
},
'screen':{ 'screen':{
'driver': 'vcsaDriver', 'driver': 'vcsaDriver',
'encoding': 'auto', 'encoding': 'auto',

View File

@ -31,13 +31,9 @@ class FenrirConfigTool:
self.presetOptions = { self.presetOptions = {
'sound.driver': ['genericDriver', 'gstreamerDriver'], 'sound.driver': ['genericDriver', 'gstreamerDriver'],
'speech.driver': ['speechdDriver', 'genericDriver'], 'speech.driver': ['speechdDriver', 'genericDriver'],
'braille.driver': ['dummyDriver', 'brailttyDriver', 'brlapiDriver'],
'screen.driver': ['vcsaDriver', 'dummyDriver', 'ptyDriver', 'debugDriver'], 'screen.driver': ['vcsaDriver', 'dummyDriver', 'ptyDriver', 'debugDriver'],
'keyboard.driver': ['evdevDriver', 'dummyDriver'], 'keyboard.driver': ['evdevDriver', 'dummyDriver'],
'remote.driver': ['unixDriver', 'tcpDriver'], 'remote.driver': ['unixDriver', 'tcpDriver'],
'braille.flushMode': ['word', 'char', 'fix', 'none'],
'braille.cursorFocusMode': ['page', 'fixCell'],
'braille.cursorFollowMode': ['review', 'last', 'none'],
'keyboard.charEchoMode': ['0', '1', '2'], 'keyboard.charEchoMode': ['0', '1', '2'],
'general.punctuationLevel': ['none', 'some', 'most', 'all'], 'general.punctuationLevel': ['none', 'some', 'most', 'all'],
'general.debugMode': ['File', 'Print'] 'general.debugMode': ['File', 'Print']
@ -47,8 +43,7 @@ class FenrirConfigTool:
'sound.volume': 'Volume level from 0 (quietest) to 1.0 (loudest)', 'sound.volume': 'Volume level from 0 (quietest) to 1.0 (loudest)',
'speech.rate': 'Speech rate from 0 (slowest) to 1.0 (fastest)', 'speech.rate': 'Speech rate from 0 (slowest) to 1.0 (fastest)',
'speech.pitch': 'Voice pitch from 0 (lowest) to 1.0 (highest)', 'speech.pitch': 'Voice pitch from 0 (lowest) to 1.0 (highest)',
'keyboard.charEchoMode': '0 = None, 1 = always, 2 = only while capslock', 'keyboard.charEchoMode': '0 = None, 1 = always, 2 = only while capslock'
'braille.flushMode': 'word = flush after words, char = flush after chars, fix = flush after time, none = manual flush'
} }
def check_root(self) -> bool: def check_root(self) -> bool: