Compare commits
42 Commits
2025.04.28
...
testing
Author | SHA1 | Date | |
---|---|---|---|
4bcf82178e | |||
beae1866bb | |||
40b88efa34 | |||
b1edc53362 | |||
dda84b9905 | |||
64e79f6945 | |||
f4ed8da4c3 | |||
a5ca1d28e8 | |||
4e193f133f | |||
27dcff23bb | |||
d81d563bb6 | |||
43871cea3c | |||
72bd334d65 | |||
e76b914d6e | |||
83cb330d34 | |||
b0ac6e1409 | |||
6998706934 | |||
62e1001679 | |||
d935ef2e3c | |||
e2fb28d92f | |||
8a223282df | |||
91c97dd1dd | |||
5cc719a6f3 | |||
ddc1b43304 | |||
6ad11effc6 | |||
ca0e3b5987 | |||
0009d90a68 | |||
2c2efc56f0 | |||
27c35939b1 | |||
7e87ebf04b | |||
ec6c135581 | |||
998c63cc71 | |||
26c6e32c59 | |||
97e2da614b | |||
0930a86ce7 | |||
a742c12cd8 | |||
8d50003730 | |||
bd151c7cec | |||
6f4784daed | |||
8c471adfa4 | |||
77065c55b4 | |||
7f75c231e1 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ dist/
|
||||
build/
|
||||
*.kate-swp
|
||||
.directory
|
||||
CLAUDE.md
|
||||
|
28
CREDITS
28
CREDITS
@ -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
517
README.md
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -4,8 +4,8 @@
|
||||
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
|
||||
[levelDict]
|
||||
none:===:
|
||||
some:===:-$~+*-/\@#_
|
||||
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
|
||||
some:===:-$~+*-/\@#
|
||||
most:===:.,:-_$~+*-/\@!#%^&*()[]}{<>;
|
||||
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
|
||||
|
||||
[punctDict]
|
||||
|
@ -4,8 +4,8 @@
|
||||
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
|
||||
[levelDict]
|
||||
none:===:
|
||||
some:===:-$~+*-/\@_
|
||||
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
|
||||
some:===:-$~+*-/\@
|
||||
most:===:.,:-$~+*-_/\@!#%^&*()[]}{<>;
|
||||
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
|
||||
|
||||
[punctDict]
|
||||
|
@ -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,7 +95,7 @@ fenrirMaxRate=450
|
||||
driver=vcsaDriver
|
||||
encoding=auto
|
||||
screenUpdateDelay=0.05
|
||||
ignoreScreen=
|
||||
ignoreScreen=7
|
||||
autodetectIgnoreScreen=True
|
||||
|
||||
[keyboard]
|
||||
@ -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
|
||||
@ -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=
|
||||
@ -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.
Binary file not shown.
@ -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'
|
||||
|
@ -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
|
3263
docs/fenrir.1
3263
docs/fenrir.1
File diff suppressed because it is too large
Load Diff
186
docs/fenrir.adoc
186
docs/fenrir.adoc
@ -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
|
||||
|
1892
docs/user.md
1892
docs/user.md
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
daemonize
|
||||
dbus-python
|
||||
evdev
|
||||
pexpect
|
||||
pyenchant
|
||||
|
2
setup.py
2
setup.py
@ -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'
|
||||
|
11
src/fenrir
11
src/fenrir
@ -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):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
|
88
src/fenrirscreenreader/commands/commands/adjustment_base.py
Normal file
88
src/fenrirscreenreader/commands/commands/adjustment_base.py
Normal 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
|
@ -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():
|
||||
|
@ -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
|
@ -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():
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
150
src/fenrirscreenreader/commands/commands/bookmark_base.py
Normal file
150
src/fenrirscreenreader/commands/commands/bookmark_base.py
Normal 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')
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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():
|
||||
|
@ -2,7 +2,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
from fenrirscreenreader.utils import mark_utils
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -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
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
import datetime
|
||||
|
||||
class command():
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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)
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
import os
|
||||
import importlib
|
||||
import _thread
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
import importlib
|
||||
import _thread
|
||||
import pyperclip
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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')
|
||||
|
@ -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():
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -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():
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
import time
|
||||
|
||||
class command():
|
||||
|
@ -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():
|
||||
|
@ -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():
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
209
src/fenrirscreenreader/commands/commands/progress_bar_monitor.py
Normal file
209
src/fenrirscreenreader/commands/commands/progress_bar_monitor.py
Normal 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
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
@ -4,7 +4,6 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributers.
|
||||
|
||||
from fenrirscreenreader.core import debug
|
||||
|
||||
class command():
|
||||
def __init__(self):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user