62 Commits

Author SHA1 Message Date
4bcf82178e Updated configure_pipewire script. A bit more code refactor. Preparing to start moving everything over to pep8 compliance. 2025-06-28 22:52:21 -04:00
beae1866bb Moved restore speech on prompt to existing temp interrupt speech command, Fenrir+kp_plus unbound now. 2025-06-28 01:49:20 -04:00
40b88efa34 Fix up a few errors that sneaked in while updating vmenu code and a couple other things. Logs should be much nicer now. 2025-06-25 10:56:08 -04:00
b1edc53362 Added missing dependency to requirements.txt. 2025-06-20 12:43:49 -04:00
dda84b9905 Final batch of code stability updates before anouther bout of extended testing. Still plenty to go, but making progress. 2025-06-20 03:34:50 -04:00
64e79f6945 Code cleanup, make sure race conditions can't happen, at least in theory. 2025-06-20 03:10:07 -04:00
f4ed8da4c3 Forgot one in last commit. 2025-06-20 03:04:25 -04:00
a5ca1d28e8 Make sure all except statements are no longer empty, should help a lot with debugging. 2025-06-20 03:03:43 -04:00
4e193f133f Fixed an error that crept into log permissions during code restructuring. 2025-06-20 02:19:07 -04:00
27dcff23bb Code optimization and bug fixes. Removed the broken, partial atspi driver implementation. 2025-06-18 15:08:36 -04:00
d81d563bb6 Add page up and page down to move through the voice browser by 10%. 2025-06-16 01:08:05 -04:00
43871cea3c Fixes to the voice driver. It should actually work completely now. 2025-06-15 19:52:18 -04:00
72bd334d65 Very experimental attempt to move the configure_fenrir script into fenrir itself using the vmenu system. Lots of testing please. 2025-06-15 14:04:14 -04:00
e76b914d6e Ignore screen 7 by default in case auto ignore screen doesn't work. 2025-06-12 18:43:12 -04:00
83cb330d34 Potential fixes to progress bar. Better handling of punctuation while reading. 2025-06-10 18:10:08 -04:00
b0ac6e1409 improved documentation for the diff generation. 2025-06-09 16:53:05 -04:00
6998706934 Testing fixes for security improvement, thread safety, and memory management. 2025-06-09 14:41:33 -04:00
62e1001679 Add keybinding for progress bar monitoring for laptop mode. Fenrir+Shift+P. 2025-06-09 13:11:51 -04:00
d935ef2e3c The fix for hopefully not reading all spaces broke review by character. Hopefully fix that. 2025-06-09 12:48:02 -04:00
e2fb28d92f Attempt to fix the bug where fenrir sometimes reads all the spaces in a message. 2025-06-09 02:33:36 -04:00
8a223282df Some modifications to progress bar detection, can revert if needed. 2025-06-09 02:19:39 -04:00
91c97dd1dd Fixed error in settings file, Time and date need %% instead of just a single %. 2025-06-08 18:04:06 -04:00
5cc719a6f3 Commit 2 of 2, code refactor and cleanup. 2025-06-08 14:26:24 -04:00
ddc1b43304 Commit 1 of 2, code refactor and cleanup. 2025-06-08 13:49:22 -04:00
6ad11effc6 Enhanced prompt detection 2025-06-08 13:31:37 -04:00
ca0e3b5987 Reverted changes to remot driver because some settings, e.g. highlight mode, stopped working. 2025-06-07 18:22:31 -04:00
0009d90a68 Updated .gitignore 2025-06-07 13:35:10 -04:00
2c2efc56f0 Progress beeps should now rok with dd's progress flag. 2025-06-07 13:23:45 -04:00
27c35939b1 A few minor tweaks to progressbar beeps. 2025-06-07 11:13:15 -04:00
7e87ebf04b Attempt to improve how Fenrir reads multiple characters in a row, e.g. [------]. 2025-06-07 10:51:02 -04:00
ec6c135581 Keybinding for silence until prompt returns added to laptop.conf. Fenrir+Shift+Enter 2025-06-07 10:20:09 -04:00
998c63cc71 Fixed a few typos in settings file, changed progressbars to true by default. 2025-06-07 01:48:58 -04:00
26c6e32c59 Add the ability to add custom prompts to the settings file, generic prompts are covered with the existing detection code. 2025-06-07 01:26:21 -04:00
97e2da614b 2 new features, silence speech until prompt returns and progress bar beeps. 2025-06-07 00:52:13 -04:00
0930a86ce7 --ignore-screen (-i) flag added. 2025-06-06 22:58:08 -04:00
a742c12cd8 Remote driver now responds so you can know for sure the command worked. 2025-06-06 20:32:46 -04:00
8d50003730 Updated documentation. Attempted to track down a bug that causes disable speech sound to play with no interaction.? 2025-06-06 17:56:02 -04:00
bd151c7cec Removed the speakup keyboard layout. It was never finished, and Fenrir has more functionality anyway and isn't that hard to learn. 2025-06-04 22:22:51 -04:00
6f4784daed Pty layouts skipped in keyboard layout cycling. 2025-06-04 22:14:49 -04:00
8c471adfa4 Keyboard layout shortcut changed to a single key, f4, because it multikey shortcuts require the fenrir key. 2025-06-04 20:37:21 -04:00
77065c55b4 Attempt to add keyboard shortcut to switch keyboard layout. 2025-06-04 20:17:06 -04:00
7f75c231e1 Removed _ from some punctuation, added it to most. 2025-06-04 19:34:15 -04:00
6b84a8e9bc Rename suspendingScreen to ignoreScreen. Requires edit to your settings file, find suspending and change it to ignore. This may break stuff, please let me know. 2025-04-23 01:36:03 -04:00
3212962a5b Adjusted a few of the defaults in punctuation and in settings file. Logs are now in a more screen reader friendly format with timestamps and level coming after the actual information. 2025-04-21 14:04:57 -04:00
9e9cb883e7 Updated requirements.txt. 2025-04-17 01:04:38 -04:00
26a8c8cf86 Added setproctitle to dependencies. 2025-04-17 00:35:02 -04:00
bbfd2790a9 Attempt to fix import/export GUI clipboard for some distros. 2025-04-16 21:50:37 -04:00
6edb743c23 Removed rapidfuzz from dependencies. 2025-04-16 12:14:30 -04:00
a1fcee9a45 Removed the rapidfuzz dependency. There wasn't much of a gain in performance and it did not work on some distros. 2025-04-16 12:06:54 -04:00
4e6e6e2d17 Fixed a couple of bugs in setup.py. 2025-04-16 00:52:44 -04:00
676c2b07a9 A couple of small improvements to install.sh. 2025-04-14 20:02:00 -04:00
2dda73ac87 Request to be able to use the numpad if numlock is on and only process fenrir commands if numlock is off. This should work, let me know if anything breaks. 2025-04-14 18:57:10 -04:00
f68a1af223 Fixed a typo in requirements.txt. 2025-04-10 05:46:22 -04:00
5ab66f6978 Attempt to fix the bug where Fenrir freezes the computer solid if it manages to start before sound is ready. It shuld at least release the keyboard now. 2025-03-20 02:55:39 -04:00
1552b962a1 Updated dependencies. 2025-03-02 17:43:01 -05:00
09391bfe84 Experimental fix for evdev failures. 2025-03-02 17:24:45 -05:00
e76ca9889a Same update for export to x clipboard. Now using pyperclip. 2025-03-02 16:04:38 -05:00
73206ce393 Switched from xclip to pyperclip for import from x. 2025-03-02 15:25:06 -05:00
4966b87ba1 pyttsx removed from setup file because it's no longer a speech option. 2025-02-26 17:38:22 -05:00
145cab6221 Updated dependencies to include rapidfuzz. 2025-02-26 17:08:50 -05:00
e46926f145 Fixed a traceback on shutdown. Hopefully improved responsiveness with the diff. Trying rapidfuzz for smaller screen updates, add a catch to fall back to the original difflib if there are any problems. This is experimental, please watch for bugs. 2025-02-26 17:02:25 -05:00
8cd50c5070 Hopefully improve accuracy of blank line reporting. 2025-02-26 16:05:17 -05:00
373 changed files with 7086 additions and 8642 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ dist/
build/
*.kate-swp
.directory
CLAUDE.md

28
CREDITS
View File

@ -1,18 +1,30 @@
# Fenrir screen reader
# Fenrir Screen Reader Credits
## Developers
## Current Maintainer
* Storm Dragon: Project leader
* Jeremiah: Coder.
* **Storm Dragon** - Project leader and maintainer
## Current Contributors
* **Jeremiah** - Developer
## Previous Developers
* Chrys: coder.
* **Chrys** - Original creator and main developer
## Special thanks to:
## Special Thanks
* F123 Consulting for suggestions, some funding, and endless testing.
* Stormux for continuation of the project.
* **F123 Consulting** - Suggestions, funding, and extensive testing
* **Stormux Community** - Continuation of the project and ongoing support
* **All contributors** - Bug reports, feature requests, and community support
## Community
* IRC: irc.stormux.org #stormux
* Email list: stormux+subscribe@groups.io
* Wiki: https://git.stormux.org/storm/fenrir/wiki

517
README.md
View File

