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
- 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
@ -59,13 +59,6 @@ This software is licensed under the LGPL v3.
- emacspeak
# Braille Drivers:
1. "BrlttyDriver" braille driver (WIP):
- brltty (configured and running)
- python-brlapi
# Sound Drivers:
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:
- sound driver: genericDriver (via sox, 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

View File

@ -8,7 +8,7 @@ from typing import List, Optional
@dataclass
class Dependency:
name: str
depType: str # screen, braille, input, sound, speech, core
depType: str # screen, input, sound, speech, core
moduleName: str
checkCommands: Optional[List[str]] = None # Command-line tools to check
pythonImports: Optional[List[str]] = None # Python packages to check
@ -60,12 +60,6 @@ dependencyList = [
Dependency('PTY', 'screen', 'ptyDriver',
pythonImports=['pyte']),
# Braille drivers
Dependency('DummyBraille', 'braille', 'dummyDriver'),
Dependency('DebugBraille', 'braille', 'debugDriver'),
Dependency('BRLAPI', 'braille', 'brlapiDriver',
pythonImports=['brlapi']),
# Input drivers
Dependency('DummyInput', 'input', 'dummyDriver'),
Dependency('DebugInput', 'input', 'debugDriver'),
@ -94,7 +88,6 @@ dependencyList = [
defaultModules = {
'FenrirCore',
'VCSA',
'DummyBraille',
'Evdev',
'GenericSpeech',
'GenericSound'
@ -105,7 +98,7 @@ def check_all_dependencies():
availableModules = []
# 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('-' * 20)

View File

@ -91,37 +91,6 @@ fenrirMaxPitch=99
fenrirMinRate=80
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]
driver=vcsaDriver
encoding=auto

View File

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

View File

@ -76,7 +76,7 @@ setup(
# Packages
packages=find_packages('src/'),
package_dir={'': 'src/'},
scripts=['src/fenrir','src/fenrir-daemon'],
scripts=['src/fenrir'],
# Include additional files into the package
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('- Speech-dispatcher: for the default speech driver')
print('- Espeak: as basic TTS engine')
print('- BrlTTY: for Braille')
print('- sox: is a player for the generic sound driver')

View File

@ -2,20 +2,115 @@
# -*- coding: utf-8 -*-
# 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()))))
if not fenrirPath in sys.path:
sys.path.append(fenrirPath)
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():
app = fenrirManager.fenrirManager()
app.proceed()
del app
global cliArgs
argumentParser = create_argument_parser()
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__":
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):
if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled') or \
self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled') or \
self.env['runtime']['settingsManager'].getSettingAsBool('braille', 'enabled'):
self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'enabled'):
self.env['runtime']['outputManager'].presentText(_('Fenrir muted'), soundIcon='Accept', interrupt=True)
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled','False')
self.env['runtime']['settingsManager'].setSetting('sound', 'enabled','False')
self.env['runtime']['settingsManager'].setSetting('braille', 'enabled','False')
else:
self.env['runtime']['settingsManager'].setSetting('speech', '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)
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
ScreenUpdate = 2
KeyboardInput = 3
BrailleInput = 4
PlugInputDevice = 5
BrailleFlush = 6
ScreenChanged = 7
HeartBeat = 8 # for time based scheduling
ExecuteCommand = 9
ByteInput = 10
RemoteIncomming = 11
PlugInputDevice = 4
ScreenChanged = 5
HeartBeat = 6
ExecuteCommand = 7
ByteInput = 8
RemoteIncomming = 9
def __int__(self):
return self.value
def __str__(self):

View File

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

View File

@ -2,109 +2,50 @@
# -*- coding: utf-8 -*-
# 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 settingsManager
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.eventData import fenrirEventType
from fenrirscreenreader import fenrirVersion
class fenrirManager():
def __init__(self):
self.initialized = False
cliArgs = self.handleArgs()
if not cliArgs:
return
def __init__(self, cliArgs):
self.isInitialized = False
try:
self.environment = settingsManager.settingsManager().initFenrirConfig(cliArgs, self)
if not self.environment:
raise RuntimeError('Cannot Initialize. Maybe the configfile is not available or not parseable')
except RuntimeError:
raise
self.environment['runtime']['outputManager'].presentText(_("Start Fenrir"), soundIcon='ScreenReaderOn', interrupt=True)
signal.signal(signal.SIGINT, self.captureSignal)
signal.signal(signal.SIGTERM, self.captureSignal)
self.initialized = True
self.isInitialized = True
self.modifierInput = False
self.singleKeyCommand = False
self.command = ''
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):
if not self.initialized:
if not self.isInitialized:
return
self.environment['runtime']['eventManager'].startMainEventLoop()
self.shutdown()
def handleInput(self, event):
#startTime = time.time()
self.environment['runtime']['debug'].writeDebugOut('DEBUG INPUT fenrirMan:' + str(event), debug.debugLevel.INFO)
if not event['Data']:
event['Data'] = self.environment['runtime']['inputManager'].getInputEvent()
if event['Data']:
event['Data']['EventName'] = self.environment['runtime']['inputManager'].convertEventName(event['Data']['EventName'])
self.environment['runtime']['inputManager'].handleInputEvent(event['Data'])
@ -113,6 +54,7 @@ class fenrirManager():
if self.environment['runtime']['inputManager'].noKeyPressed():
self.environment['runtime']['inputManager'].clearLastDeepInput()
if self.environment['runtime']['screenManager'].isSuspendingScreen():
self.environment['runtime']['inputManager'].writeEventBuffer()
else:
@ -132,6 +74,7 @@ class fenrirManager():
self.environment['runtime']['inputManager'].clearEventBuffer()
else:
self.environment['runtime']['inputManager'].writeEventBuffer()
if self.environment['runtime']['inputManager'].noKeyPressed():
self.modifierInput = False
self.singleKeyCommand = False
@ -140,72 +83,58 @@ class fenrirManager():
if self.environment['input']['keyForeward'] > 0:
self.environment['input']['keyForeward'] -= 1
self.environment['runtime']['commandManager'].executeDefaultTrigger('onKeyInput')
#print('handleInput:',time.time() - startTime)
def handleByteInput(self, event):
if not event['Data']:
return
if event['Data'] == b'':
if not event['Data'] or event['Data'] == b'':
return
self.environment['runtime']['byteManager'].handleByteInput(event['Data'])
self.environment['runtime']['commandManager'].executeDefaultTrigger('onByteInput')
def handleExecuteCommand(self, event):
if not event['Data']:
if not event['Data'] or event['Data'] == '':
return
if event['Data'] == '':
return
command = event['Data']
currentCommand = event['Data']
# special modes
if self.environment['runtime']['helpManager'].isTutorialMode():
if self.environment['runtime']['commandManager'].commandExists( command, 'help'):
self.environment['runtime']['commandManager'].executeCommand( command, 'help')
if self.environment['runtime']['commandManager'].commandExists(currentCommand, 'help'):
self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'help')
return
elif self.environment['runtime']['vmenuManager'].getActive():
if self.environment['runtime']['commandManager'].commandExists( command, 'vmenu-navigation'):
self.environment['runtime']['commandManager'].executeCommand( command, 'vmenu-navigation')
if self.environment['runtime']['commandManager'].commandExists(currentCommand, 'vmenu-navigation'):
self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'vmenu-navigation')
return
# default
self.environment['runtime']['commandManager'].executeCommand( command, 'commands')
self.environment['runtime']['commandManager'].executeCommand(currentCommand, 'commands')
def handleRemoteIncomming(self, event):
if not event['Data']:
return
self.environment['runtime']['remoteManager'].handleRemoteIncomming(event['Data'])
def handleScreenChange(self, event):
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():
return
self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenChanged')
self.environment['runtime']['screenDriver'].getCurrScreen()
def handleScreenUpdate(self, event):
#startTime = time.time()
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:
self.environment['runtime']['inputManager'].clearLastDeepInput()
# has cursor changed?
if self.environment['runtime']['cursorManager'].isCursorVerticalMove() or \
self.environment['runtime']['cursorManager'].isCursorHorizontalMove():
if (self.environment['runtime']['cursorManager'].isCursorVerticalMove() or
self.environment['runtime']['cursorManager'].isCursorHorizontalMove()):
self.environment['runtime']['commandManager'].executeDefaultTrigger('onCursorChange')
self.environment['runtime']['commandManager'].executeDefaultTrigger('onScreenUpdate')
self.environment['runtime']['inputManager'].clearLastDeepInput()
#print('handleScreenUpdate:',time.time() - startTime)
def handlePlugInputDevice(self, event):
try:
self.environment['runtime']['inputManager'].setLastDetectedDevices(event['Data'])
@ -214,26 +143,27 @@ class fenrirManager():
self.environment['runtime']['inputManager'].handlePlugInputDevice(event['Data'])
self.environment['runtime']['commandManager'].executeDefaultTrigger('onPlugInputDevice', force=True)
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 detectShortcutCommand(self):
if self.environment['input']['keyForeward'] > 0:
return
if len(self.environment['input']['prevInput']) > len(self.environment['input']['currInput']):
return
if self.environment['runtime']['inputManager'].isKeyPress():
self.modifierInput = self.environment['runtime']['inputManager'].currKeyIsModifier()
else:
if not self.environment['runtime']['inputManager'].noKeyPressed():
if self.singleKeyCommand:
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()):
shortcut = self.environment['runtime']['inputManager'].getCurrShortcut()
self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(shortcut)
currentShortcut = self.environment['runtime']['inputManager'].getCurrShortcut()
self.command = self.environment['runtime']['inputManager'].getCommandForShortcut(currentShortcut)
if not self.modifierInput:
if self.environment['runtime']['inputManager'].isKeyPress():
@ -252,6 +182,7 @@ class fenrirManager():
if self.singleKeyCommand:
self.environment['runtime']['eventManager'].putToEventQueue(fenrirEventType.ExecuteCommand, self.command)
self.command = ''
def setProcessName(self, name='fenrir'):
"""Attempts to set the process name to 'fenrir'."""
try:
@ -273,12 +204,14 @@ class fenrirManager():
pass
return False
def shutdownRequest(self):
try:
self.environment['runtime']['eventManager'].stopMainEventLoop()
except:
pass
def captureSignal(self, siginit, frame):
def captureSignal(self, sigInit, frame):
self.shutdownRequest()
def shutdown(self):
@ -287,10 +220,10 @@ class fenrirManager():
self.environment['runtime']['outputManager'].presentText(_("Quit Fenrir"), soundIcon='ScreenReaderOff', interrupt=True)
self.environment['runtime']['eventManager'].cleanEventQueue()
time.sleep(0.6)
for currManager in self.environment['general']['managerList']:
if self.environment['runtime'][currManager]:
self.environment['runtime'][currManager].shutdown()
del self.environment['runtime'][currManager]
for currentManager in self.environment['general']['managerList']:
if self.environment['runtime'][currentManager]:
self.environment['runtime'][currentManager].shutdown()
del self.environment['runtime'][currentManager]
self.environment = None

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3
#!/bin/python
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
@ -11,20 +11,19 @@ import string, time, re
class outputManager():
def __init__(self):
self.lastEcho = ''
def initialize(self, 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'].loadDriver(\
self.env['runtime']['settingsManager'].loadDriver(
self.env['runtime']['settingsManager'].getSetting('sound', 'driver'), 'soundDriver')
self.env['runtime']['settingsManager'].loadDriver(\
self.env['runtime']['settingsManager'].getSetting('braille', 'driver'), 'brailleDriver')
def shutdown(self):
self.env['runtime']['settingsManager'].shutdownDriver('soundDriver')
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 == '':
return
if self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'readNumbersAsDigits') and len(text.strip()) > 1:
@ -41,14 +40,10 @@ class outputManager():
toAnnounceCapital = False
self.lastEcho = text
self.speakText(text, interrupt, ignorePunctuation, toAnnounceCapital)
if flush:
if brailleAlternative != '':
brlText = brailleAlternative
else:
brlText = text
self.brailleText(brlText, flush)
def getLastEcho(self):
return self.lastEcho
def speakText(self, text, interrupt=True, ignorePunctuation=False, announceCapital=False):
if not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled'):
self.env['runtime']['debug'].writeDebugOut("Speech disabled in outputManager.speakText", debug.debugLevel.INFO)
@ -112,132 +107,6 @@ class outputManager():
self.env['runtime']['debug'].writeDebugOut("\"speak\" in outputManager.speakText ", 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):
try:
self.env['runtime']['speechDriver'].cancel()
@ -245,24 +114,6 @@ class outputManager():
except:
pass
def clearFlushTime(self):
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 == '':
return False
@ -335,9 +186,9 @@ class outputManager():
self.env['commandBuffer']['enableSpeechOnKeypress'] = True
self.env['runtime']['settingsManager'].setSetting('speech', 'enabled', str(not self.env['runtime']['settingsManager'].getSettingAsBool('speech', 'enabled')))
self.interruptOutput()
def announceActiveCursor(self, interrupt_p=False):
if self.env['runtime']['cursorManager'].isReviewMode():
self.presentText(' review cursor ', interrupt=interrupt_p)
else:
self.presentText(' text cursor ', interrupt=interrupt_p)

