diff --git a/TODO b/TODO deleted file mode 100644 index 39a5aad9..00000000 --- a/TODO +++ /dev/null @@ -1,3 +0,0 @@ -V2.0 - -Cleanup folders and config files. diff --git a/autostart/systemd/Arch/fenrir.service b/autostart/systemd/Arch/fenrir.service index dc037148..737c7cd5 100644 --- a/autostart/systemd/Arch/fenrir.service +++ b/autostart/systemd/Arch/fenrir.service @@ -4,12 +4,10 @@ Wants=systemd-udev-settle.service After=systemd-udev-settle.service getty.target [Service] Type=forking -PIDFile=/var/run/fenrir.pid +PIDFile=/run/fenrir.pid ExecStart=/usr/bin/fenrir ExecReload=/usr/bin/kill -HUP $MAINPID Restart=always -#Group=fenrirscreenreader -#User=fenrirscreenreader [Install] WantedBy=getty.target diff --git a/autostart/systemd/Debian/fenrir.service b/autostart/systemd/Debian/fenrir.service index ff278223..fdaee789 100644 --- a/autostart/systemd/Debian/fenrir.service +++ b/autostart/systemd/Debian/fenrir.service @@ -4,7 +4,7 @@ Wants=systemd-udev-settle.service After=systemd-udev-settle.service sound.target [Service] Type=forking -PIDFile=/var/run/fenrir.pid +PIDFile=/run/fenrir.pid ExecStart=/usr/local/bin/fenrir ExecReload=/usr/bin/kill -HUP $MAINPID Restart=always diff --git a/bugs b/bugs index 5b461379..67cc2421 100644 --- a/bugs +++ b/bugs @@ -1,5 +1,5 @@ -Please report Bugs and feature requests to: -https://github.com/chrys87/fenrir/issues +Please report bugs and feature requests to: +https://git.stormux.org/storm/fenrir/issues For bugs, please provide a debug file that shows the issue. How to create a debug file: diff --git a/docs/fenrir.adoc b/docs/fenrir.adoc index 1f2a5b4f..09f557b9 100644 --- a/docs/fenrir.adoc +++ b/docs/fenrir.adoc @@ -7,14 +7,14 @@ configurable and easy to customize and extend. === Credit and intended audience This document is just a customization for Slint of the genuine -https://github.com/chrys87/fenrir/blob/master/docu/user.txt[Fenrir User +https://git.stormux.org/storm/fenrir/src/branch/master/docs/user.txt[Fenrir User Manual] motly written by Chrys, main developer of Fenrir. It has been adapted to its intended audience: end users of Fenrir on Slint where it is already installed, thus concentrates on its setting and usage. You will find more information about its features, installation and how customize and troubleshoot it and contribute to its -development on https://github.com/chrys87/fenrir[the Fenrir Git +development on https://git.stormux.org/storm/fenrir[the Fenrir Git repository]. === Getting started with Fenrir @@ -2193,9 +2193,9 @@ settings.conf). Commands are python files with a special scheme. You can assign them to a shortcut using the filename without an extension or place them in a hook trigger like OnInput or OnScreenChange. For further information see developer guide. Good Examples: -https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/date.py["date.py"] +https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/date.py["date.py"] (announce the Date), -https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/shut_up.py["shut_up.py"] +https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/shut_up.py["shut_up.py"] (interrupt output) the basic scheme for a command is as follows: .... @@ -2218,7 +2218,7 @@ class command(): pass .... -* https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/command_template.py[Template +* https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/command_template.py[Template lives here] * The class needs to have the name "command". * "initialize" is running once whilst loading the command. @@ -2276,7 +2276,7 @@ root. === Bugreports and feature requests Please report Bugs and feature requests to: -https://github.com/chrys87/fenrir/issues +https://git.stormux.org/storm/fenrir/issues for bugs please provide a link:#Howto create a debug file[debug] file that shows the issue. diff --git a/docs/user.txt b/docs/user.txt index ce8e7780..425e035a 100644 --- a/docs/user.txt +++ b/docs/user.txt @@ -160,7 +160,7 @@ For Arch there are PKGBUILDs in the AUR: - Download the latest stable version from the [[https://linux-a11y.org/index.php?page=fenrir-screenreader|Fenrir-Project]] site. - Unpack the archive - - Check the needed Dependencys by running [[https://github.com/chrys87/fenrir/blob/master/check-dependencies.py|check-dependencys.py]] script + - Check the needed Dependencys by running [[https://git.stormux.org/storm/fenrir/src/branch/master/check-dependencies.py|check-dependencys.py]] script - install the missing dependencies an standard installation requires the following: * python3 >= 3.3 (and all the following is needed for python3 ) * python3-speechd (screen) @@ -171,7 +171,7 @@ For Arch there are PKGBUILDs in the AUR: * python3-pyenchant (spellchecker) * your language for aspell (aspell-) (spellchecker) * sox (sound) - * For an individual installation see [[#Support and Requirements|Support and Requirements]] or consult the [[https://github.com/chrys87/fenrir/blob/master/README.md|Readme]]) + * For an individual installation see [[#Support and Requirements|Support and Requirements]] or consult the [[https://git.stormux.org/storm/fenrir/src/branch/master/README.md|Readme]]) - run "install.sh" as root this installs Fenrir as the following @@ -185,7 +185,7 @@ to remove Fenrir just run uninstall.sh as root if you want to get the latest code you can use git to get a development snapshot: - git clone https://github.com/chrys87/fenrir.git + git clone https://git.stormux.org/storm/fenrir.git ===== Auto Start ===== @@ -1270,7 +1270,7 @@ File: ''/usr/share/fenrirscreenreader/scripts/helloWorld__-__key_h.sh'': ===== Commands ===== You can place your own commands in "/usr/share/fenrirscreenreader/commands" (path is configurable in settings.conf). Commands are python files with a special scheme. You can assign them to a shortcut using the filename without an extension or place them in a hook trigger like OnInput or OnScreenChange. For further information see developer guide. -Good Examples: [[https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/date.py|"date.py"]] (announce the Date), [[https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/shut_up.py|"shut_up.py"]] (interrupt output) +Good Examples: [[https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/date.py|"date.py"]] (announce the Date), [[https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/shut_up.py|"shut_up.py"]] (interrupt output) the basic scheme for a command is as follows: from core import debug @@ -1289,7 +1289,7 @@ the basic scheme for a command is as follows: def setCallback(self, callback): pass - * [[https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/command_template.py|Template lives here]] + * [[https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/command_template.py|Template lives here]] * The class needs to have the name "command". * "initialize" is running once whilst loading the command. * "shutdown" is running on unload like the command (quit fenrir) @@ -1319,7 +1319,7 @@ the basic scheme for a command is as follows: - You can test if speech-dispatcher works by invoking it as root\\ ''sudo spd-say "hello world"'' ===== Bugreports and feature requests ===== Please report Bugs and feature requests to: -[[https://github.com/chrys87/fenrir/issues|https://github.com/chrys87/fenrir/issues]] +[[https://git.stormux.org/storm/fenrir/issues|https://git.stormux.org/storm/fenrir/issues]] for bugs please provide a [[#Howto create a debug file|debug]] file that shows the issue. ==== How-to create a debug file ==== diff --git a/install.sh b/install.sh index 46a721e6..4c0eefae 100755 --- a/install.sh +++ b/install.sh @@ -56,7 +56,7 @@ To test Fenrir: sudo fenrir To have Fenrir start on system boot using systemd: -download service file: https://raw.githubusercontent.com/chrys87/fenrir/master/autostart/systemd/Arch/fenrir.service +download service file: https://git.stormux.org/storm/fenrir/raw/branch/master/autostart/systemd/Arch/fenrir.service move the service file to: /etc/systemd/system/fenrir.service sudo systemctl enable fenrir diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index a8c93db5..c5b56b3e 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.11.23" +version = "2025.11.24" code_name = "master" diff --git a/tools/configure_fenrir.py b/tools/configure_fenrir.py deleted file mode 100755 index 26d2eb47..00000000 --- a/tools/configure_fenrir.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python3 - -import os -import sys -import configparser -import dialog -import subprocess -import time -import select -import tempfile -from typing import Dict, List, Optional, Tuple - -class FenrirConfigTool: - def __init__(self): - os.environ['DIALOGOPTS'] = '--no-lines --visit-items' - self.tui = dialog.Dialog(dialog="dialog") - self.settingsFile = '/etc/fenrirscreenreader/settings/settings.conf' - - # Check if we need to re-run with elevated privileges - if not self.check_root(): - self.escalate_privileges() - sys.exit(0) - - self.instructions = { - 'menu': "\nNavigation: Use Up/Down arrows to move, Enter to select, Escape to go back", - 'radiolist': "\nNavigation: Use Up/Down arrows to move, Space to select option, Enter to confirm, Escape to cancel", - 'inputbox': "\nEnter your value and press Enter to confirm, or Escape to cancel" - } - - # Configuration presets and help text from original FenrirConfig - self.presetOptions = { - 'sound.driver': ['genericDriver', 'gstreamerDriver'], - 'speech.driver': ['speechdDriver', 'genericDriver'], - 'screen.driver': ['vcsaDriver', 'dummyDriver', 'ptyDriver', 'debugDriver'], - 'keyboard.driver': ['evdevDriver', 'dummyDriver'], - 'remote.driver': ['unixDriver', 'tcpDriver'], - 'keyboard.charEchoMode': ['0', '1', '2'], - 'general.punctuationLevel': ['none', 'some', 'most', 'all'], - 'general.debugMode': ['File', 'Print'] - } - - self.helpText = { - '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' - } - - def check_root(self) -> bool: - return os.geteuid() == 0 - - def find_privilege_escalation_tool(self) -> Optional[str]: - for tool in ['sudo', 'doas']: - if subprocess.run(['which', tool], stdout=subprocess.PIPE).returncode == 0: - return tool - return None - - def escalate_privileges(self): - tool = self.find_privilege_escalation_tool() - if not tool: - self.tui.msgbox("Error: Neither sudo nor doas found. Please run this script as root.") - sys.exit(1) - - try: - scriptPath = os.path.abspath(sys.argv[0]) - command = [tool, sys.executable, scriptPath] + sys.argv[1:] - os.execvp(tool, command) - except Exception as e: - self.tui.msgbox(f"Error escalating privileges: {str(e)}") - sys.exit(1) - - def is_boolean_option(self, value: str) -> bool: - """Check if the current value is likely a boolean option""" - return value.lower() in ['true', 'false'] - - def validate_input(self, section: str, option: str, value: str) -> tuple[bool, str]: - """Validate user input based on the option type and constraints""" - try: - if option.endswith('volume') or option.endswith('rate') or option.endswith('pitch'): - floatVal = float(value) - if not 0 <= floatVal <= 1.0: - return False, "Value must be between 0 and 1.0" - return True, value - except ValueError: - return False, "Invalid number format" - - def get_value_with_presets(self, section: str, option: str, currentValue: str) -> Optional[str]: - """Get value using appropriate input method based on option type""" - key = f"{section}.{option}" - - # Handle boolean options - if self.is_boolean_option(currentValue): - choices = [ - ('True', '', currentValue.lower() == 'true'), - ('False', '', currentValue.lower() == 'false') - ] - code, tag = self.tui.radiolist( - f"Select value for '{option}'" + self.instructions['radiolist'], - choices=choices - ) - return tag if code == self.tui.OK else None - - # Handle other preset options - elif key in self.presetOptions: - choices = [(opt, "", opt == currentValue) for opt in self.presetOptions[key]] - code, tag = self.tui.radiolist( - f"Select value for '{option}'" + - (f"\n{self.helpText[key]}" if key in self.helpText else "") + - self.instructions['radiolist'], - choices=choices - ) - return tag if code == self.tui.OK else None - - # Handle free-form input - else: - helpText = self.helpText.get(key, "") - code, value = self.tui.inputbox( - f"Enter value for '{option}'" + - (f"\n{helpText}" if helpText else "") + - self.instructions['inputbox'], - init=currentValue - ) - if code == self.tui.OK: - isValid, message = self.validate_input(section, option, value) - if not isValid: - self.tui.msgbox(f"Invalid input: {message}") - return None - return value - return None - - def run_command(self, cmd: List[str], needsRoot: bool = False) -> Optional[str]: - try: - if needsRoot and not self.check_root(): - tool = self.find_privilege_escalation_tool() - if tool: - cmd = [tool] + cmd - result = subprocess.run(cmd, capture_output=True, text=True) - return result.stdout.strip() if result.returncode == 0 else None - except Exception as e: - self.tui.msgbox(f"Error running command {' '.join(cmd)}: {e}") - return None - - def get_speechd_modules(self) -> List[str]: - output = self.run_command(['spd-say', '-O'], True) - if output: - lines = output.split('\n') - return [line.strip() for line in lines[1:] if line.strip()] - return [] - - def process_espeak_voice(self, voiceLine: str) -> Optional[str]: - parts = [p for p in voiceLine.split() if p] - if len(parts) < 2: - return None - langCode = parts[-2].lower() - variant = parts[-1].lower() - return f"{langCode}+{variant}" if variant and variant != 'none' else langCode - - def get_module_voices(self, moduleName: str) -> List[str]: - output = self.run_command(['spd-say', '-o', moduleName, '-L'], True) - if output: - lines = output.split('\n') - voices = [] - for line in lines[1:]: - if not line.strip(): - continue - if moduleName.lower() == 'espeak-ng': - voice = self.process_espeak_voice(line) - if voice: - voices.append(voice) - else: - voices.append(line.strip()) - return voices - return [] - - def configure_speech(self) -> None: - moduleList = self.get_speechd_modules() - if not moduleList: - self.tui.msgbox("No speech-dispatcher modules found!") - return - - code, moduleChoice = self.tui.menu( - "Select speech module:" + self.instructions['menu'], - choices=[(module, "") for module in moduleList] - ) - - if code != self.tui.OK: - return - - voiceList = self.get_module_voices(moduleChoice) - if not voiceList: - self.tui.msgbox(f"No voices found for module {moduleChoice}") - return - - code, voice = self.tui.menu( - f"Select voice for {moduleChoice}:" + self.instructions['menu'], - choices=[(v, "") for v in voiceList] - ) - - if code != self.tui.OK: - return - - # Test voice configuration - if self.test_voice(moduleChoice, voice): - config = configparser.ConfigParser(interpolation=None) - config.read(self.settingsFile) - - if 'speech' not in config: - config['speech'] = {} - - config['speech'].update({ - 'driver': 'speechdDriver', - 'module': moduleChoice, - 'voice': voice, - 'enabled': 'True', - 'rate': '0.25', - 'pitch': '0.5', - 'volume': '1.0' - }) - - with open(self.settingsFile, 'w') as configfile: - config.write(configfile) - - self.tui.msgbox("Speech configuration updated successfully!\nPlease restart Fenrir for changes to take effect.") - - def test_voice(self, moduleName: str, voiceName: str) -> bool: - testMessage = "If you hear this message, press Enter within 30 seconds to confirm." - try: - process = subprocess.Popen( - ['spd-say', '-o', moduleName, '-y', voiceName, testMessage] - ) - - code = self.tui.pause( - "Waiting for voice test...\n" - "Press Enter if you hear the message, or wait for timeout.", - 30 - ) - - process.terminate() - return code == self.tui.OK - - except Exception as e: - self.tui.msgbox(f"Error testing voice: {e}") - return False - - def edit_general_config(self) -> None: - while True: - config = configparser.ConfigParser(interpolation=None) - config.read(self.settingsFile) - sections = config.sections() - - code, section = self.tui.menu( - "Select a section to configure:" + self.instructions['menu'], - choices=[(s, "") for s in sections] + [("Go Back", "")] - ) - - if code != self.tui.OK or section == "Go Back": - break - - while True: - options = config.options(section) - choices = [(o, f"Current: {config.get(section, o)}") for o in options] - choices.append(("Go Back", "")) - - code, option = self.tui.menu( - f"Select option to edit in '{section}':" + self.instructions['menu'], - choices=choices - ) - - if code != self.tui.OK or option == "Go Back": - break - - currentValue = config.get(section, option) - newValue = self.get_value_with_presets(section, option, currentValue) - - if newValue is not None and newValue != currentValue: - config.set(section, option, newValue) - with open(self.settingsFile, 'w') as configfile: - config.write(configfile) - self.tui.msgbox("Setting updated successfully.") - - def run(self): - while True: - code, choice = self.tui.menu( - "Fenrir Configuration Tool" + self.instructions['menu'], - choices=[ - ("speech-dispatcher", "Configure module and voice"), - ("Advanced", "Edit Fenrir settings"), - ("Exit", "") - ] - ) - - if code != self.tui.OK or choice == "Exit": - break - - if choice == "speech-dispatcher": - self.configure_speech() - elif choice == "Advanced": - self.edit_general_config() - -if __name__ == "__main__": - configTool = FenrirConfigTool() - try: - configTool.run() - except Exception as e: - print(f"Unexpected error occurred: {str(e)}", file=sys.stderr) - sys.exit(1) diff --git a/tools/cycle-pulseaudio-sink.sh b/tools/cycle-pulseaudio-sink.sh deleted file mode 100755 index c897426c..00000000 --- a/tools/cycle-pulseaudio-sink.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -sinks=(`pacmd list-sinks | sed -n -e 's/\**[[:space:]]index:[[:space:]]\([[:digit:]]\)/\1/p'`) -sinks_count=${#sinks[@]} -active_sink_index=`pacmd list-sinks | sed -n -e 's/\*[[:space:]]index:[[:space:]]\([[:digit:]]\)/\1/p'` -newSink=${sinks[0]} -ord=0 - -while [ $ord -lt $sinks_count ]; -do - echo ${sinks[$ord]} - if [ ${sinks[$ord]} -gt $active_sink_index ] ; then - newSink=${sinks[$ord]} - break - fi - let ord++ -done - -pactl list short sink-inputs|while read stream; do - streamId=$(echo $stream|cut '-d ' -f1) - echo "moving stream $streamId" - pactl move-sink-input "$streamId" "$newSink" -done -pacmd set-default-sink "$newSink" - -#https://unix.stackexchange.com/questions/65246/change-pulseaudio-input-output-from-shell diff --git a/tools/deviceInfo.py b/tools/deviceInfo.py index 6aff7eb2..a7a96180 100755 --- a/tools/deviceInfo.py +++ b/tools/deviceInfo.py @@ -1,4 +1,4 @@ from pyudev import Context context = Context() for device in context.list_devices(subsystem='input'): - '{0} - {1}'.format(device.sys_name, device.device_type) + print('{0} - {1}'.format(device.sys_name, device.device_type)) diff --git a/tools/make_fenrir_user.sh b/tools/make_fenrir_user.sh deleted file mode 100755 index d97609f2..00000000 --- a/tools/make_fenrir_user.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash - - # Make sure this script is ran as root -if [[ "$(whoami)" != "root" ]]; then - echo "Please run $0 with oot privileges." - exit 1 -fi - -# This script checks for, and creates if needed, the fenrirscreenreader user. - -# Find out which group to use for uinput -uinput="$(stat -c '%G' /dev/uinput | grep -v root)" -if ! [[ "$uinput" =~ ^[a-zA-Z]+$ ]]; then - groupadd -r uinput - chown root:uinput /dev/uinput -fi - -# find out which group to use for /dev/input. -input="$(stat -c '%G' /dev/input/* | grep -v root | head -1)" -if ! [[ "$input" =~ ^[a-zA-Z]+$ ]]; then - # Create the input group - groupadd --system input - echo 'KERNEL=="event*", NAME="input/%k", MODE="660", GROUP="input"' >> /etc/udev/rules.d/99-input.rules - input="input" -fi - -# find out which group to use for /dev/tty. -tty="$(stat -c '%G' /dev/tty | grep -v root)" -if ! [[ "$tty" =~ ^[a-zA-Z]+$ ]]; then - # Create the tty group - groupadd --system tty - echo 'KERNEL=="event*", NAME="tty/%k", MODE="660", GROUP="tty"' >> /etc/udev/rules.d/99-tty.rules - tty="tty" -fi - -# Add fenrirscreenreader -id fenrirscreenreader &> /dev/null || - useradd -m -d /var/fenrirscreenreader -r -G $input,$tty,$uinput -s /bin/nologin -U fenrirscreenreader - -#configure directory structure. -mkdir -p /var/log/fenrirscreenreader /etc/fenrirscreenreader - -# Set directory ownership -chown -R fenrirscreenreader:fenrirscreenreader /var/log/fenrirscreenreader -chmod -R 755 /var/log/fenrirscreenreader -chown -R root:fenrirscreenreader /etc/fenrirscreenreader - -# Fix permissions on tty#s -for i in /dev/tty[0-9]* ; do - chmod 660 "$i" -done - -sudo -Hu fenrirscreenreader mkdir /var/fenrirscreenreader/.config/pulse - -# Set up sound -cat << EOF > /var/fenrirscreenreader/.config/pulse/client.conf -# This file is part of PulseAudio. -# -# PulseAudio is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# PulseAudio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PulseAudio; if not, see . - -## Configuration file for PulseAudio clients. See pulse-client.conf(5) for -## more information. Default values are commented out. Use either ; or # for -## commenting. - -; default-sink = -; default-source = -default-server = unix:/tmp/pulse.sock -; default-dbus-server = - -autospawn = no -; autospawn = yes -; daemon-binary = /usr/bin/pulseaudio -; extra-arguments = --log-target=syslog - -; cookie-file = - -; enable-shm = yes -; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB - -; auto-connect-localhost = no -; auto-connect-display = no -EOF - - -exit 0