@ -1,69 +1,97 @@
# Fenrir
A modern, modular, flexible and fast console screenreader.
A modern, modular, flexible and fast console screen reader.
It should run on any operating system. If you want to help, or write drivers to make it work on other systems, just let me know.
This software is licensed under the LGPL v3.
**Current maintainer:** Storm Dragon
**Previous developer:** Chrys
## Key Features
- **Multiple Interface Support**: Works in Linux TTY, and terminal emulators
- **Flexible Driver System**: Modular architecture with multiple drivers for speech, sound, input, and screen
- **Review Mode**: Navigate and review screen content without moving the edit cursor
- **Multiple Clipboard Support**: Manage multiple clipboard entries
- **Configurable Key Bindings**: Desktop and laptop keyboard layouts
- **Sound Icons**: Audio feedback for various events
- **Spell Checking**: Built-in spell checker with word management
- **Language Support**: Multiple speech synthesis languages and voices
- **Bookmark System**: Quick access to specific screen areas
- **Auto-announcement**: Automatic reading of incoming text and time announcements
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
## OS Requirements
- Linux (ptyDriver, vcsaDriver, evdevDriver)
- macOS (ptyDriver)
- BSD (ptyDriver)
- Windows (ptyDriver)
- Linux (ptyDriver, vcsaDriver, evdevDriver) - Primary platform with full support
- macOS (ptyDriver) - Limited support
- BSD (ptyDriver) - Limited support
- Windows (ptyDriver) - Limited support
## Core Requirements
- python3 >= 3.3
- screen, input, speech, sound drivers dependencies see "Features, Drivers, Extras".
- Python 3 >= 3.9 (recommended 3.13+)
- Screen, input, speech, sound driver dependencies (see "Features, Drivers, Extras" section)
- For full functionality on Linux: evdev, speech-dispatcher, sox
## Features, Drivers, Extras, Dependencies
### 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
- loaded uinput kernel module
- ReadWrite permission
- /dev/input
- /dev/uinput
2. "ptyDriver" terminal emulation input driver
- python-pyte
1. **evdevDriver** - Linux evdev input driver (recommended for Linux)
- python-evdev >=0.6.3 (This is commonly referred to as python3-evdev by your distribution)
- python-pyudev
- loaded uinput kernel module
- ReadWrite permission:
- /dev/input
- /dev/uinput
2. **ptyDriver** - Terminal emulation input driver (cross-platform)
- python-pyte
3. **atspiDriver** - AT-SPI input driver for desktop environments
- python-pyatspi2
### Remote Drivers:
1. **unixDriver** - Unix socket remote control (default)
- socat (for command-line interaction)
2. **tcpDriver** - TCP socket remote control (localhost only)
- netcat or telnet (for command-line interaction)
### Screen Drivers:
1. "vcsaDriver" screen driver for linux VCSA devices
- python-dbus
- Read permission to the following files and services:
- /sys/devices/virtual/tty/tty0/active
- /dev/tty[1-64]
- /dev/vcsa[1-64]
- read logind DBUS
2. "ptyDriver" terminal emulation driver
- python-pyte
1. **vcsaDriver** - Linux VCSA devices driver (recommended for Linux TTY)
- python-dbus
- Read permission to the following files and services:
- /sys/devices/virtual/tty/tty0/active
- /dev/tty[1-64]
- /dev/vcsa[1-64]
- read logind DBUS
2. **ptyDriver** - Terminal emulation driver (cross-platform)
- python-pyte
### Speech Drivers:
1. "genericDriver" (default) speech driver for sound as subprocess:
- espeak or espeak-ng
2. "speechdDriver" speech driver for Speech-dispatcher:
- Speech-dispatcher
- python-speechd
3. "emacspeakDriver" speech driver for emacspeak
- emacspeak
1. **speechdDriver** - Speech-dispatcher driver (recommended)
- Speech-dispatcher
- python-speechd
2. **genericDriver** - Generic subprocess speech driver
- espeak or espeak-ng (or any TTS command)
3. **debugDriver** - Debug speech driver for testing
- No dependencies
### Sound Drivers:
1. "genericDriver" (default) sound driver for sound as subprocess:
- Sox
2. "gstreamerDriver" sound driver for gstreamer
- gstreamer >=1.0
- GLib
1. **genericDriver** (default) - Generic subprocess sound driver
- Sox with opus support (recommended)
2. **gstreamerDriver** - GStreamer sound driver
- gstreamer >=1.0
- GLib
3. **debugDriver** - Debug sound driver for testing
- No dependencies
## Extras:
@ -91,16 +119,353 @@ If there is a package for your distrobution of choice, please let us know so we
- You can also just run it from Git without installing:
Requires root privileges
cd src/fenrir/
cd src/
sudo ./fenrir
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)
- input driver: evdevDriver
Settings are located in:
- **After installation**: `/etc/fenrir/settings/settings.conf`
- **Development**: `config/settings/settings.conf`
By default Fenrir uses:
- **Sound driver**: genericDriver (via sox)
- **Speech driver**: speechdDriver (via speech-dispatcher)
- **Input driver**: evdevDriver (Linux) or ptyDriver (other platforms)
- **Screen driver**: vcsaDriver (Linux TTY) or ptyDriver (terminal emulation)
## Getting Started
### Basic Usage
1. **Start Fenrir**:
```bash
sudo systemctl start fenrir # If installed as service
# OR
sudo fenrir # Run directly
```
2. **Basic Navigation**:
- **Fenrir Key**: By default `Insert`, `Keypad Insert`, or `Meta/Super` key
- **Tutorial Mode**: `Fenrir + H` to learn all commands interactively
- **Quit Fenrir**: `Fenrir + Q`
3. **Essential Commands**:
- `Ctrl` - Stop speech (shut up)
- `Fenrir + Keypad 5` - Read current screen
- `Keypad 8` - Read current line
- `Keypad 5` - Read current word
- `Keypad 2` - Read current character
- `Fenrir + T` - Announce time
- `Fenrir + S` - Spell check current word
### Keyboard Layouts
Fenrir supports two main keyboard layouts:
- **Desktop Layout**: Uses numeric keypad for navigation (recommended for desktop users)
- **Laptop Layout**: Alternative bindings for keyboards without numeric keypad
Configure in `/etc/fenrir/settings/settings.conf`:
```ini
[keyboard]
keyboardLayout=desktop # or 'laptop'
```
### First Time Setup
1. **Enable Fenrir at boot**:
```bash
sudo systemctl enable fenrir
```
2. **Configure audio** (if needed):
- For PulseAudio: Run configure_pulse.sh script (see below)
- For PipeWire: Run configure_pipewire.sh script (see below)
3. **Test speech**:
```bash
# Test speech-dispatcher directly
sudo spd-say "Hello World"
```
## Remote Control
Fenrir includes a powerful remote control system that allows external applications and scripts to control Fenrir through Unix sockets or TCP connections. This is particularly useful for automation, integration with other applications, or providing alternative control methods.
### Configuration
Enable remote control in `/etc/fenrir/settings/settings.conf`:
```ini
[remote]
enable=True
driver=unixDriver # or tcpDriver
port=22447 # for TCP driver
socketFile= # custom socket path (optional)
enableSettingsRemote=True # allow settings changes
enableCommandRemote=True # allow command execution
```
### Remote Drivers
1. **unixDriver** (recommended): Uses Unix domain sockets
- Socket location: `/tmp/fenrirscreenreader-deamon.sock` (TTY mode) or `/tmp/fenrirscreenreader-<pid>.sock`
- More secure, local-only access
- Works with `socat`
2. **tcpDriver**: Uses TCP sockets on localhost
- Default port: 22447
- Works with `netcat`, `telnet`, or any TCP client
- Local connections only (127.0.0.1)
### Using socat with Unix Sockets
The `socat` command provides the easiest way to send commands to Fenrir:
#### Basic Speech Control
```bash
# Interrupt current speech
echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Speak custom text
echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Temporarily disable speech (until next keystroke)
echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Settings Control
```bash
# Enable highlight tracking mode
echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change speech parameters
echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change punctuation level (none/some/most/all)
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set general#punctuationLevel=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Voice and TTS engine control
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Disable sound temporarily
echo "setting set sound#enabled=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set sound#volume=0.5" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Keyboard and input settings
echo "setting set keyboard#charEchoMode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set keyboard#wordEcho=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Screen control (ignore specific TTYs)
echo "setting set screen#ignoreScreen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Multiple settings at once
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuationLevel=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset all settings to defaults
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Save current settings
echo "setting save" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting saveas /tmp/my-fenrir-settings.conf" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Clipboard Operations
```bash
# Place text into clipboard
echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Export clipboard to file
echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Window Management
```bash
# Define a window area (x1 y1 x2 y2)
echo "command window 0 0 80 24" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset window to full screen
echo "command resetwindow" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### VMenu Control
```bash
# Set virtual menu context
echo "command vmenu nano/file" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset virtual menu
echo "command resetvmenu" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Application Control
```bash
# Quit Fenrir
echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
### Using TCP Driver
If using the TCP driver, replace socat commands with netcat:
```bash
# Using netcat
echo "command say Hello from TCP" | nc localhost 22447
# Using telnet
echo "command interrupt" | telnet localhost 22447
```
### Remote Command Reference
#### Command Format
```
command <action> [parameters]
setting <action> [parameters]
```
#### Available Commands
**Speech Commands:**
- `command say <text>` - Speak the specified text
- `command interrupt` - Stop current speech
- `command tempdisablespeech` - Disable speech until next key press
**Clipboard Commands:**
- `command clipboard <text>` - Add text to clipboard
- `command exportclipboard` - Export clipboard to file
**Window Commands:**
- `command window <x1> <y1> <x2> <y2>` - Define window area
- `command resetwindow` - Reset to full screen
**VMenu Commands:**
- `command vmenu <menu_path>` - Set vmenu context
- `command resetvmenu` - Reset vmenu
**Application Commands:**
- `command quitapplication` - Quit Fenrir
#### Available Settings
**Settings Commands:**
- `setting set <section>#<key>=<value>` - Set configuration value
- `setting reset` - Reset all settings to defaults
- `setting save [path]` - Save current settings
- `setting saveas <path>` - Save settings to specific file
**Common Settings:**
*Speech Settings:*
- `speech#enabled=True/False` - Enable/disable speech
- `speech#rate=0.1-1.0` - Speech rate (speed)
- `speech#pitch=0.1-1.0` - Speech pitch (tone)
- `speech#volume=0.1-1.0` - Speech volume
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
- `speech#autoReadIncoming=True/False` - Auto-read new text
*Sound Settings:*
- `sound#enabled=True/False` - Enable/disable sound
- `sound#volume=0.1-1.0` - Sound volume
- `sound#driver=driver_name` - Sound driver (genericDriver/gstreamerDriver)
- `sound#theme=theme_name` - Sound theme
*General Settings:*
- `general#punctuationLevel=none/some/most/all` - Punctuation verbosity
- `general#debugLevel=0-3` - Debug level
- `general#emoticons=True/False` - Enable emoticon replacement
- `general#autoSpellCheck=True/False` - Automatic spell checking
*Focus Settings:*
- `focus#cursor=True/False` - Follow text cursor
- `focus#highlight=True/False` - Follow text highlighting
*Keyboard Settings:*
- `keyboard#charEchoMode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
- `keyboard#wordEcho=True/False` - Echo complete words
- `keyboard#charDeleteEcho=True/False` - Echo deleted characters
- `keyboard#interruptOnKeyPress=True/False` - Interrupt speech on key press
*Screen Settings:*
- `screen#ignoreScreen=1,2,3` - TTY screens to ignore
- `screen#autodetectIgnoreScreen=True/False` - Auto-detect screens to ignore
- `screen#screenUpdateDelay=float` - Screen update delay
*Time Settings:*
- `time#enabled=True/False` - Enable time announcements
- `time#presentTime=True/False` - Announce time
- `time#presentDate=True/False` - Announce date changes
- `time#delaySec=seconds` - Announcement interval
- `time#onMinutes=00,30` - Specific minutes to announce
### Scripting Examples
#### Bash Script for Speech Notifications
```bash
#!/bin/bash
# notify_fenrir.sh - Send notifications to Fenrir
SOCKET="/tmp/fenrirscreenreader-deamon.sock"
fenrir_say() {
echo "command say $1" | socat - UNIX-CLIENT:$SOCKET
}
fenrir_interrupt() {
echo "command interrupt" | socat - UNIX-CLIENT:$SOCKET
}
# Usage examples
fenrir_say "Build completed successfully"
fenrir_interrupt
```
#### Python Integration
```python
#!/usr/bin/env python3
import socket
import os
def send_fenrir_command(command):
"""Send command to Fenrir via Unix socket"""
socket_path = "/tmp/fenrirscreenreader-deamon.sock"
if os.path.exists(socket_path):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(socket_path)
sock.send(command.encode('utf-8'))
finally:
sock.close()
# Examples
send_fenrir_command("command say Processing complete")
send_fenrir_command("setting set speech#rate=0.9")
```
### Security Considerations
- Unix sockets are accessible only to the user running Fenrir
- TCP driver binds only to localhost (127.0.0.1)
- Socket file permissions are set to write-only (0o222)
- Commands are processed with Fenrir's privileges
- Settings changes can be disabled via `enableSettingsRemote=False`
- Command execution can be disabled via `enableCommandRemote=False`
### Troubleshooting
**Socket not found:**
- Verify Fenrir is running: `ps aux | grep fenrir`
- Check socket location: `/tmp/fenrirscreenreader-*`
- Ensure remote driver is enabled in settings
**Commands not working:**
- Verify `enableCommandRemote=True` in settings
- Check Fenrir debug logs: `/var/log/fenrir.log`
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
## Configure pulseaudio
@ -127,12 +492,64 @@ just run the configuration script twice (once as user, once as root):
The script is also located in the tools directory in git
## 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
## Command Line Options
Fenrir supports several command-line options for different use cases:
```
fenrir [OPTIONS]
```
### Options:
- `-h, --help` - Show help message and exit
- `-v, --version` - Show version information and exit
- `-f, --foreground` - Run in foreground (don't daemonize)
- `-s, --setting SETTING-FILE` - Path to custom settings file
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings file options
- `-d, --debug` - Enable debug mode
- `-p, --print` - Print debug messages to screen
- `-e, --emulated-pty` - Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)
- `-E, --emulated-evdev` - Use PTY emulation with evdev for input (single instance)
- `-F, --force-all-screens` - Force Fenrir to respond on all screens, ignoring ignoreScreen setting
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
### Examples:
```bash
# Run in foreground with debug output
sudo fenrir -f -d
# Use PTY emulation for desktop use
sudo fenrir -e
# Override settings via command line
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
# Force Fenrir to work on all screens (ignore ignoreScreen setting)
sudo fenrir -F
# Ignore specific screens
sudo fenrir --ignore-screen 1
sudo fenrir -i 1 -i 2 # Ignore screens 1 and 2
```
## Localization
Translation files are located in the `locale/` directory. To install translations:
```bash
# Copy translation file to system location
sudo cp locale/your_language/LC_MESSAGES/fenrir.mo /usr/share/locale/your_language/LC_MESSAGES/fenrir.mo
```
Available languages:
- German (de)
- Spanish (es)
- Polish (pl)
- Portuguese (pt)
- Russian (ru)
## Documentation and Support
- 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
- **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](https://git.stormux.org/storm/fenrir/wiki)
- **IRC**: irc.stormux.org #stormux
- **Issues**: Report bugs and feature requests on the project repository

View File

@ -73,7 +73,8 @@ KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
KEY_KPPLUS=last_incoming
KEY_KPPLUS=progress_bar_monitor
#KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
@ -113,6 +114,8 @@ KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SHIFT,KEY_F10=voice_browser_safe
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_F10=apply_tested_voice
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
@ -126,3 +129,4 @@ KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout

View File

@ -75,9 +75,11 @@ KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
KEY_FENRIR,KEY_ENTER=temp_disable_speech
KEY_FENRIR,KEY_SHIFT,KEY_P=progress_bar_monitor
#KEY_FENRIR,KEY_SHIFT,KEY_ENTER=silence_until_prompt
KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_ENTER=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
KEY_FENRIR,KEY_SHIFT,KEY_E=toggle_emoticons
KEY_FENRIR,KEY_ENTER=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
@ -111,6 +113,8 @@ KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SHIFT,KEY_F10=voice_browser_safe
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_F10=apply_tested_voice
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
@ -126,3 +130,4 @@ KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout

View File

@ -72,7 +72,8 @@ KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
KEY_KPPLUS=last_incoming
KEY_KPPLUS=progress_bar_monitor
KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
#=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
@ -126,3 +127,4 @@ KEY_FENRIR,KEY_CTRL,KEY_C=save_settings
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume
KEY_F4=cycle_keyboard_layout

View File

@ -126,3 +126,4 @@ KEY_FENRIR,KEY_CTRL,KEY_C=save_settings
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume
KEY_F4=cycle_keyboard_layout

View File

@ -85,3 +85,5 @@ alt+f12 - quit fenrir
^[[1;3F=temp_disable_speech
# control+end - toggle auto read
^[[1;5F=toggle_auto_read
# F12 - cycle keyboard layout
^[[24~=cycle_keyboard_layout

View File

@ -1,218 +0,0 @@
# Fenrir comment: copy of speakup DefaultKeyAssignments converted to fenrir syntax
# Fenrir comment: https://android.googlesource.com/kernel/msm/+/android-7.1.0_r0.2/drivers/staging/speakup/DefaultKeyAssignments
# Fenrir comment: The insert or shift key named below is the fenrir key
# This file is intended to give you an overview of the default keys used
# by speakup for it's review functions. You may change them to be
# anything you want but that will take some familiarity with key
# mapping.
# We have remapped the insert or zero key on the keypad to act as a
# shift key. Well, actually as an altgr key. So in the following list
# InsKeyPad-period means hold down the insert key like a shift key and
# hit the keypad period.
# KeyPad-8 Say current Line
KEY_KP8=review_curr_line
# InsKeyPad-8 say from top of screen to reading cursor.
KEY_FENRIR,KEY_KP8=curr_screen_before_cursor
# KeyPad-7 Say Previous Line (UP one line)
KEY_KP7=review_prev_line
# KeyPad-9 Say Next Line (down one line)
KEY_KP9=review_next_line
# KeyPad-5 Say Current Word
KEY_KP5=review_curr_word
# InsKeyPad-5 Spell Current Word
KEY_FENRIR,KEY_KP5=review_curr_word_phonetic
# KeyPad-4 Say Previous Word (left one word)
KEY_KP4=review_prev_word
# InsKeyPad-4 say from left edge of line to reading cursor.
KEY_FENRIR,KEY_KP4=cursor_read_line_to_cursor
# KeyPad-6 Say Next Word (right one word)
KEY_KP6=review_next_word
# InsKeyPad-6 Say from reading cursor to right edge of line.
KEY_FENRIR,KEY_KP6=cursor_read_to_end_of_line
# KeyPad-2 Say Current Letter
KEY_KP2=review_curr_char
# InsKeyPad-2 say current letter phonetically
KEY_FENRIR,KEY_KP2=review_curr_char_phonetic
# KeyPad-1 Say Previous Character (left one letter)
KEY_KP1=review_prev_char
# KeyPad-3 Say Next Character (right one letter)
KEY_KP3=review_next_char
# KeyPad-plus Say Entire Screen
KEY_KPPLUS=curr_screen
# InsKeyPad-plus Say from reading cursor line to bottom of screen.
KEY_FENRIR,KEY_KPPLUS=curr_screen_after_cursor
# KeyPad-Minus Park reading cursor (toggle)
# TODO
# InsKeyPad-minus Say character hex and decimal value.
# TODO
# KeyPad-period Say Position (current line, position and console)
KEY_KPDOT=cursor_position
# InsKeyPad-period say colour attributes of current position.
KEY_FENRIR,KEY_KPDOT=attribute_cursor
# InsKeyPad-9 Move reading cursor to top of screen (insert pgup)
KEY_FENRIR,KEY_KP9=review_bottom
# InsKeyPad-3 Move reading cursor to bottom of screen (insert pgdn)
KEY_FENRIR,KEY_KP3=review_top
# InsKeyPad-7 Move reading cursor to left edge of screen (insert home)
KEY_FENRIR,KEY_KP7=review_screen_first_char
# InsKeyPad-1 Move reading cursor to right edge of screen (insert end)
KEY_FENRIR,KEY_KP1=review_screen_last_char
# ControlKeyPad-1 Move reading cursor to last character on current line.
KEY_CTRL,KEY_KP1=review_line_end
# KeyPad-Enter Shut Up (until another key is hit) and sync reading cursor
KEY_KPENTER=temp_disable_speech
# InsKeyPad-Enter Shut Up (until toggled back on).
KEY_FENRIR,KEY_KPENTER=toggle_speech
# InsKeyPad-star n<x|y> go to line (y) or column (x). Where 'n' is any
# allowed value for the row or column for your current screen.
# TODO
# KeyPad-/ Mark and Cut screen region.
KEY_KPSLASH=copy_marked_to_clipboard
# InsKeyPad-/ Paste screen region into any console.
KEY_FENRIR,KEY_KPSLASH=paste_clipboard
# Hitting any key while speakup is outputting speech will quiet the
# synth until it has caught up with what is being printed on the
# console.
# following by other fenrir commands
KEY_FENRIR,KEY_H=toggle_tutorial_mode
KEY_CTRL=shut_up
KEY_FENRIR,KEY_KP4=review_line_begin
#=review_line_end
#=review_line_first_char
#=review_line_last_char
KEY_FENRIR,KEY_ALT,KEY_1=present_first_line
KEY_FENRIR,KEY_ALT,KEY_2=present_last_line
KEY_FENRIR,KEY_SHIFT,KEY_KP4=review_prev_word_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP6=review_next_word_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP1=review_prev_char_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP3=review_next_char_phonetic
KEY_FENRIR,KEY_CTRL,KEY_KP8=review_up
KEY_FENRIR,KEY_CTRL,KEY_KP2=review_down
#=exit_review
KEY_FENRIR,KEY_I=indent_curr_line
KEY_KPPLUS=curr_screen
#=cursor_column
#=cursor_lineno
#=braille_flush
#=braille_return_to_cursor
#=braille_pan_left
#=braille_pan_right
KEY_FENRIR,KEY_CTRL,KEY_1=clear_bookmark_1
KEY_FENRIR,KEY_SHIFT,KEY_1=set_bookmark_1
KEY_FENRIR,KEY_1=bookmark_1
KEY_FENRIR,KEY_CTRL,KEY_2=clear_bookmark_2
KEY_FENRIR,KEY_SHIFT,KEY_2=set_bookmark_2
KEY_FENRIR,KEY_2=bookmark_2
KEY_FENRIR,KEY_CTRL,KEY_3=clear_bookmark_3
KEY_FENRIR,KEY_SHIFT,KEY_3=set_bookmark_3
KEY_FENRIR,KEY_3=bookmark_3
KEY_FENRIR,KEY_CTRL,KEY_4=clear_bookmark_4
KEY_FENRIR,KEY_SHIFT,KEY_4=set_bookmark_4
KEY_FENRIR,KEY_4=bookmark_4
KEY_FENRIR,KEY_CTRL,KEY_5=clear_bookmark_5
KEY_FENRIR,KEY_SHIFT,KEY_5=set_bookmark_5
KEY_FENRIR,KEY_5=bookmark_5
KEY_FENRIR,KEY_CTRL,KEY_6=clear_bookmark_6
KEY_FENRIR,KEY_SHIFT,KEY_6=set_bookmark_6
KEY_FENRIR,KEY_6=bookmark_6
KEY_FENRIR,KEY_CTRL,KEY_7=clear_bookmark_7
KEY_FENRIR,KEY_SHIFT,KEY_7=set_bookmark_7
KEY_FENRIR,KEY_7=bookmark_7
KEY_FENRIR,KEY_CTRL,KEY_8=clear_bookmark_8
KEY_FENRIR,KEY_SHIFT,KEY_8=set_bookmark_8
KEY_FENRIR,KEY_8=bookmark_8
KEY_FENRIR,KEY_CTRL,KEY_9=clear_bookmark_9
KEY_FENRIR,KEY_SHIFT,KEY_9=set_bookmark_9
KEY_FENRIR,KEY_9=bookmark_9
KEY_FENRIR,KEY_CTRL,KEY_0=clear_bookmark_10
KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
#=last_incoming
KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F9=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_BACKSLASH=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
key_FENRIR,KEY_KPENTER=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
KEY_FENRIR,KEY_KPMINUS=toggle_barrier
KEY_FENRIR,KEY_Q=quit_fenrir
KEY_FENRIR,KEY_T=time
2,KEY_FENRIR,KEY_T=date
KEY_KPSLASH=toggle_auto_indent
#=toggle_has_attribute
KEY_FENRIR,KEY_S=spell_check
2,KEY_FENRIR,KEY_S=add_word_to_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_S=remove_word_from_spell_check
KEY_FENRIR,KEY_BACKSPACE=forward_keypress
KEY_FENRIR,KEY_ALT,KEY_UP=inc_sound_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_sound_volume
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_C=clear_clipboard
KEY_FENRIR,KEY_HOME=first_clipboard
KEY_FENRIR,KEY_END=last_clipboard
KEY_FENRIR,KEY_PAGEUP=prev_clipboard
KEY_FENRIR,KEY_PAGEDOWN=next_clipboard
KEY_FENRIR,KEY_SHIFT,KEY_C=curr_clipboard
KEY_FENRIR,KEY_CTRL,KEY_C=copy_last_echo_to_clipboard
KEY_FENRIR,KEY_F5=import_clipboard_from_file
KEY_FENRIR,KEY_F6=export_clipboard_to_file
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
KEY_FENRIR,KEY_UP=next_quick_menu_value
KEY_FENRIR,KEY_LEFT=prev_quick_menu_entry
KEY_FENRIR,KEY_DOWN=prev_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume

View File

@ -5,7 +5,7 @@
[levelDict]
none:===:
some:===:-$~+*-/\@#
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
most:===:.,:-_$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
[punctDict]

View File

@ -5,7 +5,7 @@
[levelDict]
none:===:
some:===:-$~+*-/\@
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
most:===:.,:-$~+*-_/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
[punctDict]

View File

@ -4,7 +4,7 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict]
none:===:
some:===:-$~+*-/\@
some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~

View File

@ -15,7 +15,7 @@ theme=default
# Sound volume controls how loud the sounds for your selected soundpack are.
# 0 is quietest, 1.0 is loudest.
volume=1.0
volume=0.7
# shell commands for generic sound driver
# the folowing variable are substituted
@ -28,6 +28,9 @@ genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
#the following command is used to generate a frequency beep
genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
# Enable progress bar monitoring with ascending tones by default
progressMonitoring=True
[speech]
# Turn speech on or off:
enabled=True
@ -92,8 +95,8 @@ fenrirMaxRate=450
driver=vcsaDriver
encoding=auto
screenUpdateDelay=0.05
suspendingScreen=
autodetectSuspendingScreen=True
ignoreScreen=7
autodetectIgnoreScreen=True
[keyboard]
driver=evdevDriver
@ -121,7 +124,9 @@ interruptOnKeyPressFilter=
doubleTapTimeout=0.2
[general]
debugLevel=0
# Debug levels: 0=DEACTIVE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
# For production use, WARNING (2) provides good balance of useful info without spam
debugLevel=2
# debugMode sets where the debug output should send to:
# debugMode=File writes to debugFile (Default:/tmp/fenrir-PID.log)
# debugMode=Print just prints on the screen
@ -131,7 +136,7 @@ punctuationProfile=default
punctuationLevel=some
respectPunctuationPause=True
newLinePause=True
numberOfClipboards=10
numberOfClipboards=50
# used path for "export_clipboard_to_file"
# $user is replaced by username
#clipboardExportPath=/home/$user/fenrirClipboard
@ -140,8 +145,8 @@ emoticons=True
# define the current Fenrir key
fenrirKeys=KEY_KP0,KEY_META,KEY_INSERT
scriptKeys=KEY_COMPOSE
timeFormat=%H:%M:%P
dateFormat=%A, %B %d, %Y
timeFormat=%%I:%%M%%P
dateFormat=%%A, %%B %%d, %%Y
autoSpellCheck=True
spellCheckLanguage=en_US
# path for your scripts "scriptKeys" functionality
@ -163,7 +168,7 @@ autoPresentIndent=False
# 1 = sound only
# 2 = speak only
autoPresentIndentMode=1
# play a sound when attributes are changeing
# play a sound when attributes change
hasAttributes=True
# shell for PTY emulatiun (empty = default shell)
shell=
@ -190,7 +195,7 @@ enableSettingsRemote=True
enableCommandRemote=True
[barrier]
enabled=True
enabled=False
leftBarriers=│└┌─
rightBarriers=│┘┐─
@ -211,8 +216,32 @@ list=
vmenuPath=
quickMenu=speech#rate;speech#pitch;speech#volume
[prompt]
# Custom prompt patterns for silence until prompt feature
# You can add your own shell prompt patterns as regular expressions
# Each pattern should be on a separate line, format: customPatterns=pattern1,pattern2,pattern3
#
# Built-in patterns include:
# - Shell prompts: $, #, >, user@host$, [user@host]$, bash-5.1$
# - Package manager prompts: [Y/n], [y/N], [Yes/No], (Y/n), (y/N)
# - sudo prompts: [sudo] password for user:, Password:, user's password:
# - Confirmation prompts: Press any key, Are you sure?, Please confirm
#
# Custom pattern examples:
# For PS1='[\u@\h \W] \$ ' use: \[.*@.*\s.*\]\s*[$#>]\s*
# For "[user@hostname ~] $" use: \[.*@.*\s.*\]\s*[$#>]\s*
# For custom prompts ending with specific strings, use patterns like: .*your_prompt_ending$
# For custom package manager prompts: .*your_package_manager.*\[[YyNn]/[YyNn]\].*
customPatterns=
# Specific prompt strings to match exactly (useful for very specific custom prompts)
# Format: exactMatches=prompt1,prompt2,prompt3
# Examples:
# exactMatches=[storm@fenrir ~] $,[root@fenrir ~] #,Continue installation? [Y/n]
exactMatches=
[time]
# automatic time anouncement
# automatic time announcement
enabled=False
# present time
presentTime=True

Binary file not shown.

Binary file not shown.

View File

@ -30,9 +30,6 @@ ContentChanged='ContentChanged.wav'
# Speech has turned On or Off
SpeechOn='SpeechOn.wav'
SpeechOff='SpeechOff.wav'
# Braille has turned On or Off
BrailleOn='BrailleOn.wav'
BrailleOff='BrailleOff.wav'
# SoundIcons has turned On or Off
SoundOn='SoundOn.wav'
SoundOff='SoundOff.wav'
@ -44,9 +41,8 @@ PlaceEndMark='PlaceEndMark.wav'
CopyToClipboard='CopyToClipboard.wav'
# Pasted on the screen
PasteClipboardOnScreen='PasteClipboardOnScreen.wav'
# An error accoured while speech or braille output or reading the screen
# An error accoured while speech output or reading the screen
ErrorSpeech='ErrorSpeech.wav'
ErrorBraille='ErrorBraille.wav'
ErrorScreen='ErrorScreen.wav'
# If you cursor over an text that has attributs (like color)
HasAttributes='has_attribute.wav'

View File

@ -1,4 +1,389 @@
1. Basic
2. Commands
3. Useful API
# Fenrir Development Guide
This document provides information for developers who want to contribute to Fenrir or understand its architecture.
## Project Structure
Fenrir follows a modular, driver-based architecture:
```
src/fenrirscreenreader/
├── core/ # Core system modules
│ ├── fenrirManager.py # Main application manager
│ ├── screenManager.py # Screen handling
│ ├── inputManager.py # Input handling
│ ├── outputManager.py # Speech/sound output
│ ├── commandManager.py # Command system
│ └── settingsManager.py # Configuration management
├── commands/ # Command implementations
│ ├── commands/ # User-invoked commands
│ ├── onCursorChange/ # Cursor movement hooks
│ ├── onScreenUpdate/ # Screen update hooks
│ ├── onKeyInput/ # Key input hooks
│ └── help/ # Tutorial system
├── drivers/ # Driver implementations
│ ├── inputDriver/ # Input drivers (evdev, pty, atspi)
│ ├── screenDriver/ # Screen drivers (vcsa, pty)
│ ├── speechDriver/ # Speech drivers (speechd, generic)
│ └── soundDriver/ # Sound drivers (generic, gstreamer)
└── utils/ # Utility modules
```
## Core Architecture
### Driver System
Fenrir uses a pluggable driver architecture:
1. **Input Drivers**: Capture keyboard input
- evdevDriver: Linux evdev (recommended)
- ptyDriver: Terminal emulation
- atspiDriver: AT-SPI for desktop
2. **Screen Drivers**: Read screen content
- vcsaDriver: Linux VCSA devices
- ptyDriver: Terminal emulation
3. **Speech Drivers**: Text-to-speech output
- speechdDriver: Speech-dispatcher
- genericDriver: Command-line TTS
4. **Sound Drivers**: Audio output
- genericDriver: Sox-based
- gstreamerDriver: GStreamer
5. **Remote Drivers**: Remote control interfaces
- unixDriver: Unix socket control
- tcpDriver: TCP socket control
### Command System
Commands are Python modules that implement specific functionality:
```python
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Command description')
def run(self):
# Command implementation
pass
```
### Event Hooks
Fenrir supports various event hooks:
- **onCursorChange**: Triggered when cursor moves
- **onScreenUpdate**: Triggered on screen content changes
- **onKeyInput**: Triggered on key presses
- **onByteInput**: Triggered on byte-level input
- **onScreenChanged**: Triggered when switching screens
## Development Setup
### Requirements
- Python 3.6+
- python3-evdev
- python3-pyudev
- speech-dispatcher
- sox
### Getting Started
```bash
# Clone repository
git clone https://git.stormux.org/storm/fenrir.git
cd fenrir
# Install dependencies
sudo pip3 install -r requirements.txt
# Run from source
cd src/
sudo ./fenrir -f -d
```
### Testing
```bash
# Run in debug mode
sudo ./fenrir -f -d -p
# Debug output goes to:
# - Console (with -p flag)
# - /var/log/fenrir.log
```
## Creating Commands
### Basic Command
Create a file in `src/fenrirscreenreader/commands/commands/`:
```python
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 _('My custom command')
def run(self):
# Get current text
text = self.env['screen']['newContentText']
# Speak something
self.env['runtime']['outputManager'].presentText('Hello World')
# Play sound
self.env['runtime']['outputManager'].playSoundIcon('Accept')
```
### Key Bindings
Add key bindings in keyboard layout files:
`config/keyboard/desktop.conf` or `config/keyboard/laptop.conf`
```ini
[KEY_CTRL]#[KEY_ALT]#[KEY_H]=my_command
```
### Event Hooks
Create event handlers in appropriate directories:
```python
# onCursorChange/my_hook.py
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('My cursor change handler')
def run(self):
if self.env['runtime']['cursorManager'].isCursorHorizontalMove():
# Handle horizontal cursor movement
pass
```
## Creating Drivers
### Driver Template
```python
class driver():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
# Driver-specific methods...
```
### Input Driver
Implement these methods:
- `getInputEvent()`: Return input events
- `writeEventBuffer()`: Handle output events
- `grabDevices()`: Take exclusive control
- `releaseDevices()`: Release control
### Screen Driver
Implement these methods:
- `getCurrScreen()`: Get current screen content
- `getSessionInformation()`: Get session info
### Speech Driver
Implement these methods:
- `speak()`: Speak text
- `cancel()`: Stop speech
- `setCallback()`: Set callback functions
### Remote Driver
Implement these methods:
- `initialize()`: Setup socket/connection
- `watchDog()`: Listen for incoming commands
- `shutdown()`: Clean up connections
#### Remote Driver Example
```python
class driver(remoteDriver):
def initialize(self, environment):
self.env = environment
# Start watchdog thread
self.env['runtime']['processManager'].addCustomEventThread(
self.watchDog, multiprocess=True
)
def watchDog(self, active, eventQueue):
# Listen for connections and process commands
while active.value:
# Accept connections
# Parse incoming data
# Send to event queue
eventQueue.put({
"Type": fenrirEventType.RemoteIncomming,
"Data": command_text
})
```
## Configuration
### Settings System
Settings are hierarchical:
1. Command-line options (`-o`)
2. Configuration file
3. Hard-coded defaults
### Adding Settings
1. Add default value to `core/settingsData.py`
2. Access via `self.env['runtime']['settingsManager'].getSetting(section, key)`
## Debugging
### Debug Levels
- 0: DEACTIVE
- 1: ERROR
- 2: WARNING
- 3: INFO
### Debug Output
```python
self.env['runtime']['debug'].writeDebugOut(
'Debug message',
debug.debugLevel.INFO
)
```
### Testing Commands
```bash
# Test specific functionality
sudo fenrir -f -d -o "general#debugLevel=3"
# Test with custom config
sudo fenrir -f -s /path/to/test.conf
```
## Contributing
### Code Style
- Follow PEP 8
- Use descriptive variable names
- Add docstrings for complex functions
- Handle exceptions gracefully
### Testing
- Test with different drivers
- Test keyboard layouts
- Test on different terminals
- Verify accessibility features
### Submitting Changes
1. Fork the repository
2. Create feature branch
3. Make changes with clear commit messages
4. Test thoroughly
5. Submit pull request
## API Reference
### Environment Structure
The `environment` dict contains all runtime data:
```python
environment = {
'runtime': {
'settingsManager': settingsManager,
'commandManager': commandManager,
'screenManager': screenManager,
'inputManager': inputManager,
'outputManager': outputManager,
'debug': debugManager,
# ... other managers
},
'screen': {
'newContentText': '',
'oldContentText': '',
'newCursor': {'x': 0, 'y': 0},
'oldCursor': {'x': 0, 'y': 0},
# ... screen data
},
'general': {
'prevCommand': '',
'currCommand': '',
# ... general data
}
}
```
### Common Operations
#### Speaking Text
```python
self.env['runtime']['outputManager'].presentText('Hello')
```
#### Playing Sounds
```python
self.env['runtime']['outputManager'].playSoundIcon('Accept')
```
#### Getting Settings
```python
rate = self.env['runtime']['settingsManager'].getSetting('speech', 'rate')
```
#### Cursor Information
```python
x = self.env['screen']['newCursor']['x']
y = self.env['screen']['newCursor']['y']
```
#### Screen Content
```python
text = self.env['screen']['newContentText']
lines = text.split('\n')
current_line = lines[self.env['screen']['newCursor']['y']]
```
## Maintenance
### Release Process
1. Update version in `fenrirVersion.py`
2. Update changelog
3. Test on multiple systems
4. Tag release
5. Update documentation
### Compatibility
- Maintain Python 3.6+ compatibility
- Test on multiple Linux distributions
- Ensure driver compatibility
- Check dependencies
## Resources
- **Repository**: https://git.stormux.org/storm/fenrir
- **Wiki**: https://git.stormux.org/storm/fenrir/wiki
- **Issues**: Use repository issue tracker
- **Community**: IRC irc.stormux.org #stormux
- **Email**: stormux+subscribe@groups.io

File diff suppressed because it is too large Load Diff

View File

@ -1202,6 +1202,47 @@ link:#Settings[Settings]
=== Commandline Arguments
Fenrir supports several command-line options:
....
fenrir [OPTIONS]
....
==== Available Options
`+-h, --help+`::
Show help message and exit.
`+-v, --version+`::
Show version information and exit.
`+-f, --foreground+`::
Run Fenrir in the foreground instead of as a daemon.
`+-s, --setting SETTING-FILE+`::
Path to a custom settings file.
`+-o, --options SECTION#SETTING=VALUE;..+`::
Override settings file options (see below for details).
`+-d, --debug+`::
Enable debug mode. Debug information will be logged.
`+-p, --print+`::
Print debug messages to screen in addition to logging them.
`+-e, --emulated-pty+`::
Use PTY emulation with escape sequences for input. This enables usage in desktop/X11/Wayland environments and terminal emulators.
`+-E, --emulated-evdev+`::
Use PTY emulation with evdev for input (single instance mode).
`+-F, --force-all-screens+`::
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
`+-i, -I, --ignore-screen <SCREEN>+`::
Ignore specific screen(s). Can be used multiple times to ignore multiple screens. This is equivalent to setting ignoreScreen in the configuration file and will be combined with any existing ignore settings.
==== Set settings options
You can specify options that overwrite the setting.conf. This is done
@ -1224,9 +1265,154 @@ or change the debug level to verbose
fenrir -o "general#debugLevel=3"
....
Example using force all screens option:
....
fenrir -F
....
You can find the available sections and variables here #Settings See
Syntax link:#settings.conf syntax[#settings.conf syntax]
=== Remote Control
Fenrir includes a powerful remote control system that allows external applications and scripts to control Fenrir through Unix sockets or TCP connections.
==== Configuration
Enable remote control in settings.conf:
....
[remote]
enable=True
driver=unixDriver
enableSettingsRemote=True
enableCommandRemote=True
....
==== Using socat with Unix Sockets
The `+socat+` command provides the easiest way to send commands to Fenrir:
===== Basic Speech Control
....
# Interrupt current speech
echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Speak custom text
echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Temporarily disable speech (until next keystroke)
echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
===== Settings Control
....
# Enable highlight tracking mode
echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change speech rate
echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change punctuation level (none/some/most/all)
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Voice and TTS control
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Multiple settings at once
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuationLevel=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset all settings to defaults
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
===== Clipboard Operations
....
# Place text into clipboard
echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Export clipboard to file
echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
===== Application Control
....
# Quit Fenrir
echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
==== Command Reference
===== Available Commands
*Speech Commands:*
* `+command say <text>+` - Speak the specified text
* `+command interrupt+` - Stop current speech
* `+command tempdisablespeech+` - Disable speech until next key press
*Clipboard Commands:*
* `+command clipboard <text>+` - Add text to clipboard
* `+command exportclipboard+` - Export clipboard to file
*Window Commands:*
* `+command window <x1> <y1> <x2> <y2>+` - Define window area
* `+command resetwindow+` - Reset to full screen
*VMenu Commands:*
* `+command vmenu <menu_path>+` - Set virtual menu context
* `+command resetvmenu+` - Reset virtual menu
*Application Commands:*
* `+command quitapplication+` - Quit Fenrir
===== Available Settings
*Settings Commands:*
* `+setting set <section>#<key>=<value>+` - Set configuration value
* `+setting reset+` - Reset all settings to defaults
* `+setting save [path]+` - Save current settings
*Key Settings You Can Control:*
*Speech Settings:*
* `+speech#enabled=True/False+` - Enable/disable speech
* `+speech#rate=0.1-1.0+` - Speech rate (speed)
* `+speech#pitch=0.1-1.0+` - Speech pitch (tone)
* `+speech#volume=0.1-1.0+` - Speech volume
* `+speech#voice=voice_name+` - Voice selection (e.g., "en-us+f3")
* `+speech#module=module_name+` - TTS module (e.g., "espeak-ng")
*General Settings:*
* `+general#punctuationLevel=none/some/most/all+` - Punctuation verbosity
* `+general#autoSpellCheck=True/False+` - Automatic spell checking
* `+general#emoticons=True/False+` - Enable emoticon replacement
*Sound Settings:*
* `+sound#enabled=True/False+` - Enable/disable sound
* `+sound#volume=0.1-1.0+` - Sound volume
*Focus Settings:*
* `+focus#cursor=True/False+` - Follow text cursor
* `+focus#highlight=True/False+` - Follow text highlighting
*Keyboard Settings:*
* `+keyboard#charEchoMode=0-2+` - Character echo (0=none, 1=always, 2=capslock only)
* `+keyboard#wordEcho=True/False+` - Echo complete words
*Screen Settings:*
* `+screen#ignoreScreen=1,2,3+` - TTY screens to ignore
==== settings.conf syntax
the syntax of the link:#Settings[settings.conf] is quite simple and

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,11 @@
#!/bin/bash
#!/usr/bin/env bash
#Basic install script for Fenrir.
read -p "This will install Fenrir. Press ctrl+C to cancel, or enter to continue." continue
read -rp "This will install Fenrir. Press ctrl+C to cancel, or enter to continue."
# Fenrir main application
install -m755 -d /opt/fenrirscreenreader
cp -af src/* /opt/fenrirscreenreader
ln -fs /opt/fenrirscreenreader/fenrir-daemon /usr/bin/fenrir-daemon
ln -fs /opt/fenrirscreenreader/fenrir /usr/bin/fenrir
# tools
install -m755 -d /usr/share/fenrirscreenreader/tools
@ -33,8 +32,9 @@ cp -af config/sound/template /usr/share/sounds/fenrirscreenreader/template
# config
if [ -f "/etc/fenrirscreenreader/settings/settings.conf" ]; then
echo "Do you want to overwrite your current global settings? (y/n)"
read yn
if [ $yn = "Y" -o $yn = "y" ]; then
read -r yn
yn="${yn:0:1}"
if [[ "${yn^}" == "Y" ]]; then
mv /etc/fenrirscreenreader/settings/settings.conf /etc/fenrirscreenreader/settings/settings.conf.bak
echo "Your old settings.conf has been backed up to settings.conf.bak."
install -m644 -D "config/settings/settings.conf" /etc/fenrirscreenreader/settings/settings.conf

View File

@ -1,7 +1,10 @@
evdev>=1.1.2
daemonize>=2.5.0
dbus-python>=1.2.8
pyudev>=0.21.0
daemonize
dbus-python
evdev
pexpect
pyttsx3
pyte>=0.7.0
pyenchant
pyperclip
pyte
pyudev
pyxdg
setproctitle

View File

@ -37,7 +37,7 @@ for directory in directories:
if not forceSettingsFlag:
try:
files = [f for f in files if not f.endswith('settings.conf')]
except:
except Exception as e:
pass
elif 'config/scripts' in directory:
destDir = '/usr/share/fenrirscreenreader/scripts'
@ -99,10 +99,11 @@ setup(
"evdev>=1.1.2",
"daemonize>=2.5.0",
"dbus-python>=1.2.8",
"pyperclip",
"pyudev>=0.21.0",
"setuptools",
"setproctitle",
"pexpect",
"pyttsx3",
"pyte>=0.7.0",
],
)
@ -112,10 +113,10 @@ if not forceSettingsFlag:
# create settings file from example if not exist
if not os.path.isfile('/etc/fenrirscreenreader/settings/settings.conf'):
try:
copyfile('/etc/fenrirscreenreader/settings/settings.conf.example', '/etc/fenrirscreenreader/settings/settings.conf')
copyfile('config/fenrirscreenreader/settings/settings.conf', '/etc/fenrirscreenreader/settings/settings.conf')
print('create settings file in /etc/fenrirscreenreader/settings/settings.conf')
except:
pass
except OSError as e:
print(f"Could not copy settings file to destination: {e}")
else:
print('settings.conf file found. It is not overwritten automatical')

View File

@ -67,6 +67,17 @@ def create_argument_parser():
action='store_true',
help='Use PTY emulation with evdev for input (single instance)'
)
argumentParser.add_argument(
'-F', '--force-all-screens',
action='store_true',
help='Force Fenrir to respond on all screens, ignoring ignoreScreen setting'
)
argumentParser.add_argument(
'-i', '-I', '--ignore-screen',
metavar='SCREEN',
action='append',
help='Ignore specific screen(s). Can be used multiple times. Same as ignoreScreen setting.'
)
return argumentParser
def validate_arguments(cliArgs):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
# this command is just to initialize stuff.
# like init index lists in memoryManager

View File

@ -4,14 +4,13 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import word_utils
import string
initialized = False
try:
import enchant
initialized = True
except:
except Exception as e:
pass
class command():

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import math
class AdjustmentCommand():
"""Base class for speech and sound adjustment commands"""
def __init__(self, section, setting, direction, step=0.1):
self.section = section # 'speech' or 'sound'
self.setting = setting # 'rate', 'pitch', 'volume'
self.direction = direction # 'inc' or 'dec'
self.step = step
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
action = "Increase" if self.direction == 'inc' else "Decrease"
if self.section == 'speech':
return _(f'{action} the speech {self.setting}')
else:
return _(f'{action} the {self.section} {self.setting}')
def run(self):
if self.section == 'sound' and self.setting == 'volume':
# Sound volume uses different method
self._adjust_sound_volume()
else:
# Speech rate, pitch, volume use standard method
self._adjust_speech_setting()
def _adjust_speech_setting(self):
"""Adjust speech settings (rate, pitch, volume)"""
value = self.env['runtime']['settingsManager'].getSettingAsFloat(self.section, self.setting)
# Apply adjustment with rounding
if self.direction == 'inc':
value = round((math.ceil(10 * value) / 10) + self.step, 2)
if value > 1.0:
value = 1.0
else: # dec
value = round((math.ceil(10 * value) / 10) - self.step, 2)
if value < 0.0:
value = 0.0
# Set the new value
self.env['runtime']['settingsManager'].setSetting(self.section, self.setting, str(value))
# Present feedback
percentage = int(value * 100)
if self.section == 'speech':
feedback = _("{0} percent speech {1}").format(percentage, self.setting)
else:
feedback = _("{0} percent {1} {2}").format(percentage, self.section, self.setting)
self.env['runtime']['outputManager'].presentText(feedback, soundIcon='', interrupt=True)
def _adjust_sound_volume(self):
"""Adjust sound volume using same logic as speech"""
value = self.env['runtime']['settingsManager'].getSettingAsFloat(self.section, self.setting)
# Sound volume uses same math as speech settings
if self.direction == 'inc':
value = round((math.ceil(10 * value) / 10) + self.step, 2)
if value > 1.0:
value = 1.0
else: # dec
value = round((math.ceil(10 * value) / 10) - self.step, 2)
if value < 0.0:
value = 0.0
# Set the new value
self.env['runtime']['settingsManager'].setSetting(self.section, self.setting, str(value))
# Present feedback with appropriate sound icon
percentage = int(value * 100)
sound_icon = 'SoundOn' if self.direction == 'inc' else 'SoundOff'
feedback = _("{0} percent sound volume").format(percentage)
self.env['runtime']['outputManager'].presentText(feedback, soundIcon=sound_icon, interrupt=True)
def setCallback(self, callback):
pass

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader import fenrirVersion
class command():

View File

@ -0,0 +1,71 @@
#!/usr/bin/env python3
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return "Apply the last tested voice from safe voice browser"
def run(self):
try:
# Check if we have a tested voice
if ('commandBuffer' not in self.env or
'lastTestedModule' not in self.env['commandBuffer'] or
'lastTestedVoice' not in self.env['commandBuffer']):
self.env['runtime']['outputManager'].presentText("No voice has been tested yet", interrupt=True)
self.env['runtime']['outputManager'].presentText("Use voice browser first", interrupt=True)
return
module = self.env['commandBuffer']['lastTestedModule']
voice = self.env['commandBuffer']['lastTestedVoice']
self.env['runtime']['outputManager'].presentText(f"Applying {voice} from {module}", interrupt=True)
# Apply to runtime settings only (temporary until saved)
settingsManager = self.env['runtime']['settingsManager']
# Store old values for safety
oldDriver = settingsManager.getSetting('speech', 'driver')
oldModule = settingsManager.getSetting('speech', 'module')
oldVoice = settingsManager.getSetting('speech', 'voice')
try:
# Apply new settings to runtime only (use setSetting to update settingArgDict)
settingsManager.setSetting('speech', 'driver', 'speechdDriver')
settingsManager.setSetting('speech', 'module', module)
settingsManager.setSetting('speech', 'voice', voice)
# Apply to speech driver instance directly
if 'speechDriver' in self.env['runtime']:
speechDriver = self.env['runtime']['speechDriver']
# Set the module and voice on the driver instance
speechDriver.setModule(module)
speechDriver.setVoice(voice)
self.env['runtime']['outputManager'].presentText("Voice applied successfully!", interrupt=True)
self.env['runtime']['outputManager'].presentText("Use save settings to make permanent", interrupt=True)
self.env['runtime']['outputManager'].playSound('Accept')
except Exception as e:
# Revert on failure
settingsManager.setSetting('speech', 'driver', oldDriver)
settingsManager.setSetting('speech', 'module', oldModule)
settingsManager.setSetting('speech', 'voice', oldVoice)
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=True)
self.env['runtime']['outputManager'].playSound('Error')
except Exception as e:
self.env['runtime']['outputManager'].presentText(f"Apply voice error: {str(e)}", interrupt=True)
self.env['runtime']['outputManager'].playSound('Error')
def setCallback(self, callback):
pass

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import screen_utils
class command():

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '1'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(1, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '10'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(10, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '2'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(2, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '3'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(3, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '4'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(4, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '5'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(5, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '6'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(6, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '7'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(7, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '8'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(8, 'read')

View File

@ -4,45 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '9'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(9, 'read')

View File

@ -0,0 +1,150 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import gettext
_ = gettext.gettext
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
class BookmarkCommand():
"""Base class for bookmark operations - read, set, clear"""
def __init__(self, bookmark_id, action='read'):
self.ID = str(bookmark_id)
self.action = action
def initialize(self, environment):
self.env = environment
# Always initialize bookmark structure - all commands need this
if self.ID not in self.env['commandBuffer']['bookMarks']:
self.env['commandBuffer']['bookMarks'][self.ID] = {}
def shutdown(self):
pass
def getDescription(self):
if self.action == 'read':
return _('read Bookmark {0}').format(self.ID)
elif self.action == 'set':
return _('set Bookmark {0}').format(self.ID)
elif self.action == 'clear':
return _('remove Bookmark {0}').format(self.ID)
return f'{self.action} Bookmark {self.ID}'
def run(self):
if self.action == 'read':
self._read_bookmark()
elif self.action == 'set':
self._set_bookmark()
elif self.action == 'clear':
self._clear_bookmark()
def _read_bookmark(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText('Bookmark {0} not set'.format(self.ID), interrupt=True)
return
if currApp not in self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText('Bookmark for application {0} not set'.format(currApp), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp].get('1'):
self.env['runtime']['outputManager'].presentText('Bookmark for application {0} not set'.format(currApp), interrupt=True)
return
# Get bookmarked text
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def _set_bookmark(self):
if not self.env['commandBuffer']['Marks']['1']:
self.env['runtime']['outputManager'].presentText(_("No mark found"), interrupt=True)
return
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
self.env['commandBuffer']['bookMarks'][self.ID][currApp] = {}
self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'] = self.env['commandBuffer']['Marks']['1'].copy()
if self.env['commandBuffer']['Marks']['2']:
self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'] = self.env['commandBuffer']['Marks']['2'].copy()
else:
self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'] = None
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} set for application {1}').format(self.ID, currApp), interrupt=True)
# Clear marks after setting bookmark
self.env['commandBuffer']['Marks']['1'] = None
self.env['commandBuffer']['Marks']['2'] = None
def _clear_bookmark(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if self.ID in self.env['commandBuffer']['bookMarks'] and currApp in self.env['commandBuffer']['bookMarks'][self.ID]:
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
# Factory function to create bookmark command instances
def create_bookmark_commands():
"""Create all bookmark command instances"""
commands = {}
# Create read bookmark commands (bookmark_1 through bookmark_10)
for i in range(1, 11):
commands[f'bookmark_{i}'] = lambda i=i: BookmarkCommand(i, 'read')
# Create set bookmark commands (set_bookmark_1 through set_bookmark_10)
for i in range(1, 11):
commands[f'set_bookmark_{i}'] = lambda i=i: BookmarkCommand(i, 'set')
# Create clear bookmark commands (clear_bookmark_1 through clear_bookmark_10)
for i in range(1, 11):
commands[f'clear_bookmark_{i}'] = lambda i=i: BookmarkCommand(i, 'clear')
return commands
# For backwards compatibility, provide individual command classes
# This allows the existing command loading system to work unchanged
def _make_command_class(bookmark_id, action):
"""Create a command class for a specific bookmark and action"""
class command(BookmarkCommand):
def __init__(self):
super().__init__(bookmark_id, action)
return command
# Generate individual command classes for each bookmark operation
# These will be used by the existing command loading system
# Read bookmarks (bookmark_1.py style)
for i in range(1, 11):
globals()[f'bookmark_{i}_command'] = _make_command_class(i, 'read')
# Set bookmarks (set_bookmark_1.py style)
for i in range(1, 11):
globals()[f'set_bookmark_{i}_command'] = _make_command_class(i, 'set')
# Clear bookmarks (clear_bookmark_1.py style)
for i in range(1, 11):
globals()[f'clear_bookmark_{i}_command'] = _make_command_class(i, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '1'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(1, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '10'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(10, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '2'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(2, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '3'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(3, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '4'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(4, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '5'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(5, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '6'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(6, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '7'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(7, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '8'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(8, 'clear')

View File

@ -4,24 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'bookmark_base.py')
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
BookmarkCommand = _module.BookmarkCommand
class command():
class command(BookmarkCommand):
def __init__(self):
self.ID = '9'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(9, 'clear')

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
class command():

View File

@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
class command():

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import os
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('cycles between available keyboard layouts')
def getAvailableLayouts(self):
"""Get list of available keyboard layout files"""
layouts = []
# Check standard locations for keyboard layouts
settingsRoot = '/etc/fenrirscreenreader/'
if not os.path.exists(settingsRoot):
# Fallback to source directory
import fenrirscreenreader
fenrirPath = os.path.dirname(fenrirscreenreader.__file__)
settingsRoot = fenrirPath + '/../../config/'
keyboardPath = settingsRoot + 'keyboard/'
if os.path.exists(keyboardPath):
for file in os.listdir(keyboardPath):
if file.endswith('.conf') and not file.startswith('__') and not file.lower().startswith('pty'):
layout_name = file.replace('.conf', '')
if layout_name not in layouts:
layouts.append(layout_name)
# Ensure we have at least basic layouts
if not layouts:
layouts = ['desktop', 'laptop']
else:
layouts.sort()
return layouts
def run(self):
current_layout = self.env['runtime']['settingsManager'].getSetting('keyboard', 'keyboardLayout')
# Extract layout name from full path if needed
if '/' in current_layout:
current_layout = os.path.basename(current_layout).replace('.conf', '')
# Get available layouts
available_layouts = self.getAvailableLayouts()
# Find next layout in cycle
try:
current_index = available_layouts.index(current_layout)
next_index = (current_index + 1) % len(available_layouts)
except ValueError:
# If current layout not found, start from beginning
next_index = 0
next_layout = available_layouts[next_index]
# Update setting and reload shortcuts
self.env['runtime']['settingsManager'].setSetting('keyboard', 'keyboardLayout', next_layout)
# Reload shortcuts with new layout
try:
self.env['runtime']['inputManager'].reloadShortcuts()
self.env['runtime']['outputManager'].presentText(
_('Switched to {} keyboard layout').format(next_layout),
interrupt=True
)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut(
"Error reloading shortcuts: " + str(e),
debug.debugLevel.ERROR
)
self.env['runtime']['outputManager'].presentText(
_('Error switching keyboard layout'),
interrupt=True
)
def setCallback(self, callback):
pass

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import datetime
class command():

View File

@ -4,36 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
initialized = False
try:
import alsaaudio
initialized = True
except:
pass
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
from fenrirscreenreader.core import debug
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _("Decrease system volume")
def run(self):
if not initialized:
self.env['runtime']['outputManager'].presentText(_('alsaaudio is not installed'), interrupt=True)
return
mixer = alsaaudio.Mixer()
value = mixer.getvolume()[0]
value = value - 5
if value < 5:
value = 5
mixer.setvolume(value)
self.env['runtime']['outputManager'].presentText(_("{0} percent system volume").format(value), interrupt=True)
def setCallback(self, callback):
pass
super().__init__('alsa', 'volume', 'dec')

View File

@ -4,30 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('decrease sound volume')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.1:
value = 0.1
self.env['runtime']['settingsManager'].setSetting('sound', 'volume', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent sound volume").format(int(value * 100)), soundIcon='SoundOff', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('sound', 'volume', 'dec')

View File

@ -4,26 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Decreases the pitch of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.0:
value = 0.0
self.env['runtime']['settingsManager'].setSetting('speech', 'pitch', str(value))
self.env['runtime']['outputManager'].presentText(_('{0} percent speech pitch').format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('speech', 'pitch', 'dec')

View File

@ -4,27 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Decreases the rate of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.0:
value = 0.0
self.env['runtime']['settingsManager'].setSetting('speech', 'rate', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech rate").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('speech', 'rate', 'dec')

View File

@ -4,28 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Decreases the volume of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.1:
value = 0.1
self.env['runtime']['settingsManager'].setSetting('speech', 'volume', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech volume").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('speech', 'volume', 'dec')

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -30,7 +30,7 @@ class command():
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
clipboardFile.write(clipboard)
clipboardFile.close()
os.chmod(clipboardFilePath, 0o666)
os.chmod(clipboardFilePath, 0o644)
self.env['runtime']['outputManager'].presentText(_('clipboard exported to file'), interrupt=True)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('export_clipboard_to_file:run: Filepath:'+ clipboardFile +' trace:' + str(e),debug.debugLevel.ERROR)

View File

@ -4,16 +4,17 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import subprocess, os
from subprocess import Popen, PIPE
import os
import importlib
import _thread
import pyperclip
class command():
def __init__(self):
pass
def initialize(self, environment):
def initialize(self, environment, scriptPath=''):
self.env = environment
self.scriptPath = scriptPath
def shutdown(self):
pass
def getDescription(self):
@ -22,56 +23,48 @@ class command():
_thread.start_new_thread(self._threadRun , ())
def _threadRun(self):
try:
# Check if clipboard is empty
if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'):
self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True)
return
# Get current clipboard content
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
user = self.env['general']['currUser']
# First try to find xclip in common locations
xclip_paths = [
'/usr/bin/xclip',
'/bin/xclip',
'/usr/local/bin/xclip'
]
xclip_path = None
for path in xclip_paths:
if os.path.isfile(path) and os.access(path, os.X_OK):
xclip_path = path
# Remember original display environment variable if it exists
originalDisplay = os.environ.get('DISPLAY', '')
success = False
# Try different display options
for i in range(10):
display = f":{i}"
try:
# Set display environment variable
os.environ['DISPLAY'] = display
# Attempt to set clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
pyperclip.copy(clipboard)
# If we get here without exception, we found a working display
success = True
break
if not xclip_path:
self.env['runtime']['outputManager'].presentText(
'xclip not found in common locations',
interrupt=True
)
return
for display in range(10):
p = Popen(
['su', user, '-p', '-c', f"{xclip_path} -d :{display} -selection clipboard"],
stdin=PIPE, stdout=PIPE, stderr=PIPE, preexec_fn=os.setpgrp
)
stdout, stderr = p.communicate(input=clipboard.encode('utf-8'))
self.env['runtime']['outputManager'].interruptOutput()
stderr = stderr.decode('utf-8')
stdout = stdout.decode('utf-8')
if stderr == '':
break
if stderr != '':
self.env['runtime']['outputManager'].presentText(stderr, soundIcon='', interrupt=False)
except Exception:
# Failed for this display, try next one
continue
# Restore original display setting
if originalDisplay:
os.environ['DISPLAY'] = originalDisplay
else:
self.env['runtime']['outputManager'].presentText('exported to the X session.', interrupt=True)
os.environ.pop('DISPLAY', None)
# Notify the user of the result
if success:
self.env['runtime']['outputManager'].presentText(_('exported to the X session.'), interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(_('failed to export to X clipboard. No available display found.'), interrupt=True)
except Exception as e:
self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False)
def setCallback(self, callback):
pass

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
import os

View File

@ -4,10 +4,11 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import subprocess, os
from subprocess import Popen, PIPE
import importlib
import _thread
import pyperclip
import os
class command():
def __init__(self):
pass
@ -22,33 +23,41 @@ class command():
_thread.start_new_thread(self._threadRun , ())
def _threadRun(self):
try:
# Find xclip path
xclip_paths = ['/usr/bin/xclip', '/bin/xclip', '/usr/local/bin/xclip']
xclip_path = None
for path in xclip_paths:
if os.path.isfile(path) and os.access(path, os.X_OK):
xclip_path = path
break
if not xclip_path:
self.env['runtime']['outputManager'].presentText('xclip not found in common locations', interrupt=True)
return
xClipboard = ''
for display in range(10):
p = Popen('su ' + self.env['general']['currUser'] + ' -p -c "' + xclip_path + ' -d :' + str(display) + ' -o"', stdout=PIPE, stderr=PIPE, shell=True)
stdout, stderr = p.communicate()
self.env['runtime']['outputManager'].interruptOutput()
stderr = stderr.decode('utf-8')
xClipboard = stdout.decode('utf-8')
if (stderr == ''):
break
if stderr != '':
self.env['runtime']['outputManager'].presentText(stderr , soundIcon='', interrupt=False)
# Remember original display environment variable if it exists
originalDisplay = os.environ.get('DISPLAY', '')
clipboardContent = None
# Try different display options
for i in range(10):
display = f":{i}"
try:
# Set display environment variable
os.environ['DISPLAY'] = display
# Attempt to get clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
clipboardContent = pyperclip.paste()
# If we get here without exception, we found a working display
if clipboardContent:
break
except Exception:
# Failed for this display, try next one
continue
# Restore original display setting
if originalDisplay:
os.environ['DISPLAY'] = originalDisplay
else:
self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', xClipboard)
os.environ.pop('DISPLAY', None)
# Process the clipboard content if we found any
if clipboardContent and isinstance(clipboardContent, str):
self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', clipboardContent)
self.env['runtime']['outputManager'].presentText('Import to Clipboard', soundIcon='CopyToClipboard', interrupt=True)
self.env['runtime']['outputManager'].presentText(xClipboard, soundIcon='', interrupt=False)
self.env['runtime']['outputManager'].presentText(clipboardContent, soundIcon='', interrupt=False)
else:
self.env['runtime']['outputManager'].presentText('No text found in clipboard or no accessible display', interrupt=True)
except Exception as e:
self.env['runtime']['outputManager'].presentText(e , soundIcon='', interrupt=False)
self.env['runtime']['outputManager'].presentText(str(e), soundIcon='', interrupt=False)
def setCallback(self, callback):
pass

View File

@ -4,36 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
initialized = False
try:
import alsaaudio
initialized = True
except:
pass
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
from fenrirscreenreader.core import debug
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _("Increase system volume")
def run(self):
if not initialized:
self.env['runtime']['outputManager'].presentText(_('alsaaudio is not installed'), interrupt=True)
return
mixer = alsaaudio.Mixer()
value = mixer.getvolume()[0]
value = value + 5
if value > 100:
value = 100
mixer.setvolume(value)
self.env['runtime']['outputManager'].presentText(_("{0} percent system volume").format(value), interrupt=True)
def setCallback(self, callback):
pass
super().__init__('alsa', 'volume', 'inc')

View File

@ -4,29 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('adjusts the volume for in coming sounds')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume')
value = round((math.ceil(10 * value) / 10) + 0.1, 2)
if value > 1.0:
value = 1.0
self.env['runtime']['settingsManager'].setSetting('sound', 'volume', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent sound volume").format(int(value * 100)), soundIcon='SoundOn', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('sound', 'volume', 'inc')

View File

@ -4,27 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Increases the pitch of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch')
value = round((math.ceil(10 * value) / 10) + 0.1, 2)
if value > 1.0:
value = 1.0
self.env['runtime']['settingsManager'].setSetting('speech', 'pitch', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech pitch").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('speech', 'pitch', 'inc')

View File

@ -4,27 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Increase the speech rate')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate')
value = round((math.ceil(10 * value) / 10) + 0.1, 2)
if value > 1.0:
value = 1.0
self.env['runtime']['settingsManager'].setSetting('speech', 'rate', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech rate").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('speech', 'rate', 'inc')

View File

@ -4,27 +4,14 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import math
import os
import importlib.util
_base_path = os.path.join(os.path.dirname(__file__), 'adjustment_base.py')
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
AdjustmentCommand = _module.AdjustmentCommand
class command():
class command(AdjustmentCommand):
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Increase the speech volume')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume')
value = round((math.ceil(10 * value) / 10) + 0.1, 2)
if value > 1.0:
value = 1.0
self.env['runtime']['settingsManager'].setSetting('speech', 'volume', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech volume").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__('speech', 'volume', 'inc')

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
import time
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import line_utils
class command():

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -4,7 +4,6 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
from fenrirscreenreader.core import debug
class command():
def __init__(self):

View File

@ -0,0 +1,209 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
import re
import time
import threading
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
# Use commandBuffer like other commands
if 'progressMonitoring' not in self.env['commandBuffer']:
# Check if progress monitoring should be enabled by default from settings
try:
defaultEnabled = self.env['runtime']['settingsManager'].getSettingAsBool('sound', 'progressMonitoring')
except Exception as e:
# If setting doesn't exist, default to False
defaultEnabled = False
self.env['commandBuffer']['progressMonitoring'] = defaultEnabled
self.env['commandBuffer']['lastProgressTime'] = 0
self.env['commandBuffer']['lastProgressValue'] = -1
def shutdown(self):
self.stopProgressMonitoring()
def getDescription(self):
return _('Toggle progress bar monitoring with ascending tones')
def run(self):
# Check if commandBuffer exists
if 'progressMonitoring' not in self.env['commandBuffer']:
self.env['commandBuffer']['progressMonitoring'] = False
self.env['commandBuffer']['lastProgressTime'] = 0
self.env['commandBuffer']['lastProgressValue'] = -1
if self.env['commandBuffer']['progressMonitoring']:
self.stopProgressMonitoring()
self.env['runtime']['outputManager'].presentText(_("Progress monitoring disabled"), interrupt=True)
else:
self.startProgressMonitoring()
self.env['runtime']['outputManager'].presentText(_("Progress monitoring enabled"), interrupt=True)
def startProgressMonitoring(self):
self.env['commandBuffer']['progressMonitoring'] = True
self.env['commandBuffer']['lastProgressTime'] = time.time()
self.env['commandBuffer']['lastProgressValue'] = -1
# Don't control speech - let user decide with silence_until_prompt
def stopProgressMonitoring(self):
self.env['commandBuffer']['progressMonitoring'] = False
# Don't control speech - progress monitor is beep-only
def detectProgress(self, text):
if not self.env['commandBuffer']['progressMonitoring']:
return
# Skip progress detection if current screen looks like a prompt
if self.isCurrentLinePrompt():
return
currentTime = time.time()
# Pattern 1: Percentage (50%, 25.5%, etc.)
percentMatch = re.search(r'(\d+(?:\.\d+)?)\s*%', text)
if percentMatch:
percentage = float(percentMatch.group(1))
if percentage != self.env['commandBuffer']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 2: Fraction (15/100, 3 of 10, etc.)
fractionMatch = re.search(r'(\d+)\s*(?:of|/)\s*(\d+)', text)
if fractionMatch:
current = int(fractionMatch.group(1))
total = int(fractionMatch.group(2))
if total > 0:
percentage = (current / total) * 100
if percentage != self.env['commandBuffer']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
# Improved pattern to avoid matching IRC channels like [#channel]
barMatch = re.search(r'\[([#=\-\*]+)([\s\.]*)\]', text)
if barMatch:
filled = len(barMatch.group(1))
unfilled = len(barMatch.group(2))
total = filled + unfilled
# Require at least 2 progress chars total and unfilled portion must be spaces/dots
if total >= 2 and (not barMatch.group(2) or re.match(r'^[\s\.]*$', barMatch.group(2))):
percentage = (filled / total) * 100
if percentage != self.env['commandBuffer']['lastProgressValue']:
self.playProgressTone(percentage)
self.env['commandBuffer']['lastProgressValue'] = percentage
self.env['commandBuffer']['lastProgressTime'] = currentTime
return
# Pattern 4: Generic activity indicators (Loading..., Working..., etc.)
activityPattern = re.search(r'(loading|processing|working|installing|downloading|compiling|building).*\.{2,}', text, re.IGNORECASE)
if activityPattern:
# Play a steady beep every 2 seconds for ongoing activity
if currentTime - self.env['commandBuffer']['lastProgressTime'] >= 2.0:
self.playActivityBeep()
self.env['commandBuffer']['lastProgressTime'] = currentTime
def playProgressTone(self, percentage):
# Map 0-100% to 400-1200Hz frequency range
frequency = 400 + (percentage * 8)
frequency = max(400, min(1200, frequency)) # Clamp to safe range
self.env['runtime']['outputManager'].playFrequence(frequency, 0.15, interrupt=False)
def playActivityBeep(self):
# Single tone for generic activity
self.env['runtime']['outputManager'].playFrequence(800, 0.1, interrupt=False)
def isCurrentLinePrompt(self):
"""Check if the current line looks like a standalone prompt (not command with progress)"""
import re
try:
# Get the current screen content
if not self.env['screen']['newContentText']:
return False
lines = self.env['screen']['newContentText'].split('\n')
if not lines:
return False
# Check the last line (most common) and current cursor line for prompt patterns
linesToCheck = []
# Add last line (most common for prompts)
if lines:
linesToCheck.append(lines[-1])
# Add current cursor line if different from last line
if (self.env['screen']['newCursor']['y'] < len(lines) and
self.env['screen']['newCursor']['y'] != len(lines) - 1):
linesToCheck.append(lines[self.env['screen']['newCursor']['y']])
# Standalone prompt patterns (no commands mixed in)
standalonePromptPatterns = [
r'^\s*\$\s*$', # Just $ (with whitespace)
r'^\s*#\s*$', # Just # (with whitespace)
r'^\s*>\s*$', # Just > (with whitespace)
r'^\[.*\]\s*[\\\$#>]\s*$', # [path]$ without commands
r'^[a-zA-Z0-9._-]+[\\\$#>]\s*$', # bash-5.1$ without commands
# Interactive prompt patterns (these ARE standalone)
r'.*\?\s*\[[YyNn]/[YyNn]\]\s*$', # ? [Y/n] or ? [y/N] style
r'.*\?\s*\[[Yy]es/[Nn]o\]\s*$', # ? [Yes/No] style
r'.*continue\?\s*\[[YyNn]/[YyNn]\].*$', # "continue? [Y/n]" style
r'^::.*\?\s*\[[YyNn]/[YyNn]\].*$', # pacman style prompts
# Authentication prompts (these ARE standalone)
r'^\[[Ss]udo\]\s*[Pp]assword\s*for\s+.*:\s*$', # [sudo] password
r'^[Pp]assword\s*:\s*$', # Password:
r'.*[Pp]assword\s*:\s*$', # general password prompts
# Continuation prompts (these ARE standalone)
r'^[Pp]ress\s+any\s+key\s+to\s+continue.*$', # Press any key
r'^[Aa]re\s+you\s+sure\?\s*.*$', # Are you sure?
]
for line in linesToCheck:
line = line.strip()
if not line:
continue
# Check if this line contains both a prompt AND other content (like commands)
# If so, don't treat it as a standalone prompt
hasPromptMarker = bool(re.search(r'.*@.*[\\\$#>]', line) or re.search(r'^\[.*\]\s*[\\\$#>]', line))
if hasPromptMarker:
# If line has prompt marker but also has significant content after it,
# it's a command line, not a standalone prompt
promptEnd = max(
line.rfind('$'),
line.rfind('#'),
line.rfind('>'),
line.rfind('\\')
)
if promptEnd >= 0 and promptEnd < len(line) - 5: # More than just whitespace after prompt
continue # This is a command line, not a standalone prompt
for pattern in standalonePromptPatterns:
try:
if re.search(pattern, line):
return True
except re.error:
continue
return False
except Exception as e:
# If anything fails, assume it's not a prompt to be safe
return False
def setCallback(self, callback):
pass

Some files were not shown because too many files have changed in this diff Show More