Compare commits
19 Commits
95170e7d39
...
2024.12.10
Author | SHA1 | Date | |
---|---|---|---|
|
8ef3d2856b | ||
|
8f28ee360a | ||
|
8782d53d03 | ||
|
7d276c95ea | ||
|
d70073274b | ||
|
f6d3baebc1 | ||
|
5a59ef6325 | ||
|
baa4c9a937 | ||
|
3757a1ceeb | ||
|
1696d62526 | ||
|
84514edc96 | ||
|
0e787c21ab | ||
|
9cdf80b313 | ||
|
24e82936a9 | ||
|
0f932fb93a | ||
|
4c9e0bfd36 | ||
|
353f9d3676 | ||
|
064172648f | ||
|
a93df78ffc |
67
README.md
67
README.md
@@ -5,7 +5,7 @@ It should run on any operating system. If you want to help, or write drivers to
|
||||
This software is licensed under the LGPL v3.
|
||||
|
||||
|
||||
# OS Requirements
|
||||
## OS Requirements
|
||||
|
||||
- Linux (ptyDriver, vcsaDriver, evdevDriver)
|
||||
- macOS (ptyDriver)
|
||||
@@ -13,15 +13,15 @@ This software is licensed under the LGPL v3.
|
||||
- Windows (ptyDriver)
|
||||
|
||||
|
||||
# Core Requirements
|
||||
## 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
|
||||
## Features, Drivers, Extras, Dependencies
|
||||
|
||||
# Input Drivers:
|
||||
### Input Drivers:
|
||||
1. "evdevDriver" input driver for linux evdev
|
||||
- python-evdev >=0.6.3 (This is commonly referred to as python3-evdev by your distribution)
|
||||
- python-pyudev
|
||||
@@ -33,7 +33,7 @@ This software is licensed under the LGPL v3.
|
||||
- python-pyte
|
||||
|
||||
|
||||
# Screen Drivers:
|
||||
### Screen Drivers:
|
||||
|
||||
1. "vcsaDriver" screen driver for linux VCSA devices
|
||||
- python-dbus
|
||||
@@ -46,27 +46,18 @@ This software is licensed under the LGPL v3.
|
||||
- python-pyte
|
||||
|
||||
|
||||
# Speech Drivers:
|
||||
### Speech Drivers:
|
||||
|
||||
1. "genericDriver" (default) speech driver for sound as subprocess:
|
||||
- espeak or espeak-ng
|
||||
2. "espeakDriver" speech driver for Espeak or Espeak-NG:
|
||||
- python-espeak
|
||||
3. "speechdDriver" speech driver for Speech-dispatcher:
|
||||
2. "speechdDriver" speech driver for Speech-dispatcher:
|
||||
- Speech-dispatcher
|
||||
- python-speechd
|
||||
4. "emacspeakDriver" speech driver for emacspeak
|
||||
3. "emacspeakDriver" speech driver for 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:
|
||||
- Sox
|
||||
@@ -75,7 +66,7 @@ This software is licensed under the LGPL v3.
|
||||
- GLib
|
||||
|
||||
|
||||
# Extras:
|
||||
## Extras:
|
||||
|
||||
1. spellchecker
|
||||
- python-pyenchant
|
||||
@@ -86,38 +77,32 @@ This software is licensed under the LGPL v3.
|
||||
- pyalsaaudio (needs libasound2's headers).
|
||||
|
||||
|
||||
# installation
|
||||
## installation
|
||||
|
||||
If there is a package for your distrobution of choice, please let us know so we can add it here.
|
||||
|
||||
- Archlinux: PKGBUILD in AUR (fenrir-git recommended)
|
||||
- PIP: sudo pip install fenrir-screenreader
|
||||
- Archlinux: PKGBUILD in AUR
|
||||
- fenrir: stable release
|
||||
- fenrir-git: Bleeding edge release
|
||||
- Manual:
|
||||
- install "espeak" and "sox" with your package manager
|
||||
- sudo pip install -r requirements.txt
|
||||
- run install.sh or uninstall.sh as root
|
||||
- you also can just run it from Git without installing:
|
||||
You can just run the following as root:
|
||||
if you are in Fenrir Git rootfolder:
|
||||
- install "espeak" and "sox" with your package manager
|
||||
- sudo pip install -r requirements.txt
|
||||
- run install.sh or uninstall.sh as root
|
||||
- You can also just run it from Git without installing:
|
||||
Requires root privileges
|
||||
|
||||
cd src/fenrir/
|
||||
sudo ./fenrir
|
||||
|
||||
Same thing, but use the daemon so the terminal is not blocked:
|
||||
|
||||
cd src/fenrir/
|
||||
sudo ./fenrir-daemon
|
||||
|
||||
Settings "settings.conf" is located in the "config" directory or after installation in /etc/fenrir/settings.
|
||||
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
|
||||
|
||||
|
||||
# Configure pulseaudio
|
||||
## Configure pulseaudio
|
||||
|
||||
Pulseaudio by default only plays sound for the user its currently running for. As fenrir is running as root, your local user does not hear the sound and speech produced by fenrir.
|
||||
for this fenrir provides a script to configure pulseaudio to stream the sound played as root to your local user. This is not a issue of fenrir but this is how pulseaudio works.
|
||||
@@ -130,7 +115,7 @@ just run the configuration script twice (once as user, once as root):
|
||||
The script is also located in the tools directory in git
|
||||
|
||||
|
||||
# Configure pipewire
|
||||
## Configure pipewire
|
||||
|
||||
Pipewire by default only plays sound for the user its currently running for. As fenrir is running as root, your local user does not hear the sound and speech produced by fenrir.
|
||||
for this fenrir provides a script to configure pipewire to stream the sound played as root to your local user. This is not a issue of fenrir but this is how pipewire works.
|
||||
@@ -142,10 +127,12 @@ just run the configuration script twice (once as user, once as root):
|
||||
|
||||
The script is also located in the tools directory in git
|
||||
|
||||
# localization
|
||||
## localization
|
||||
copy fenrir.mo translations file from fenrir/locale/your_language/LC_MESSAGES/fenrir.mo to /usr/share/locale/your_language/LC_MESSAGES/fenrir.mo
|
||||
|
||||
|
||||
# Documentation
|
||||
## Documentation and Support
|
||||
|
||||
Here is the [Fenrir Wiki](https://github.com/chrys87/fenrir/wiki). It is currently being updated, so keep checking back. Feel free to help with documentation.
|
||||
- Email list: [stormux+subscribe@groups.io](mailto:stormux+subscribe@groups.io?subject=subscribe) with the subject subscribe.
|
||||
- [Fenrir Wiki](https://git.stormux.org/storm/fenrir/wiki)
|
||||
- IRC: irc.stormux.org #stormux
|
||||
|
@@ -1,3 +1,3 @@
|
||||
#!/bin/sh
|
||||
[ -r ./conf ] && . ./conf
|
||||
exec fenrir
|
||||
exec fenrir -f
|
||||
|
@@ -1,18 +1,15 @@
|
||||
[Unit]
|
||||
Description=Fenrir screenreader
|
||||
Wants=systemd-udev-settle.service
|
||||
After=systemd-udev-settle.service sound.target
|
||||
After=systemd-udev-settle.service getty.target
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile=/var/run/fenrir.pid
|
||||
ExecStart=/usr/bin/fenrir-daemon
|
||||
ExecStart=/usr/bin/fenrir
|
||||
ExecReload=/usr/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
#Group=fenrirscreenreader
|
||||
#User=fenrirscreenreader
|
||||
|
||||
[Install]
|
||||
# start as early as possible in boot process
|
||||
#WantedBy=sound.target
|
||||
# start as soon the login prompt is available
|
||||
WantedBy=getty.target
|
||||
|
@@ -5,7 +5,7 @@ After=systemd-udev-settle.service sound.target
|
||||
[Service]
|
||||
Type=forking
|
||||
PIDFile=/var/run/fenrir.pid
|
||||
ExecStart=/usr/local/bin/fenrir-daemon
|
||||
ExecStart=/usr/local/bin/fenrir
|
||||
ExecReload=/usr/bin/kill -HUP $MAINPID
|
||||
Restart=always
|
||||
#Group=fenrirscreenreader
|
||||
|
@@ -1,232 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, sys
|
||||
|
||||
# default installation
|
||||
# core
|
||||
# speech: speech-dispatcher
|
||||
# sound: sox
|
||||
# braille: brltty:
|
||||
defaultInstallation = ['FenrirCore','vcsaDriver','dummyDriver (braille)','evdevDriver','genericDriver (speech)', 'genericDriver (sound)']
|
||||
currentInstallation = []
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
print('checking dependencys...')
|
||||
# CORE
|
||||
print('')
|
||||
print('fenrir core:')
|
||||
available = True
|
||||
try:
|
||||
from daemonize import Daemonize
|
||||
print('python3-daemonize: OK')
|
||||
except:
|
||||
print('python3-daemonize: FAIL')
|
||||
available = available and False
|
||||
@dataclass
|
||||
class Dependency:
|
||||
name: str
|
||||
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
|
||||
devicePaths: Optional[List[str]] = None # Device files to check
|
||||
|
||||
def check_dependency(dep: Dependency) -> bool:
|
||||
"""Check if a single dependency is satisfied."""
|
||||
isAvailable = True
|
||||
|
||||
try:
|
||||
import enchant
|
||||
print('pyenchant: OK')
|
||||
except:
|
||||
print('pyenchant: FAIL')
|
||||
available = available and False
|
||||
|
||||
if available:
|
||||
currentInstallation.append('FenrirCore')
|
||||
|
||||
# SCREEN
|
||||
print('--------------------')
|
||||
print('screen driver')
|
||||
# dummy and debug
|
||||
print('dummyDriver (screen): OK')
|
||||
currentInstallation.append('dummyDriver (screen)')
|
||||
if dep.pythonImports:
|
||||
for package in dep.pythonImports:
|
||||
try:
|
||||
moduleName = package.split('.')[0]
|
||||
__import__(moduleName)
|
||||
print(f'{package}: OK')
|
||||
except ImportError:
|
||||
print(f'{package}: FAIL')
|
||||
isAvailable = False
|
||||
|
||||
# VCSA (screen driver)
|
||||
print('vcsaDriver')
|
||||
available = True
|
||||
try:
|
||||
import dbus
|
||||
print('python3-dbus: OK')
|
||||
except:
|
||||
print('python3-dbus: FAIL')
|
||||
available = available and False
|
||||
if os.path.exists('/dev/vcsa'):
|
||||
print('VCSA Device: OK')
|
||||
else:
|
||||
print('VCSA Device: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('vcsaDriver')
|
||||
print('')
|
||||
# pty emulation (screen driver)
|
||||
print('ptyDriver')
|
||||
available = True
|
||||
try:
|
||||
import pyte
|
||||
print('pyte: OK')
|
||||
except:
|
||||
print('pyte: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('ptyDriver (screen)')
|
||||
if dep.checkCommands:
|
||||
for cmd in dep.checkCommands:
|
||||
if os.path.exists(f'/usr/bin/{cmd}') or os.path.exists(f'/bin/{cmd}'):
|
||||
print(f'{cmd}: OK')
|
||||
else:
|
||||
print(f'{cmd}: FAIL')
|
||||
isAvailable = False
|
||||
|
||||
if dep.devicePaths:
|
||||
for path in dep.devicePaths:
|
||||
if os.path.exists(path):
|
||||
print(f'{path}: OK')
|
||||
else:
|
||||
print(f'{path}: FAIL')
|
||||
isAvailable = False
|
||||
|
||||
return isAvailable
|
||||
|
||||
# Define all dependencies
|
||||
dependencyList = [
|
||||
# Core dependencies
|
||||
Dependency('FenrirCore', 'core', 'core',
|
||||
pythonImports=['daemonize', 'enchant']),
|
||||
|
||||
# BRAILLE
|
||||
print('--------------------')
|
||||
print('braille driver')
|
||||
# dummy and debug
|
||||
print('dummyDriver (braille): OK')
|
||||
currentInstallation.append('dummyDriver (braille)')
|
||||
print('debugDriver (braille): OK')
|
||||
currentInstallation.append('debugDriver (braille)')
|
||||
# brltty (braille driver)
|
||||
print('brlapiDriver')
|
||||
available = True
|
||||
try:
|
||||
import brlapi
|
||||
print('python3-brlapi: OK')
|
||||
except:
|
||||
print('python3-brlapi: FAIL')
|
||||
available = available and False
|
||||
# Screen drivers
|
||||
Dependency('DummyScreen', 'screen', 'dummyDriver'),
|
||||
Dependency('VCSA', 'screen', 'vcsaDriver',
|
||||
pythonImports=['dbus'],
|
||||
devicePaths=['/dev/vcsa']),
|
||||
Dependency('PTY', 'screen', 'ptyDriver',
|
||||
pythonImports=['pyte']),
|
||||
|
||||
# Input drivers
|
||||
Dependency('DummyInput', 'input', 'dummyDriver'),
|
||||
Dependency('DebugInput', 'input', 'debugDriver'),
|
||||
Dependency('Evdev', 'input', 'evdevDriver',
|
||||
pythonImports=['evdev', 'evdev.InputDevice', 'evdev.UInput', 'pyudev']),
|
||||
Dependency('PTYInput', 'input', 'ptyDriver',
|
||||
pythonImports=['pyte']),
|
||||
|
||||
# Sound drivers
|
||||
Dependency('DummySound', 'sound', 'dummyDriver'),
|
||||
Dependency('DebugSound', 'sound', 'debugDriver'),
|
||||
Dependency('GenericSound', 'sound', 'genericDriver',
|
||||
checkCommands=['play', 'sox']),
|
||||
Dependency('GStreamer', 'sound', 'gstreamerDriver',
|
||||
pythonImports=['gi', 'gi.repository.GLib', 'gi.repository.Gst']),
|
||||
|
||||
# Speech drivers
|
||||
Dependency('DummySpeech', 'speech', 'dummyDriver'),
|
||||
Dependency('DebugSpeech', 'speech', 'debugDriver'),
|
||||
Dependency('Speechd', 'speech', 'speechdDriver',
|
||||
pythonImports=['speechd']),
|
||||
Dependency('GenericSpeech', 'speech', 'genericDriver',
|
||||
checkCommands=['espeak-ng'])
|
||||
]
|
||||
|
||||
defaultModules = {
|
||||
'FenrirCore',
|
||||
'VCSA',
|
||||
'Evdev',
|
||||
'GenericSpeech',
|
||||
'GenericSound'
|
||||
}
|
||||
|
||||
def check_all_dependencies():
|
||||
print('Checking dependencies...\n')
|
||||
availableModules = []
|
||||
|
||||
if available:
|
||||
currentInstallation.append('brlapiDriver')
|
||||
# INPUT
|
||||
print('--------------------')
|
||||
print('input driver')
|
||||
# dummy and debug
|
||||
print('dummyDriver (input): OK')
|
||||
currentInstallation.append('dummyDriver (input)')
|
||||
print('debugDriver (input): OK')
|
||||
currentInstallation.append('debugDriver (input)')
|
||||
# evdev (input driver)
|
||||
print('evdevDriver')
|
||||
available = True
|
||||
try:
|
||||
import evdev
|
||||
from evdev import InputDevice, UInput
|
||||
print('python3-evdev: OK')
|
||||
except:
|
||||
print('python3-evdev: FAIL')
|
||||
available = available and False
|
||||
try:
|
||||
import pyudev
|
||||
print('python3-pyudev: OK')
|
||||
except:
|
||||
print('python3-pyudev: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('evdevDriver')
|
||||
# pty emulation (input driver)
|
||||
print('')
|
||||
print('ptyDriver')
|
||||
available = True
|
||||
try:
|
||||
import pyte
|
||||
print('pyte: OK')
|
||||
except:
|
||||
print('pyte: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('ptyDriver (Input)')
|
||||
# SOUND
|
||||
print('--------------------')
|
||||
print('sound driver')
|
||||
# dummy and debug
|
||||
print('dummyDriver (sound): OK')
|
||||
currentInstallation.append('dummyDriver (sound)')
|
||||
print('debugDriver (sound): OK')
|
||||
currentInstallation.append('debugDriver (sound)')
|
||||
print('genericDriver (uses sox by default)')
|
||||
available = True
|
||||
if os.path.exists('/usr/bin/play') and os.path.exists('/usr/bin/sox'):
|
||||
print('sox: OK')
|
||||
else:
|
||||
print('sox: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('genericDriver (sound)')
|
||||
print('')
|
||||
# gstreamer (sound driver)
|
||||
print('gstreamerDriver')
|
||||
available = True
|
||||
try:
|
||||
import gi
|
||||
print('gi: OK')
|
||||
except:
|
||||
print('gi: FAIL')
|
||||
available = available and False
|
||||
try:
|
||||
from gi.repository import GLib
|
||||
print('gi GLib: OK')
|
||||
except:
|
||||
print('gi GLib: FAIL')
|
||||
available = available and False
|
||||
try:
|
||||
gi.require_version('Gst', '1.0')
|
||||
from gi.repository import Gst
|
||||
print('gi Gst: OK')
|
||||
except:
|
||||
print('gi Gst: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('gstreamerDriver')
|
||||
# Group dependencies by type for organized output
|
||||
for depType in ['core', 'screen', 'input', 'sound', 'speech']:
|
||||
print(f'{depType.upper()} DRIVERS')
|
||||
print('-' * 20)
|
||||
|
||||
depsOfType = [d for d in dependencyList if d.depType == depType]
|
||||
for dep in depsOfType:
|
||||
print(f'\nChecking {dep.name}:')
|
||||
if check_dependency(dep):
|
||||
availableModules.append(dep.name)
|
||||
print('')
|
||||
|
||||
# SPEECH
|
||||
print('--------------------')
|
||||
print('speech driver')
|
||||
# dummy and debug
|
||||
print('dummyDriver (speech): OK')
|
||||
currentInstallation.append('dummyDriver (speech)')
|
||||
print('debugDriver (speech): OK')
|
||||
currentInstallation.append('debugDriver (speech)')
|
||||
# speechd (speech driver)
|
||||
print('speechdDriver')
|
||||
available = True
|
||||
try:
|
||||
import speechd
|
||||
print('python3-speechd: OK')
|
||||
except:
|
||||
print('python3-speechd: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('speechdDriver')
|
||||
print('')
|
||||
# espeak (speech driver)
|
||||
print('espeakDriver')
|
||||
available = True
|
||||
try:
|
||||
from espeak import espeak
|
||||
print('python3-espeak: OK')
|
||||
except:
|
||||
print('python3-espeak: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('espeakDriver')
|
||||
print('genericDriver (uses espeak-ng by default)')
|
||||
available = True
|
||||
if os.path.exists('/usr/bin/espeak-ng') or os.path.exists('/bin/espeak-ng'):
|
||||
print('espeak-ng: OK')
|
||||
else:
|
||||
print('espeak-ng: FAIL')
|
||||
available = available and False
|
||||
if available:
|
||||
currentInstallation.append('genericDriver (speech)')
|
||||
print_summary(availableModules)
|
||||
|
||||
# SUMMERY
|
||||
print('====================')
|
||||
available = True
|
||||
missing = []
|
||||
for element in defaultInstallation:
|
||||
if not element in currentInstallation:
|
||||
available = False
|
||||
missing.append(element)
|
||||
if available:
|
||||
print('Default Setup: OK')
|
||||
else:
|
||||
print('Default Setup: FAIL')
|
||||
print('Unavailable Default Modules:')
|
||||
for e in missing:
|
||||
print(e)
|
||||
print('you may need to install the missing dependencys for the modules above or reconfigure fenrir to not use them')
|
||||
print('')
|
||||
print('Available Modules:')
|
||||
for element in currentInstallation:
|
||||
print(element)
|
||||
def print_summary(availableModules: List[str]):
|
||||
print('=' * 20)
|
||||
print('SUMMARY')
|
||||
print('=' * 20)
|
||||
|
||||
missingModules = defaultModules - set(availableModules)
|
||||
if missingModules:
|
||||
print('Default Setup: FAIL')
|
||||
print('\nUnavailable Default Modules:')
|
||||
for module in missingModules:
|
||||
print(f'- {module}')
|
||||
print('\nYou may need to install the missing dependencies for the modules above or reconfigure fenrir to not use them.')
|
||||
else:
|
||||
print('Default Setup: OK')
|
||||
|
||||
print('\nAvailable Modules:')
|
||||
for module in availableModules:
|
||||
print(f'- {module}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_all_dependencies()
|
||||
|
@@ -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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# needs pandoc and php installed
|
||||
|
||||
# remove old files
|
||||
|
3
setup.py
3
setup.py
@@ -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')
|
||||
|
109
src/fenrir
109
src/fenrir
@@ -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()
|
||||
main()
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
@@ -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()
|
@@ -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')
|
@@ -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)
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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):
|
||||
|
@@ -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)
|
@@ -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):
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
||||
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
|
||||
@@ -139,73 +82,59 @@ class fenrirManager():
|
||||
self.environment['runtime']['inputManager'].handleDeviceGrab()
|
||||
|
||||
if self.environment['input']['keyForeward'] > 0:
|
||||
self.environment['input']['keyForeward'] -=1
|
||||
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']:
|
||||
|
||||
def handleExecuteCommand(self, event):
|
||||
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 handleHeartBeat(self, event):
|
||||
self.environment['runtime']['commandManager'].executeDefaultTrigger('onHeartBeat', force=True)
|
||||
|
||||
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)
|
||||
self.singleKeyCommand = len(self.environment['input']['currInput']) == 1
|
||||
|
||||
if not(self.singleKeyCommand and self.environment['runtime']['inputManager'].noKeyPressed()):
|
||||
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,7 +182,8 @@ class fenrirManager():
|
||||
if self.singleKeyCommand:
|
||||
self.environment['runtime']['eventManager'].putToEventQueue(fenrirEventType.ExecuteCommand, self.command)
|
||||
self.command = ''
|
||||
def setProcessName(self, name = 'fenrir'):
|
||||
|
||||
def setProcessName(self, name='fenrir'):
|
||||
"""Attempts to set the process name to 'fenrir'."""
|
||||
try:
|
||||
from setproctitle import setproctitle
|
||||
@@ -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
|
||||
|
||||
|
@@ -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,27 +11,26 @@ 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'].getSetting('speech', 'driver'), 'speechDriver')
|
||||
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')
|
||||
self.env['runtime']['settingsManager'].loadDriver(
|
||||
self.env['runtime']['settingsManager'].getSetting('speech', 'driver'), 'speechDriver')
|
||||
self.env['runtime']['settingsManager'].loadDriver(
|
||||
self.env['runtime']['settingsManager'].getSetting('sound', 'driver'), 'soundDriver')
|
||||
|
||||
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:
|
||||
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):
|
||||
self.env['runtime']['debug'].writeDebugOut("soundIcon found" ,debug.debugLevel.INFO)
|
||||
self.env['runtime']['debug'].writeDebugOut("soundIcon found", debug.debugLevel.INFO)
|
||||
return
|
||||
if (len(text) > 1) and (text.strip(string.whitespace) == ''):
|
||||
return
|
||||
@@ -40,35 +39,31 @@ class outputManager():
|
||||
if self.playSoundIcon('capital', False):
|
||||
toAnnounceCapital = False
|
||||
self.lastEcho = text
|
||||
self.speakText(text, interrupt, ignorePunctuation,toAnnounceCapital)
|
||||
if flush:
|
||||
if brailleAlternative != '':
|
||||
brlText = brailleAlternative
|
||||
else:
|
||||
brlText = text
|
||||
self.brailleText(brlText, flush)
|
||||
self.speakText(text, interrupt, ignorePunctuation, toAnnounceCapital)
|
||||
|
||||
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)
|
||||
self.env['runtime']['debug'].writeDebugOut("Speech disabled in outputManager.speakText", debug.debugLevel.INFO)
|
||||
return
|
||||
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
|
||||
if interrupt:
|
||||
self.interruptOutput()
|
||||
try:
|
||||
self.env['runtime']['speechDriver'].setLanguage(self.env['runtime']['settingsManager'].getSetting('speech', 'language'))
|
||||
except Exception as e:
|
||||
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("setting speech language in outputManager.speakText", debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
|
||||
|
||||
try:
|
||||
self.env['runtime']['speechDriver'].setVoice(self.env['runtime']['settingsManager'].getSetting('speech', 'voice'))
|
||||
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(str(e),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)
|
||||
|
||||
try:
|
||||
if announceCapital:
|
||||
@@ -76,199 +71,55 @@ class outputManager():
|
||||
else:
|
||||
self.env['runtime']['speechDriver'].setPitch(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch'))
|
||||
except Exception as e:
|
||||
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("setting speech pitch in outputManager.speakText", debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
|
||||
|
||||
try:
|
||||
self.env['runtime']['speechDriver'].setRate(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate'))
|
||||
except Exception as e:
|
||||
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("setting speech rate in outputManager.speakText", debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
|
||||
|
||||
try:
|
||||
self.env['runtime']['speechDriver'].setModule(self.env['runtime']['settingsManager'].getSetting('speech', 'module'))
|
||||
except Exception as e:
|
||||
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("setting speech module in outputManager.speakText", debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
|
||||
|
||||
try:
|
||||
self.env['runtime']['speechDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume'))
|
||||
except Exception as e:
|
||||
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("setting speech volume in outputManager.speakText ", debug.debugLevel.ERROR)
|
||||
self.env['runtime']['debug'].writeDebugOut(str(e), debug.debugLevel.ERROR)
|
||||
|
||||
try:
|
||||
if self.env['runtime']['settingsManager'].getSettingAsBool('general', 'newLinePause'):
|
||||
cleanText = text.replace('\n',' , ')
|
||||
cleanText = text.replace('\n', ' , ')
|
||||
else:
|
||||
cleanText = text.replace('\n',' ')
|
||||
cleanText = text.replace('\n', ' ')
|
||||
|
||||
cleanText = self.env['runtime']['textManager'].replaceHeadLines(cleanText)
|
||||
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']['debug'].writeDebugOut("Speak: "+ cleanText,debug.debugLevel.INFO)
|
||||
self.env['runtime']['debug'].writeDebugOut("Speak: "+ cleanText, debug.debugLevel.INFO)
|
||||
except Exception as e:
|
||||
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("\"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()
|
||||
self.env['runtime']['debug'].writeDebugOut("Interrupt speech",debug.debugLevel.INFO)
|
||||
self.env['runtime']['debug'].writeDebugOut("Interrupt speech", debug.debugLevel.INFO)
|
||||
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):
|
||||
def playSoundIcon(self, soundIcon='', interrupt=True):
|
||||
if soundIcon == '':
|
||||
return False
|
||||
soundIcon = soundIcon.upper()
|
||||
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
|
||||
|
||||
try:
|
||||
@@ -278,40 +129,40 @@ class outputManager():
|
||||
return False
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume'))
|
||||
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:
|
||||
self.env['runtime']['soundDriver'].playSoundFile(self.env['soundIcons'][soundIcon], interrupt)
|
||||
return True
|
||||
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
|
||||
|
||||
def playFrequence(self, frequence, duration, interrupt=True):
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
self.env['runtime']['soundDriver'].setVolume(self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume'))
|
||||
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
|
||||
try:
|
||||
adjustVolume = 1.0 - (frequence / 20000)
|
||||
@@ -324,7 +175,7 @@ class outputManager():
|
||||
self.env['runtime']['soundDriver'].playFrequence(frequence, duration, adjustVolume, interrupt)
|
||||
return True
|
||||
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
|
||||
@@ -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)
|
||||
|
||||
|
@@ -12,7 +12,6 @@ runtimeData = {
|
||||
'soundDriver': None,
|
||||
'inputDriver': None,
|
||||
'remoteDriver': None,
|
||||
'brailleDriver': None,
|
||||
'inputManager': None,
|
||||
'commandManager': None,
|
||||
'screenManager': None,
|
||||
|
@@ -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
|
||||
@@ -221,21 +212,4 @@ class screenManager():
|
||||
try:
|
||||
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)
|
||||
self.env['runtime']['debug'].writeDebugOut('screenManager:injectTextToScreen ' + str(e),debug.debugLevel.ERROR)
|
||||
|
@@ -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',
|
||||
|
@@ -4,5 +4,5 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
version = "2024.12.07"
|
||||
codeName = "testing"
|
||||
version = "2024.12.10"
|
||||
codeName = "stable"
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user