View File

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

View File

@ -59,8 +59,6 @@ class screenManager():
if self.isCurrScreenIgnoredChanged():
self.env['runtime']['inputManager'].setExecuteDeviceGrab()
self.env['runtime']['inputManager'].handleDeviceGrab()
if self.isScreenChange():
self.changeBrailleScreen()
if not self.isSuspendingScreen(self.env['screen']['newTTY']):
self.update(eventData, 'onScreenChange')
self.env['screen']['lastScreenUpdate'] = time.time()
@ -74,9 +72,6 @@ class screenManager():
self.env['runtime']['inputManager'].handleDeviceGrab()
if not self.getCurrScreenIgnored():
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()
elif self.isCurrScreenIgnoredChanged():
self.env['runtime']['outputManager'].interruptOutput()
@ -145,14 +140,10 @@ class screenManager():
self.env['screen']['newContentText'][cursorLineEnd:] == self.env['screen']['oldContentText'][cursorLineEnd:]:
cursorLineStartOffset = cursorLineStart
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:
cursorLineEndOffset = cursorLineStart + self.env['screen']['newCursor']['x'] + 3
oldScreenText = self.env['screen']['oldContentText'][cursorLineStartOffset:cursorLineEndOffset]
# oldScreenText = re.sub(' +',' ',oldScreenText)
newScreenText = self.env['screen']['newContentText'][cursorLineStartOffset:cursorLineEndOffset]
#newScreenText = re.sub(' +',' ',newScreenText)
diff = self.differ.compare(oldScreenText, newScreenText)
diffList = list(diff)
typing = True
@ -222,20 +213,3 @@ class screenManager():
self.env['runtime']['screenDriver'].injectTextToScreen(text, screen)
except Exception as e:
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,
'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':{
'driver': 'vcsaDriver',
'encoding': 'auto',

View File

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