Moved flag parsing to launcher. Got read of fenrir-daemon and put everything into the fenrir launcher.

This commit is contained in:
Storm Dragon 2024-12-07 23:36:21 -05:00
parent 4c9e0bfd36
commit 9cdf80b313
4 changed files with 161 additions and 189 deletions

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

@ -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): def handleExecuteCommand(self, event):
if not event['Data']: 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