Compare commits
39 Commits
2025.12.06
...
6d4f55ffe5
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d4f55ffe5 | |||
| 8638bca1d5 | |||
| 0273f9b956 | |||
| 1670c5711b | |||
| fed8f24126 | |||
| 40120f825c | |||
| 17dea6b026 | |||
| 57c09e0db9 | |||
| 4050c32a16 | |||
| 2e10c1c43b | |||
| 1e67876883 | |||
| 14cf6b6088 | |||
| 900a027643 | |||
| 0e50175463 | |||
| 7283f04778 | |||
| 8d3495f74f | |||
| a6cd47dafc | |||
| 0bb2e52deb | |||
| b8eb815a86 | |||
| beca468338 | |||
| a26fe26c8c | |||
| 508fd11610 | |||
| afe0e71a1d | |||
| 9e8d0b3869 | |||
| d7f86ca0de | |||
| 49a79d2722 | |||
| 4ab024d115 | |||
| c4ae27a01b | |||
| 668d39b444 | |||
| 8b25afbf5a | |||
| efeb040f75 | |||
| 7a17b36d50 | |||
| 047a31b4bf | |||
| 096aef9f08 | |||
| 76472b83b5 | |||
| a52bf624ec | |||
| f4e28a246f | |||
| 560ceb26c9 | |||
| 61868c94e5 |
@@ -1,7 +1,6 @@
|
|||||||
# Fenrir
|
# Fenrir
|
||||||
|
|
||||||
A modern, modular, flexible and fast console screen reader.
|
A modern, modular, flexible and fast console screen reader for Linux.
|
||||||
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.
|
This software is licensed under the LGPL v3.
|
||||||
|
|
||||||
**Current maintainer:** Storm Dragon
|
**Current maintainer:** Storm Dragon
|
||||||
@@ -9,7 +8,7 @@ This software is licensed under the LGPL v3.
|
|||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
- **Multiple Interface Support**: Works in Linux TTY, and terminal emulators
|
- **Linux Console Support**: Works as a Linux TTY screen reader, with optional X11 terminal mode
|
||||||
- **Flexible Driver System**: Modular architecture with multiple drivers for speech, sound, input, and screen
|
- **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
|
- **Review Mode**: Navigate and review screen content without moving the edit cursor
|
||||||
- **Table Navigation**: Advanced table mode with column headers, cell-by-cell navigation, and boundary feedback
|
- **Table Navigation**: Advanced table mode with column headers, cell-by-cell navigation, and boundary feedback
|
||||||
@@ -24,12 +23,14 @@ This software is licensed under the LGPL v3.
|
|||||||
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
|
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
|
||||||
|
|
||||||
|
|
||||||
## OS Requirements
|
## Platform Support
|
||||||
|
|
||||||
- Linux (ptyDriver, vcsaDriver, evdevDriver) - Primary platform with full support
|
Fenrir is a Linux screen reader. Linux is the only officially supported platform.
|
||||||
- macOS (ptyDriver) - Limited support
|
|
||||||
- BSD (ptyDriver) - Limited support
|
**Other platforms (macOS, BSD, Windows):** Pull requests adding support for other operating systems may be accepted provided they do not break Linux functionality. However, no special care will be taken to preserve functionality on secondary platforms. If changes to Fenrir break support on a non-Linux OS, it is the responsibility of third-party contributors to submit fixes.
|
||||||
- Windows (ptyDriver) - Limited support
|
|
||||||
|
- Linux TTY (`vcsaDriver`, `evdevDriver`) - Full support
|
||||||
|
- X11 terminal emulators (`-x`, `ptyDriver`, `x11Driver`) - Supported
|
||||||
|
|
||||||
|
|
||||||
## Core Requirements
|
## Core Requirements
|
||||||
@@ -49,11 +50,8 @@ This software is licensed under the LGPL v3.
|
|||||||
- ReadWrite permission:
|
- ReadWrite permission:
|
||||||
- /dev/input
|
- /dev/input
|
||||||
- /dev/uinput
|
- /dev/uinput
|
||||||
2. **ptyDriver** - Terminal emulation input driver (cross-platform)
|
2. **x11Driver** - X11 terminal-scoped input driver for `fenrir -x`
|
||||||
- python-pyte
|
- python-xlib
|
||||||
3. **atspiDriver** - AT-SPI input driver for desktop environments
|
|
||||||
- python-pyatspi2
|
|
||||||
|
|
||||||
### Remote Drivers:
|
### Remote Drivers:
|
||||||
1. **unixDriver** - Unix socket remote control (default)
|
1. **unixDriver** - Unix socket remote control (default)
|
||||||
- socat (for command-line interaction)
|
- socat (for command-line interaction)
|
||||||
@@ -70,7 +68,7 @@ This software is licensed under the LGPL v3.
|
|||||||
- /dev/tty[1-64]
|
- /dev/tty[1-64]
|
||||||
- /dev/vcsa[1-64]
|
- /dev/vcsa[1-64]
|
||||||
- read logind DBUS
|
- read logind DBUS
|
||||||
2. **ptyDriver** - Terminal emulation driver (cross-platform)
|
2. **ptyDriver** - Terminal emulation screen driver for `fenrir -x`
|
||||||
- python-pyte
|
- python-pyte
|
||||||
|
|
||||||
|
|
||||||
@@ -131,8 +129,8 @@ Settings are located in:
|
|||||||
By default Fenrir uses:
|
By default Fenrir uses:
|
||||||
- **Sound driver**: genericDriver (via sox)
|
- **Sound driver**: genericDriver (via sox)
|
||||||
- **Speech driver**: speechdDriver (via speech-dispatcher)
|
- **Speech driver**: speechdDriver (via speech-dispatcher)
|
||||||
- **Input driver**: evdevDriver (Linux) or ptyDriver (other platforms)
|
- **Input driver**: evdevDriver for Linux TTY, x11Driver for `fenrir -x`
|
||||||
- **Screen driver**: vcsaDriver (Linux TTY) or ptyDriver (terminal emulation)
|
- **Screen driver**: vcsaDriver for Linux TTY, ptyDriver for `fenrir -x`
|
||||||
|
|
||||||
## Audio Configuration
|
## Audio Configuration
|
||||||
|
|
||||||
@@ -222,7 +220,7 @@ Fenrir supports two main keyboard layouts:
|
|||||||
Configure in `/etc/fenrir/settings/settings.conf`:
|
Configure in `/etc/fenrir/settings/settings.conf`:
|
||||||
```ini
|
```ini
|
||||||
[keyboard]
|
[keyboard]
|
||||||
keyboardLayout=desktop # or 'laptop'
|
keyboard_layout=desktop # or 'laptop'
|
||||||
```
|
```
|
||||||
|
|
||||||
### First Time Setup
|
### First Time Setup
|
||||||
@@ -255,15 +253,16 @@ Enable remote control in `/etc/fenrir/settings/settings.conf`:
|
|||||||
enable=True
|
enable=True
|
||||||
driver=unixDriver # or tcpDriver
|
driver=unixDriver # or tcpDriver
|
||||||
port=22447 # for TCP driver
|
port=22447 # for TCP driver
|
||||||
socketFile= # custom socket path (optional)
|
socket_file= # custom socket path (optional)
|
||||||
enableSettingsRemote=True # allow settings changes
|
enable_settings_remote=True # allow settings changes
|
||||||
enableCommandRemote=True # allow command execution
|
enable_command_remote=True # allow command execution
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remote Drivers
|
### Remote Drivers
|
||||||
|
|
||||||
1. **unixDriver** (recommended): Uses Unix domain sockets
|
1. **unixDriver** (recommended): Uses Unix domain sockets
|
||||||
- Socket location: `/tmp/fenrirscreenreader-deamon.sock` (TTY mode) or `/tmp/fenrirscreenreader-<pid>.sock`
|
- Socket location: `/tmp/fenrirscreenreader-deamon.sock` for the standard control socket
|
||||||
|
- `fenrir -x` instances also create private sockets: `/tmp/fenrirscreenreader-<pid>.sock`
|
||||||
- More secure, local-only access
|
- More secure, local-only access
|
||||||
- Works with `socat`
|
- Works with `socat`
|
||||||
|
|
||||||
@@ -276,6 +275,19 @@ enableCommandRemote=True # allow command execution
|
|||||||
|
|
||||||
The `socat` command provides the easiest way to send commands to Fenrir:
|
The `socat` command provides the easiest way to send commands to Fenrir:
|
||||||
|
|
||||||
|
#### Instance Discovery
|
||||||
|
```bash
|
||||||
|
# List registered Fenrir instances and their socket paths
|
||||||
|
echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
In X terminal mode (`fenrir -x`), multiple Fenrir instances can run at the same
|
||||||
|
time. Each instance has a private socket, and one instance may also own the
|
||||||
|
standard control socket. Use `ls` or `command ls` on the standard socket to find
|
||||||
|
the private socket for a specific instance. Untargeted commands sent through a
|
||||||
|
shared or broadcast path are claimed by one instance so duplicate instances do
|
||||||
|
not all perform the same action.
|
||||||
|
|
||||||
#### Basic Speech Control
|
#### Basic Speech Control
|
||||||
```bash
|
```bash
|
||||||
# Interrupt current speech
|
# Interrupt current speech
|
||||||
@@ -299,8 +311,8 @@ echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreade
|
|||||||
echo "setting set speech#volume=0.9" | 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)
|
# Change punctuation level (none/some/most/all)
|
||||||
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
echo "setting set general#punctuationLevel=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set general#punctuation_level=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Voice and TTS engine control
|
# Voice and TTS engine control
|
||||||
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
@@ -311,14 +323,14 @@ echo "setting set sound#enabled=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenre
|
|||||||
echo "setting set sound#volume=0.5" | 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
|
# Keyboard and input settings
|
||||||
echo "setting set keyboard#charEchoMode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set keyboard#char_echo_mode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
echo "setting set keyboard#wordEcho=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set keyboard#word_echo=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Screen control (ignore specific TTYs)
|
# Screen control (ignore specific TTYs)
|
||||||
echo "setting set screen#ignoreScreen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set screen#ignore_screen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Multiple settings at once
|
# 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
|
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Reset all settings to defaults
|
# Reset all settings to defaults
|
||||||
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
@@ -403,6 +415,9 @@ setting <action> [parameters]
|
|||||||
**Application Commands:**
|
**Application Commands:**
|
||||||
- `command quitapplication` - Quit Fenrir
|
- `command quitapplication` - Quit Fenrir
|
||||||
|
|
||||||
|
**Instance Commands:**
|
||||||
|
- `ls` / `list` / `command ls` / `command list` - List registered Fenrir instances and their socket paths
|
||||||
|
|
||||||
#### Available Settings
|
#### Available Settings
|
||||||
|
|
||||||
**Settings Commands:**
|
**Settings Commands:**
|
||||||
@@ -421,7 +436,7 @@ setting <action> [parameters]
|
|||||||
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
|
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
|
||||||
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
|
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
|
||||||
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
|
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
|
||||||
- `speech#autoReadIncoming=True/False` - Auto-read new text
|
- `speech#auto_read_incoming=True/False` - Auto-read new text
|
||||||
|
|
||||||
*Sound Settings:*
|
*Sound Settings:*
|
||||||
- `sound#enabled=True/False` - Enable/disable sound
|
- `sound#enabled=True/False` - Enable/disable sound
|
||||||
@@ -430,32 +445,32 @@ setting <action> [parameters]
|
|||||||
- `sound#theme=theme_name` - Sound theme
|
- `sound#theme=theme_name` - Sound theme
|
||||||
|
|
||||||
*General Settings:*
|
*General Settings:*
|
||||||
- `general#punctuationLevel=none/some/most/all` - Punctuation verbosity
|
- `general#punctuation_level=none/some/most/all` - Punctuation verbosity
|
||||||
- `general#debugLevel=0-3` - Debug level
|
- `general#debug_level=0-3` - Debug level
|
||||||
- `general#emoticons=True/False` - Enable emoticon replacement
|
- `general#emoticons=True/False` - Enable emoticon replacement
|
||||||
- `general#autoSpellCheck=True/False` - Automatic spell checking
|
- `general#auto_spell_check=True/False` - Automatic spell checking
|
||||||
|
|
||||||
*Focus Settings:*
|
*Focus Settings:*
|
||||||
- `focus#cursor=True/False` - Follow text cursor
|
- `focus#cursor=True/False` - Follow text cursor
|
||||||
- `focus#highlight=True/False` - Follow text highlighting
|
- `focus#highlight=True/False` - Follow text highlighting
|
||||||
|
|
||||||
*Keyboard Settings:*
|
*Keyboard Settings:*
|
||||||
- `keyboard#charEchoMode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
|
- `keyboard#char_echo_mode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
|
||||||
- `keyboard#wordEcho=True/False` - Echo complete words
|
- `keyboard#word_echo=True/False` - Echo complete words
|
||||||
- `keyboard#charDeleteEcho=True/False` - Echo deleted characters
|
- `keyboard#char_delete_echo=True/False` - Echo deleted characters
|
||||||
- `keyboard#interruptOnKeyPress=True/False` - Interrupt speech on key press
|
- `keyboard#interrupt_on_key_press=True/False` - Interrupt speech on key press
|
||||||
|
|
||||||
*Screen Settings:*
|
*Screen Settings:*
|
||||||
- `screen#ignoreScreen=1,2,3` - TTY screens to ignore
|
- `screen#ignore_screen=1,2,3` - TTY screens to ignore
|
||||||
- `screen#autodetectIgnoreScreen=True/False` - Auto-detect screens to ignore
|
- `screen#autodetect_ignore_screen=True/False` - Auto-detect screens to ignore
|
||||||
- `screen#screenUpdateDelay=float` - Screen update delay
|
- `screen#screen_update_delay=float` - Screen update delay
|
||||||
|
|
||||||
*Time Settings:*
|
*Time Settings:*
|
||||||
- `time#enabled=True/False` - Enable time announcements
|
- `time#enabled=True/False` - Enable time announcements
|
||||||
- `time#presentTime=True/False` - Announce time
|
- `time#present_time=True/False` - Announce time
|
||||||
- `time#presentDate=True/False` - Announce date changes
|
- `time#present_date=True/False` - Announce date changes
|
||||||
- `time#delaySec=seconds` - Announcement interval
|
- `time#delay_sec=seconds` - Announcement interval
|
||||||
- `time#onMinutes=00,30` - Specific minutes to announce
|
- `time#on_minutes=00,30` - Specific minutes to announce
|
||||||
|
|
||||||
## Table Navigation
|
## Table Navigation
|
||||||
|
|
||||||
@@ -623,7 +638,7 @@ rsync -av source/ destination/ # File synchronization
|
|||||||
### Customization
|
### Customization
|
||||||
|
|
||||||
Progress monitoring can be configured through settings:
|
Progress monitoring can be configured through settings:
|
||||||
- **Default enabled**: Set `progressMonitoring=True` in sound section
|
- **Default enabled**: Set `progress_monitoring=True` in sound section
|
||||||
- **Sound integration**: Works with all sound drivers (sox, gstreamer)
|
- **Sound integration**: Works with all sound drivers (sox, gstreamer)
|
||||||
- **Remote control**: Enable/disable through remote commands
|
- **Remote control**: Enable/disable through remote commands
|
||||||
|
|
||||||
@@ -677,8 +692,8 @@ send_fenrir_command("setting set speech#rate=0.9")
|
|||||||
- TCP driver binds only to localhost (127.0.0.1)
|
- TCP driver binds only to localhost (127.0.0.1)
|
||||||
- Socket file permissions are set to write-only (0o222)
|
- Socket file permissions are set to write-only (0o222)
|
||||||
- Commands are processed with Fenrir's privileges
|
- Commands are processed with Fenrir's privileges
|
||||||
- Settings changes can be disabled via `enableSettingsRemote=False`
|
- Settings changes can be disabled via `enable_settings_remote=False`
|
||||||
- Command execution can be disabled via `enableCommandRemote=False`
|
- Command execution can be disabled via `enable_command_remote=False`
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
@@ -688,7 +703,7 @@ send_fenrir_command("setting set speech#rate=0.9")
|
|||||||
- Ensure remote driver is enabled in settings
|
- Ensure remote driver is enabled in settings
|
||||||
|
|
||||||
**Commands not working:**
|
**Commands not working:**
|
||||||
- Verify `enableCommandRemote=True` in settings
|
- Verify `enable_command_remote=True` in settings
|
||||||
- Check Fenrir debug logs: `/var/log/fenrir.log`
|
- Check Fenrir debug logs: `/var/log/fenrir.log`
|
||||||
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
|
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
|
||||||
|
|
||||||
@@ -708,9 +723,9 @@ fenrir [OPTIONS]
|
|||||||
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings file options
|
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings file options
|
||||||
- `-d, --debug` - Enable debug mode
|
- `-d, --debug` - Enable debug mode
|
||||||
- `-p, --print` - Print debug messages to screen
|
- `-p, --print` - Print debug messages to screen
|
||||||
- `-e, --emulated-pty` - Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)
|
- `-x, --x11` - Use PTY emulation with X11 keyboard input scoped to the terminal window
|
||||||
- `-E, --emulated-evdev` - Use PTY emulation with evdev for input (single instance)
|
- `--x11-window-id WINDOWID` - X11 window id to use for `--x11` terminal mode
|
||||||
- `-F, --force-all-screens` - Force Fenrir to respond on all screens, ignoring ignoreScreen setting
|
- `-F, --force-all-screens` - Force Fenrir to respond on all screens, ignoring ignore_screen setting
|
||||||
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
|
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
|
||||||
|
|
||||||
### Examples:
|
### Examples:
|
||||||
@@ -718,13 +733,16 @@ fenrir [OPTIONS]
|
|||||||
# Run in foreground with debug output
|
# Run in foreground with debug output
|
||||||
sudo fenrir -f -d
|
sudo fenrir -f -d
|
||||||
|
|
||||||
# Use PTY emulation for desktop use
|
# Use PTY emulation with X11 terminal-scoped keybindings
|
||||||
sudo fenrir -e
|
fenrir -x
|
||||||
|
|
||||||
|
# Use X11 mode with an explicit terminal window id
|
||||||
|
fenrir -x --x11-window-id 0x123456
|
||||||
|
|
||||||
# Override settings via command line
|
# Override settings via command line
|
||||||
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
|
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
|
||||||
|
|
||||||
# Force Fenrir to work on all screens (ignore ignoreScreen setting)
|
# Force Fenrir to work on all screens (ignore ignore_screen setting)
|
||||||
sudo fenrir -F
|
sudo fenrir -F
|
||||||
|
|
||||||
# Ignore specific screens
|
# Ignore specific screens
|
||||||
@@ -732,6 +750,18 @@ sudo fenrir --ignore-screen 1
|
|||||||
sudo fenrir -i 1 -i 2 # Ignore screens 1 and 2
|
sudo fenrir -i 1 -i 2 # Ignore screens 1 and 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### X11 Terminal Mode
|
||||||
|
|
||||||
|
`fenrir -x` runs Fenrir inside a GUI terminal without root. It uses the PTY screen driver for terminal contents and the X11 input driver for Fenrir keybindings. Unlike evdev mode, it does not listen to the whole desktop; key grabs are scoped to the target terminal window so desktop screen readers can keep their own global bindings outside that terminal.
|
||||||
|
|
||||||
|
By default, Fenrir targets the X11 window from `WINDOWID`, falling back to the active X11 window. If a terminal does not set `WINDOWID`, pass the window explicitly with `--x11-window-id`.
|
||||||
|
|
||||||
|
X11 terminal mode uses the normal keyboard layout files, including desktop and laptop bindings. It supports Fenrir keys such as numpad Insert/`KEY_KP0`, CapsLock/`KEY_CAPSLOCK`, Insert/`KEY_INSERT`, and Super/Windows/`KEY_META`. Script keys such as Compose use `KEY_COMPOSE`.
|
||||||
|
|
||||||
|
This mode requires `python-xlib`.
|
||||||
|
|
||||||
|
For users who want a dedicated PTY/terminal screen reader instead of Fenrir's Linux console focus, see TDSR: https://github.com/tspivey/tdsr
|
||||||
|
|
||||||
## Localization
|
## Localization
|
||||||
Translation files are located in the `locale/` directory. To install translations:
|
Translation files are located in the `locale/` directory. To install translations:
|
||||||
|
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ ls -la config/punctuation/default.conf
|
|||||||
# Test basic functionality (ask user to run)
|
# Test basic functionality (ask user to run)
|
||||||
sudo ./src/fenrir --help
|
sudo ./src/fenrir --help
|
||||||
|
|
||||||
# Test in emulation mode (safer for desktop environments)
|
# Test version output
|
||||||
sudo ./src/fenrir -e --version
|
./src/fenrir --version
|
||||||
|
|
||||||
# Quick functionality test (3-5 seconds)
|
# Quick console functionality test (3-5 seconds, ask user to run on a TTY)
|
||||||
sudo timeout 5 ./src/fenrir -e -f || echo "Timeout reached (expected)"
|
sudo timeout 5 ./src/fenrir -f || echo "Timeout reached (expected)"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Expected Result**: No immediate crashes, basic help/version output works
|
**Expected Result**: No immediate crashes, basic help/version output works
|
||||||
|
|||||||
+10
-12
@@ -50,7 +50,13 @@ def check_dependency(dep: Dependency) -> bool:
|
|||||||
dependencyList = [
|
dependencyList = [
|
||||||
# Core dependencies
|
# Core dependencies
|
||||||
Dependency('FenrirCore', 'core', 'core',
|
Dependency('FenrirCore', 'core', 'core',
|
||||||
pythonImports=['daemonize', 'enchant', 'pyperclip', 'setproctitle']),
|
pythonImports=[
|
||||||
|
'daemonize',
|
||||||
|
'enchant',
|
||||||
|
'pyperclip',
|
||||||
|
'setproctitle',
|
||||||
|
'xdg'
|
||||||
|
]),
|
||||||
|
|
||||||
# Screen drivers
|
# Screen drivers
|
||||||
Dependency('DummyScreen', 'screen', 'dummyDriver'),
|
Dependency('DummyScreen', 'screen', 'dummyDriver'),
|
||||||
@@ -58,16 +64,13 @@ dependencyList = [
|
|||||||
pythonImports=['dbus'],
|
pythonImports=['dbus'],
|
||||||
devicePaths=['/dev/vcsa']),
|
devicePaths=['/dev/vcsa']),
|
||||||
Dependency('PTY', 'screen', 'ptyDriver',
|
Dependency('PTY', 'screen', 'ptyDriver',
|
||||||
pythonImports=['pyte', 'xdg']),
|
pythonImports=['pyte']),
|
||||||
|
|
||||||
# Input drivers
|
# Input drivers
|
||||||
Dependency('DummyInput', 'input', 'dummyDriver'),
|
Dependency('DummyInput', 'input', 'dummyDriver'),
|
||||||
Dependency('DebugInput', 'input', 'debugDriver'),
|
Dependency('DebugInput', 'input', 'debugDriver'),
|
||||||
Dependency('Evdev', 'input', 'evdevDriver',
|
Dependency('Evdev', 'input', 'evdevDriver',
|
||||||
pythonImports=['evdev', 'evdev.InputDevice', 'evdev.UInput', 'pyudev']),
|
pythonImports=['evdev', 'evdev.InputDevice', 'evdev.UInput', 'pyudev']),
|
||||||
Dependency('PTYInput', 'input', 'ptyDriver',
|
|
||||||
pythonImports=['pyte']),
|
|
||||||
|
|
||||||
# Sound drivers
|
# Sound drivers
|
||||||
Dependency('DummySound', 'sound', 'dummyDriver'),
|
Dependency('DummySound', 'sound', 'dummyDriver'),
|
||||||
Dependency('DebugSound', 'sound', 'debugDriver'),
|
Dependency('DebugSound', 'sound', 'debugDriver'),
|
||||||
@@ -82,11 +85,7 @@ dependencyList = [
|
|||||||
Dependency('Speechd', 'speech', 'speechdDriver',
|
Dependency('Speechd', 'speech', 'speechdDriver',
|
||||||
pythonImports=['speechd']),
|
pythonImports=['speechd']),
|
||||||
Dependency('GenericSpeech', 'speech', 'genericDriver',
|
Dependency('GenericSpeech', 'speech', 'genericDriver',
|
||||||
checkCommands=['espeak-ng']),
|
checkCommands=['espeak-ng'])
|
||||||
|
|
||||||
# Additional dependencies
|
|
||||||
Dependency('Pexpect', 'core', 'pexpectDriver',
|
|
||||||
pythonImports=['pexpect'])
|
|
||||||
]
|
]
|
||||||
|
|
||||||
defaultModules = {
|
defaultModules = {
|
||||||
@@ -94,8 +93,7 @@ defaultModules = {
|
|||||||
'VCSA',
|
'VCSA',
|
||||||
'Evdev',
|
'Evdev',
|
||||||
'GenericSpeech',
|
'GenericSpeech',
|
||||||
'GenericSound',
|
'GenericSound'
|
||||||
'Pexpect'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_all_dependencies():
|
def check_all_dependencies():
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ This directory contains keyboard layout files for Fenrir screen reader.
|
|||||||
|
|
||||||
- **desktop.conf** - Desktop layout using numeric keypad (recommended)
|
- **desktop.conf** - Desktop layout using numeric keypad (recommended)
|
||||||
- **laptop.conf** - Laptop layout for keyboards without numeric keypad
|
- **laptop.conf** - Laptop layout for keyboards without numeric keypad
|
||||||
- **nvda-desktop.conf** - NVDA-compatible desktop layout
|
|
||||||
- **nvda-laptop.conf** - NVDA-compatible laptop layout
|
|
||||||
- **pty.conf** - PTY emulation layout for terminal use
|
|
||||||
- **pty2.conf** - Alternative PTY emulation layout
|
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
@@ -36,7 +32,7 @@ To change keyboard layout, edit `/etc/fenrir/settings/settings.conf`:
|
|||||||
|
|
||||||
```ini
|
```ini
|
||||||
[keyboard]
|
[keyboard]
|
||||||
keyboardLayout=desktop # or laptop, nvda-desktop, nvda-laptop, pty, pty2
|
keyboardLayout=desktop # or laptop
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Key Constants
|
## Available Key Constants
|
||||||
@@ -582,4 +578,3 @@ BTN_TRIGGER_HAPPY37
|
|||||||
BTN_TRIGGER_HAPPY38
|
BTN_TRIGGER_HAPPY38
|
||||||
BTN_TRIGGER_HAPPY39
|
BTN_TRIGGER_HAPPY39
|
||||||
BTN_TRIGGER_HAPPY40
|
BTN_TRIGGER_HAPPY40
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ KEY_FENRIR,KEY_CTRL,KEY_P=toggle_punctuation_level
|
|||||||
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
||||||
KEY_FENRIR,KEY_BACKSLASH=toggle_output
|
KEY_FENRIR,KEY_BACKSLASH=toggle_output
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
|
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
|
||||||
|
KEY_FENRIR,KEY_CTRL,KEY_D=toggle_diff_mode
|
||||||
|
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_E=cycle_key_echo
|
||||||
key_FENRIR,KEY_KPENTER=toggle_auto_read
|
key_FENRIR,KEY_KPENTER=toggle_auto_read
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
||||||
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
|
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
|
|||||||
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
||||||
KEY_FENRIR,KEY_CTRL,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_SHIFT,KEY_E=toggle_emoticons
|
||||||
|
KEY_FENRIR,KEY_CTRL,KEY_D=toggle_diff_mode
|
||||||
|
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_E=cycle_key_echo
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
||||||
KEY_FENRIR,KEY_Y=toggle_highlight_tracking
|
KEY_FENRIR,KEY_Y=toggle_highlight_tracking
|
||||||
#=toggle_barrier
|
#=toggle_barrier
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# This file contains terminal escape sequences as shortcut
|
|
||||||
# It is used for PTY screen / Input driver (Terminal emulation)
|
|
||||||
# ^[ is used as escape
|
|
||||||
|
|
||||||
# f1 - fenrir help
|
|
||||||
^[OP=toggle_tutorial_mode
|
|
||||||
# double tap control+end read attributes
|
|
||||||
2,^[[1;5F=attribute_cursor
|
|
||||||
#=toggle_has_attribute
|
|
||||||
# escape - stop speech
|
|
||||||
^[=shut_up
|
|
||||||
# context menu key - stop speech
|
|
||||||
^[[29~=shut_up
|
|
||||||
# alt+shift+down - review to bottom
|
|
||||||
^[[1;4B=review_bottom
|
|
||||||
# alt+shift_up - review to top
|
|
||||||
^[[1;4A=review_top
|
|
||||||
# alt+down - review current line
|
|
||||||
^[[1;3B=review_curr_line
|
|
||||||
# alt+left - review previous line
|
|
||||||
^[[1;3D=review_prev_line
|
|
||||||
# alt+right - review next line
|
|
||||||
^[[1;3C=review_next_line
|
|
||||||
# alt+shift+left - beginning of line
|
|
||||||
^[[1;4D=review_line_begin
|
|
||||||
# alt+shift+right - end of line
|
|
||||||
^[[1;4C=review_line_end
|
|
||||||
# control+down - review current word
|
|
||||||
^[[1;5B=review_curr_word
|
|
||||||
# control+left - review previous word
|
|
||||||
^[[1;5D=review_prev_word
|
|
||||||
# control+right - review next word
|
|
||||||
^[[1;5C=review_next_word
|
|
||||||
# shift+down - review current character
|
|
||||||
^[[1;2B=review_curr_char
|
|
||||||
# shift+left - review previous character
|
|
||||||
^[[1;2D=review_prev_char
|
|
||||||
# shift+right - review next character
|
|
||||||
^[[1;2C=review_next_char
|
|
||||||
# control+shift+down - current character phonetic
|
|
||||||
^[[1;6B=curr_char_phonetic
|
|
||||||
# control+shift+left - previous character phonetic
|
|
||||||
^[[1;6D=prev_char_phonetic
|
|
||||||
# control+shift+right - next character phonetic
|
|
||||||
^[[1;6C=next_char_phonetic
|
|
||||||
# f2 - toggle sound
|
|
||||||
^[OQ=toggle_sound
|
|
||||||
# f3 - toggle highlight tracking
|
|
||||||
^[OR=toggle_highlight_tracking
|
|
||||||
alt+f12 - quit fenrir
|
|
||||||
^[[24;3~=quit_fenrir
|
|
||||||
# alt+f12 - time
|
|
||||||
^[[24;3~=time
|
|
||||||
# 2,alt+f12 - date
|
|
||||||
2,^[[24;3~=date
|
|
||||||
# alt+[ - previous clipboard
|
|
||||||
^[[=prev_clipboard
|
|
||||||
# alt+] - next clipboard
|
|
||||||
^[]=next_clipboard
|
|
||||||
# control+f6 - Read current clipboard
|
|
||||||
^[[17;5~=curr_clipboard
|
|
||||||
# f6 - copy to clipboard
|
|
||||||
^[[17~=copy_marked_to_clipboard
|
|
||||||
# shift+f6 - clear clipboard
|
|
||||||
^[[17;2~=clear_clipboard
|
|
||||||
# f7 - paste clipboard
|
|
||||||
^[[18~=paste_clipboard
|
|
||||||
# alt+f8 - export clipboard to X
|
|
||||||
^[[19;3~=export_clipboard_to_x
|
|
||||||
# control+f8 - import clipboard from X
|
|
||||||
^[[19;5~=import_clipboard_from_x
|
|
||||||
# alt+f9 - export clipboard to file
|
|
||||||
^[[20;3~=export_clipboard_to_file
|
|
||||||
# control+f9 - import clipboard from file
|
|
||||||
^[[20;5~=import_clipboard_from_file
|
|
||||||
# shift+f5 - remove marks
|
|
||||||
^[[15;2~=remove_marks
|
|
||||||
# f5 - set mark
|
|
||||||
^[[15~=set_mark
|
|
||||||
# f8 - Last utterance to clipboard
|
|
||||||
^[[19~=copy_last_echo_to_clipboard
|
|
||||||
# lat+\ Toggle auto announcement of indentation
|
|
||||||
^[\=toggle_auto_indent
|
|
||||||
# alt+end - temperarily disable speech
|
|
||||||
^[[1;3F=temp_disable_speech
|
|
||||||
# control+end - toggle auto read
|
|
||||||
^[[1;5F=toggle_auto_read
|
|
||||||
# F12 - cycle keyboard layout
|
|
||||||
^[[24~=cycle_keyboard_layout
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# This file contains terminal escape sequences as shortcut
|
|
||||||
# It is used for PTY screen / Input driver (Terminal emulation)
|
|
||||||
# ^[ is used as escape
|
|
||||||
|
|
||||||
^[h=toggle_tutorial_mode
|
|
||||||
^[/=shut_up
|
|
||||||
^[[D=shut_up
|
|
||||||
^[O=review_bottom
|
|
||||||
^[U=review_top
|
|
||||||
#^[[1;3B=review_curr_line
|
|
||||||
^[i=review_curr_line
|
|
||||||
^[u=review_prev_line
|
|
||||||
^[o=review_next_line
|
|
||||||
^[J=review_line_begin
|
|
||||||
^[L=review_line_end
|
|
||||||
^[j=review_line_first_char
|
|
||||||
^[L=review_line_last_char
|
|
||||||
^[k=review_curr_word
|
|
||||||
^[j=review_prev_word
|
|
||||||
^[l=review_next_word
|
|
||||||
^[,=review_curr_char
|
|
||||||
^[m=review_prev_char
|
|
||||||
^[.=review_next_char
|
|
||||||
^[<=curr_char_phonetic
|
|
||||||
^[M=prev_char_phonetic
|
|
||||||
^[>=next_char_phonetic
|
|
||||||
^[OR=toggle_sound
|
|
||||||
^[OS=toggle_speech
|
|
||||||
^[8=toggle_highlight_tracking
|
|
||||||
^[Q=quit_fenrir
|
|
||||||
^[t=time
|
|
||||||
^[T=date
|
|
||||||
^[[5~=prev_clipboard
|
|
||||||
^[[6~=next_clipboard
|
|
||||||
^[C=curr_clipboard
|
|
||||||
^[c=copy_marked_to_clipboard
|
|
||||||
^[v=paste_clipboard
|
|
||||||
^[[15~=import_clipboard_from_file
|
|
||||||
^[X=remove_marks
|
|
||||||
^[x=set_mark
|
|
||||||
^[\=toggle_auto_indent
|
|
||||||
^[B=copy_last_echo_to_clipboard
|
|
||||||
# alt+f8 - export clipboard to X
|
|
||||||
# ^[[19;3~=export_clipboard_to_x
|
|
||||||
# # control+f8 - import clipboard from X
|
|
||||||
# ^[[19;5~=import_clipboard_from_x
|
|
||||||
+178
-106
@@ -3,14 +3,13 @@
|
|||||||
enabled=True
|
enabled=True
|
||||||
|
|
||||||
# Select the driver used to play sounds, choices are genericDriver and gstreamerDriver.
|
# Select the driver used to play sounds, choices are genericDriver and gstreamerDriver.
|
||||||
|
# Generic driver uses fewer dependencies but spawns a process for each sound played including progress bar beeps
|
||||||
# Gstreamer is the default.
|
# Gstreamer is the default.
|
||||||
driver=gstreamerDriver
|
driver=gstreamerDriver
|
||||||
#driver=genericDriver
|
#driver=genericDriver
|
||||||
|
|
||||||
# Sound themes. These are the pack of sounds used for sound alerts.
|
# Sound themes. These are the pack of sounds used for sound alerts.
|
||||||
# Sound packs may be located at /usr/share/sounds
|
# Sound packs may be located at /usr/share/sounds
|
||||||
# For system wide availability, or ~/.local/share/fenrirscreenreader/sounds
|
|
||||||
# For the current user.
|
|
||||||
theme=default
|
theme=default
|
||||||
|
|
||||||
# Sound volume controls how loud the sounds for your selected soundpack are.
|
# Sound volume controls how loud the sounds for your selected soundpack are.
|
||||||
@@ -24,12 +23,12 @@ volume=0.7
|
|||||||
# fenrirFrequence = the frequency to play
|
# fenrirFrequence = the frequency to play
|
||||||
# fenrirDuration = the duration of the frequency
|
# fenrirDuration = the duration of the frequency
|
||||||
# the following command is used to play a soundfile
|
# the following command is used to play a soundfile
|
||||||
genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
|
generic_play_file_command=play -q -v fenrirVolume fenrirSoundFile
|
||||||
#the following command is used to generate a frequency beep
|
#the following command is used to generate a frequency beep
|
||||||
genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
|
generic_frequency_command=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
|
||||||
|
|
||||||
# Enable progress bar monitoring with ascending tones by default
|
# Enable progress bar monitoring with ascending tones by default
|
||||||
progressMonitoring=True
|
progress_monitoring=True
|
||||||
|
|
||||||
[speech]
|
[speech]
|
||||||
# Turn speech on or off:
|
# Turn speech on or off:
|
||||||
@@ -45,7 +44,13 @@ rate=0.5
|
|||||||
# Pitch controls the pitch of the voice, select from 0, lowest, to 1.0, highest.
|
# Pitch controls the pitch of the voice, select from 0, lowest, to 1.0, highest.
|
||||||
pitch=0.5
|
pitch=0.5
|
||||||
# Pitch for capital letters
|
# Pitch for capital letters
|
||||||
capitalPitch=0.9
|
capital_pitch=0.9
|
||||||
|
# How to indicate capital letters:
|
||||||
|
# pitch = change speech pitch (uses capital_pitch value)
|
||||||
|
# beep = play Caps.wav sound icon
|
||||||
|
# both = play beep AND change pitch
|
||||||
|
# none = no special indication
|
||||||
|
capital_indicator=pitch
|
||||||
|
|
||||||
# Volume controls the loudness of the voice, select from 0, quietest, to 1.0, loudest.
|
# Volume controls the loudness of the voice, select from 0, quietest, to 1.0, loudest.
|
||||||
volume=1.0
|
volume=1.0
|
||||||
@@ -65,10 +70,26 @@ volume=1.0
|
|||||||
#language=en
|
#language=en
|
||||||
|
|
||||||
# Read new text as it happens?
|
# Read new text as it happens?
|
||||||
autoReadIncoming=True
|
auto_read_incoming=True
|
||||||
|
|
||||||
# Speak individual numbers instead of whole string.
|
# Speak individual numbers instead of whole string.
|
||||||
readNumbersAsDigits = False
|
read_numbers_as_digits = False
|
||||||
|
|
||||||
|
# Flood control: batch rapid updates instead of speaking each one
|
||||||
|
# Number of updates within rapid_update_window to trigger batching
|
||||||
|
rapid_update_threshold=5
|
||||||
|
|
||||||
|
# Time window (seconds) for detecting rapid updates
|
||||||
|
rapid_update_window=0.3
|
||||||
|
|
||||||
|
# How often to speak batched content (seconds)
|
||||||
|
batch_flush_interval=0.5
|
||||||
|
|
||||||
|
# Maximum lines to keep when batching (keeps newest, drops oldest)
|
||||||
|
max_batch_lines=100
|
||||||
|
|
||||||
|
# Only enable flood control if this many new lines appear in the window
|
||||||
|
flood_line_threshold=500
|
||||||
|
|
||||||
# genericSpeechCommand is the command that is executed for talking
|
# genericSpeechCommand is the command that is executed for talking
|
||||||
# the following variables are replaced with values
|
# the following variables are replaced with values
|
||||||
@@ -81,106 +102,149 @@ readNumbersAsDigits = False
|
|||||||
# fenrirVolume = is replaced with the current volume
|
# fenrirVolume = is replaced with the current volume
|
||||||
# fenrirPitch = is replaced with the current pitch
|
# fenrirPitch = is replaced with the current pitch
|
||||||
# fenrirRate = is replaced with the current speed (speech rate)
|
# fenrirRate = is replaced with the current speed (speech rate)
|
||||||
genericSpeechCommand=espeak-ng -a fenrirVolume -s fenrirRate -p fenrirPitch -v fenrirVoice -- "fenrirText"
|
generic_speech_command=espeak-ng -a fenrir_volume -s fenrir_rate -p fenrir_pitch -v fenrir_voice -- "fenrir_text"
|
||||||
|
|
||||||
# min and max values of the TTS system that is used in genericSpeechCommand
|
# min and max values of the TTS system that is used in generic_speech_command
|
||||||
fenrirMinVolume=0
|
fenrir_min_volume=0
|
||||||
fenrirMaxVolume=200
|
fenrir_max_volume=200
|
||||||
fenrirMinPitch=0
|
fenrir_min_pitch=0
|
||||||
fenrirMaxPitch=99
|
fenrir_max_pitch=99
|
||||||
fenrirMinRate=80
|
fenrir_min_rate=80
|
||||||
fenrirMaxRate=450
|
fenrir_max_rate=450
|
||||||
|
|
||||||
[screen]
|
[screen]
|
||||||
|
# Screen driver: vcsaDriver (Linux TTY/virtual console), ptyDriver (terminal emulation)
|
||||||
driver=vcsaDriver
|
driver=vcsaDriver
|
||||||
|
# Text encoding: auto (auto-detect), utf-8, latin1, etc.
|
||||||
encoding=auto
|
encoding=auto
|
||||||
screenUpdateDelay=0.05
|
# Delay in seconds between screen updates (lower = more responsive, higher = less CPU)
|
||||||
ignoreScreen=7
|
# Recommended: 0.05 for most systems
|
||||||
autodetectIgnoreScreen=True
|
screen_update_delay=0.05
|
||||||
|
# TTY number to completely ignore (e.g., 7 for X11/graphical login screen)
|
||||||
|
ignore_screen=7
|
||||||
|
# Automatically detect and ignore graphical TTYs (X11, Wayland sessions)
|
||||||
|
autodetect_ignore_screen=True
|
||||||
|
|
||||||
[keyboard]
|
[keyboard]
|
||||||
driver=evdevDriver
|
driver=evdevDriver
|
||||||
# filter input devices NOMICE, ALL or a DEVICE NAME
|
# filter input devices NOMICE, ALL or a DEVICE NAME
|
||||||
device=ALL
|
device=ALL
|
||||||
# gives Fenrir exclusive access to the keyboard and lets it control keystrokes.
|
# gives Fenrir exclusive access to the keyboard and lets it control keystrokes.
|
||||||
grabDevices=True
|
grab_devices=True
|
||||||
ignoreShortcuts=False
|
# Optional X11 target window id for x11Driver. Empty uses WINDOWID or active window.
|
||||||
|
x11_window_id=
|
||||||
|
# Ignore shortcut bindings and pass all keys through without processing Fenrir commands.
|
||||||
|
# When True, Fenrir will only monitor screen content without intercepting keyboard input.
|
||||||
# the current shortcut layout located in /etc/fenrirscreenreader/keyboard
|
# the current shortcut layout located in /etc/fenrirscreenreader/keyboard
|
||||||
keyboardLayout=desktop
|
ignore_shortcuts=False
|
||||||
|
keyboard_layout=desktop
|
||||||
# echo chars while typing.
|
# echo chars while typing.
|
||||||
# 0 = None
|
# 0 = None
|
||||||
# 1 = always
|
# 1 = always
|
||||||
# 2 = only while capslock (not compatible with capslock as fenrir key)
|
# 2 = only while capslock (not compatible with capslock as fenrir key)
|
||||||
charEchoMode=1
|
char_echo_mode=1
|
||||||
# echo deleted chars
|
# echo deleted chars
|
||||||
charDeleteEcho=True
|
char_delete_echo=True
|
||||||
# echo word after pressing space
|
# echo word after pressing space
|
||||||
wordEcho=False
|
word_echo=False
|
||||||
# interrupt speech on any keypress
|
# interrupt speech on any keypress
|
||||||
interruptOnKeyPress=True
|
interrupt_on_key_press=True
|
||||||
# you can filter the keys on that the speech should interrupt (empty = all keys, otherwhise the given keys)
|
# you can filter the keys on that the speech should interrupt (empty = all keys, otherwhise the given keys)
|
||||||
interruptOnKeyPressFilter=
|
interrupt_on_key_press_filter=
|
||||||
# timeout for double tap in sec
|
# timeout for double tap in sec
|
||||||
doubleTapTimeout=0.2
|
double_tap_timeout=0.2
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
# Debug levels: 0=DEACTIVE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
|
# Debug levels: 0=NONE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
|
||||||
# For production use, WARNING (2) provides good balance of useful info without spam
|
# For production use, WARNING (2) provides good balance of useful info without spam
|
||||||
debugLevel=2
|
# The default is 0, no logging.
|
||||||
|
debug_level=0
|
||||||
# debugMode sets where the debug output should send to:
|
# debugMode sets where the debug output should send to:
|
||||||
# debugMode=File writes to debugFile (Default:/tmp/fenrir-PID.log)
|
# debugMode=File writes to debug_file (Default:/tmp/fenrir-PID.log)
|
||||||
# debugMode=Print just prints on the screen
|
# debugMode=Print just prints on the screen
|
||||||
debugMode=File
|
debug_mode=File
|
||||||
debugFile=
|
debug_file=
|
||||||
punctuationProfile=default
|
# Punctuation settings control how punctuation is spoken during text review.
|
||||||
punctuationLevel=some
|
# Profile selects a punctuation definition file from config/punctuation/ (e.g., default.conf)
|
||||||
respectPunctuationPause=True
|
punctuation_profile=default
|
||||||
|
punctuation_level=some
|
||||||
|
respect_punctuation_pause=True
|
||||||
# Replace undefined punctuation with spaces instead of removing them
|
# Replace undefined punctuation with spaces instead of removing them
|
||||||
# This improves readability of text with punctuation like [X]mute, IP addresses, etc.
|
# This improves readability of text with punctuation like [X]mute, IP addresses, etc.
|
||||||
replaceUndefinedPunctuationWithSpace=True
|
replace_undefined_punctuation_with_space=True
|
||||||
newLinePause=True
|
# Pause speech briefly at newline characters for better readability
|
||||||
numberOfClipboards=50
|
new_line_pause=True
|
||||||
|
number_of_clipboards=50
|
||||||
# used path for "export_clipboard_to_file"
|
# used path for "export_clipboard_to_file"
|
||||||
# $user is replaced by username
|
# $user is replaced by username
|
||||||
#clipboardExportPath=/home/$user/fenrirClipboard
|
#clipboardExportPath=/home/$user/fenrirClipboard
|
||||||
clipboardExportPath=/tmp/fenrirClipboard
|
clipboard_export_path=/tmp/fenrirClipboard
|
||||||
|
# Convert text emoticons like :) to descriptive text (e.g., "smiling face")
|
||||||
emoticons=True
|
emoticons=True
|
||||||
# define the current Fenrir key
|
# Define the Fenrir modifier key(s) - used to trigger Fenrir commands
|
||||||
fenrirKeys=KEY_KP0,KEY_META,KEY_INSERT
|
# Examples: KEY_KP0 (numpad 0), KEY_META (Super/Windows), KEY_INSERT
|
||||||
scriptKeys=KEY_COMPOSE
|
# Multiple keys: KEY_KP0,KEY_META,KEY_INSERT
|
||||||
timeFormat=%%I:%%M%%P
|
fenrir_keys=KEY_KP0,KEY_META,KEY_INSERT
|
||||||
dateFormat=%%A, %%B %%d, %%Y
|
# Script key - used to execute custom scripts in script_path
|
||||||
autoSpellCheck=True
|
script_keys=KEY_COMPOSE
|
||||||
spellCheckLanguage=en_US
|
# Time format using Python strftime codes (https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior)
|
||||||
# path for your scripts "scriptKeys" functionality
|
# Default: %%I:%%M%%P (12-hour format, e.g., "06:38pm")
|
||||||
scriptPath=/usr/share/fenrirscreenreader/scripts
|
# 24-hour format: %%H:%%M (e.g., "18:38")
|
||||||
# overload commands, and create new one without changing Fenrir default
|
# Common codes: %%I=12hr hour, %%H=24hr hour, %%M=minute, %%S=second, %%P=am/pm lowercase, %%p=AM/PM uppercase
|
||||||
commandPath=
|
time_format=%%I:%%M%%P
|
||||||
#fenrirBGColor = the backgroundcolor
|
# Date format using Python strftime codes
|
||||||
#fenrirFGColor = the foregroundcolor
|
# Default: %%A, %%B %%d, %%Y (e.g., "Tuesday, December 10, 2024")
|
||||||
#fenrirUnderline = speak the underline attribute
|
# ISO format: %%Y-%%m-%%d (e.g., "2024-12-10")
|
||||||
#fenrirBold = speak the bold attribute
|
# Common codes: %%A=weekday name, %%B=month name, %%d=day, %%Y=year with century, %%m=month number
|
||||||
#fenrirBlink = speak the blink attribute
|
date_format=%%A, %%B %%d, %%Y
|
||||||
#fenrirFont = the font
|
# Automatically spell check words when reviewing character-by-character
|
||||||
#fenrirFontSize = the fontsize
|
auto_spell_check=True
|
||||||
attributeFormatString=Background fenrirBGColor,Foreground fenrirFGColor,fenrirUnderline,fenrirBold,fenrirBlink, Font fenrirFont,Fontsize fenrirFontSize
|
# Language for spell checking (format: language_COUNTRY, e.g., en_US, en_GB, es_ES)
|
||||||
# present indentation
|
spell_check_language=en_US
|
||||||
autoPresentIndent=False
|
# path for your scripts "script_keys" functionality
|
||||||
# speak is only invoked on changeing ident level, sound always
|
script_path=/usr/share/fenrirscreenreader/scripts
|
||||||
# 0 = sound and speak
|
# Override default commands or add custom commands without modifying Fenrir installation
|
||||||
# 1 = sound only
|
# Leave empty to use default commands only
|
||||||
# 2 = speak only
|
command_path=
|
||||||
autoPresentIndentMode=1
|
# Format string for announcing text attributes (colors, formatting)
|
||||||
# play a sound when attributes change
|
# Available variables:
|
||||||
hasAttributes=True
|
# fenrirBGColor = the background color
|
||||||
# shell for PTY emulatiun (empty = default shell)
|
# fenrirFGColor = the foreground color
|
||||||
|
# fenrirUnderline = speak the underline attribute
|
||||||
|
# fenrirBold = speak the bold attribute
|
||||||
|
# fenrirBlink = speak the blink attribute
|
||||||
|
# fenrirFont = the font name
|
||||||
|
# fenrirFontSize = the font size
|
||||||
|
attribute_format_string=Background fenrirBGColor,Foreground fenrirFGColor,fenrirUnderline,fenrirBold,fenrirBlink, Font fenrirFont,Fontsize fenrirFontSize
|
||||||
|
# Automatically announce indentation level changes (useful for Python, YAML, etc.)
|
||||||
|
auto_present_indent=False
|
||||||
|
# How to present indentation changes:
|
||||||
|
# 0 = sound and speak (both audio feedback and voice announcement)
|
||||||
|
# 1 = sound only (just play a tone)
|
||||||
|
# 2 = speak only (just voice announcement)
|
||||||
|
auto_present_indent_mode=1
|
||||||
|
# Play a sound when text attributes (color, bold, etc.) change
|
||||||
|
has_attributes=True
|
||||||
|
# Shell to use for PTY emulation mode (empty = use system default shell)
|
||||||
|
# Examples: /bin/bash, /bin/zsh, /usr/bin/fish
|
||||||
shell=
|
shell=
|
||||||
|
# Diff review presentation mode:
|
||||||
|
# speech = spoken feedback only
|
||||||
|
# sound = sound cues only (falls back to speech when cue is unavailable)
|
||||||
|
# both = speech and sound cues
|
||||||
|
diff_presentation=both
|
||||||
|
# Diff review verbosity:
|
||||||
|
# compact = concise role/location feedback
|
||||||
|
# verbose = include diff line content during navigation
|
||||||
|
diff_verbosity=compact
|
||||||
|
|
||||||
[focus]
|
[focus]
|
||||||
#follow the text cursor
|
# Follow and announce text cursor position changes
|
||||||
cursor=True
|
cursor=True
|
||||||
#follow highlighted text changes
|
# Follow and announce highlighted/selected text changes (useful in menus)
|
||||||
highlight=False
|
highlight=False
|
||||||
|
# Suppress generic incoming announcements for full-screen TUIs that redraw often
|
||||||
|
tui=False
|
||||||
|
|
||||||
[remote]
|
[remote]
|
||||||
enable=True
|
enable=True
|
||||||
@@ -191,40 +255,43 @@ driver=unixDriver
|
|||||||
# tcp port
|
# tcp port
|
||||||
port=22447
|
port=22447
|
||||||
# socket filepath
|
# socket filepath
|
||||||
socketFile=
|
socket_file=
|
||||||
# allow settings to overwrite
|
# allow settings to overwrite
|
||||||
enableSettingsRemote=True
|
enable_settings_remote=True
|
||||||
# allow commands to be executed
|
# allow commands to be executed
|
||||||
enableCommandRemote=True
|
enable_command_remote=True
|
||||||
|
|
||||||
[barrier]
|
[barrier]
|
||||||
|
# Enable barrier detection - automatically detects table/box borders for improved navigation
|
||||||
enabled=False
|
enabled=False
|
||||||
leftBarriers=│└┌─
|
# Characters that represent left-side barriers/borders (for table/box detection)
|
||||||
rightBarriers=│┘┐─
|
left_barriers=│└┌─
|
||||||
|
# Characters that represent right-side barriers/borders (for table/box detection)
|
||||||
|
right_barriers=│┘┐─
|
||||||
|
|
||||||
[review]
|
[review]
|
||||||
lineBreak=True
|
# Announce line breaks during review mode
|
||||||
endOfScreen=True
|
line_break=True
|
||||||
# leave the review when pressing a key
|
# Announce when reaching the end of screen during review
|
||||||
leaveReviewOnCursorChange=True
|
end_of_screen=True
|
||||||
# leave the review when changing the screen
|
# Exit review mode when cursor position changes (e.g., when typing)
|
||||||
leaveReviewOnScreenChange=True
|
leave_review_on_cursor_change=True
|
||||||
|
# Exit review mode when switching to a different TTY/screen
|
||||||
[promote]
|
leave_review_on_screen_change=True
|
||||||
enabled=True
|
|
||||||
inactiveTimeoutSec=120
|
|
||||||
list=
|
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
vmenuPath=
|
# Custom path for VMenu (virtual menu) profiles
|
||||||
# quickMenu: Semicolon-separated list of settings for quick adjustment
|
# Leave empty to use default location (/etc/fenrirscreenreader/vmenu-profiles/)
|
||||||
|
vmenu_path=
|
||||||
|
# Quick menu: Semicolon-separated list of settings for rapid adjustment via VMenu
|
||||||
|
# Access with Fenrir+F10, navigate to "Quick Settings"
|
||||||
# Format: section#setting;section#setting;...
|
# Format: section#setting;section#setting;...
|
||||||
# Supported settings:
|
# Supported settings:
|
||||||
# - speech#rate, speech#pitch, speech#volume (0.0-1.0)
|
# - speech#rate, speech#pitch, speech#volume (0.0-1.0)
|
||||||
# - speech#module, speech#voice (speechdDriver only, auto-added)
|
# - speech#module, speech#voice (speechdDriver only, auto-added)
|
||||||
# Note: speech#module and speech#voice are automatically added when
|
# Note: speech#module and speech#voice are automatically added when
|
||||||
# speechdDriver is active. Do not add them manually.
|
# speechdDriver is active. Do not add them manually.
|
||||||
quickMenu=speech#rate;speech#pitch;speech#volume
|
quick_menu=speech#rate;speech#pitch;speech#volume
|
||||||
|
|
||||||
[prompt]
|
[prompt]
|
||||||
# Custom prompt patterns for silence until prompt feature
|
# Custom prompt patterns for silence until prompt feature
|
||||||
@@ -242,27 +309,32 @@ quickMenu=speech#rate;speech#pitch;speech#volume
|
|||||||
# For "[user@hostname ~] $" 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 prompts ending with specific strings, use patterns like: .*your_prompt_ending$
|
||||||
# For custom package manager prompts: .*your_package_manager.*\[[YyNn]/[YyNn]\].*
|
# For custom package manager prompts: .*your_package_manager.*\[[YyNn]/[YyNn]\].*
|
||||||
customPatterns=
|
custom_patterns=
|
||||||
|
|
||||||
# Specific prompt strings to match exactly (useful for very specific custom prompts)
|
# Specific prompt strings to match exactly (useful for very specific custom prompts)
|
||||||
# Format: exactMatches=prompt1,prompt2,prompt3
|
# Format: exact_matches=prompt1,prompt2,prompt3
|
||||||
# Examples:
|
# Examples:
|
||||||
# exactMatches=[storm@fenrir ~] $,[root@fenrir ~] #,Continue installation? [Y/n]
|
# exact_matches=[storm@fenrir ~] $,[root@fenrir ~] #,Continue installation? [Y/n]
|
||||||
exactMatches=
|
exact_matches=
|
||||||
|
|
||||||
[time]
|
[time]
|
||||||
# automatic time announcement
|
# Enable automatic time announcements
|
||||||
enabled=False
|
enabled=False
|
||||||
# present time
|
# Announce the current time
|
||||||
presentTime=True
|
present_time=True
|
||||||
# present date (on change)
|
# Announce the date (only when it changes, e.g., at midnight)
|
||||||
presentDate=True
|
present_date=True
|
||||||
# present time after a given period of seconds
|
# Announce time every N seconds (0 = disabled)
|
||||||
delaySec=0
|
# If delay_sec > 0, on_minutes is ignored
|
||||||
# present time after to given minutes example every 15 minutes: 00,15,30,45
|
delay_sec=0
|
||||||
# if delaySec is >0 onMinutes is ignored
|
# Announce time at specific minutes of each hour (comma-separated)
|
||||||
onMinutes=00,30
|
# Examples:
|
||||||
# announce via soundicon (not interrupting)
|
# 00 = Only on the hour (1:00, 2:00, 3:00, etc.)
|
||||||
|
# 00,30 = On the hour and half-hour (1:00, 1:30, 2:00, 2:30, etc.)
|
||||||
|
# 00,15,30,45 = Every 15 minutes
|
||||||
|
# Note: This is ignored if delay_sec > 0
|
||||||
|
on_minutes=00,30
|
||||||
|
# Play a sound icon before time announcement (non-interrupting)
|
||||||
announce=True
|
announce=True
|
||||||
# interrupt current speech for time announcement
|
# Interrupt current speech to announce time immediately
|
||||||
interrupt=False
|
interrupt=False
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,6 @@
|
|||||||
# Screen Reader Turned On or Off
|
# Screen Reader Turned On or Off
|
||||||
ScreenReaderOn='ScreenReaderOn.wav'
|
ScreenReaderOn='ScreenReaderOn.wav'
|
||||||
ScreenReaderOff='ScreenReaderOff.wav'
|
ScreenReaderOff='ScreenReaderOff.wav'
|
||||||
# PTY bypass
|
|
||||||
PTYBypass='PTYBypass.wav'
|
|
||||||
# Cancel the current command
|
# Cancel the current command
|
||||||
Cancel='Cancel.wav'
|
Cancel='Cancel.wav'
|
||||||
# Accept command
|
# Accept command
|
||||||
@@ -46,8 +44,6 @@ ErrorSpeech='ErrorSpeech.wav'
|
|||||||
ErrorScreen='ErrorScreen.wav'
|
ErrorScreen='ErrorScreen.wav'
|
||||||
# If you cursor over an text that has attributs (like color)
|
# If you cursor over an text that has attributs (like color)
|
||||||
HasAttributes='has_attribute.wav'
|
HasAttributes='has_attribute.wav'
|
||||||
# fenrir can promote strings if they appear on the screen.
|
|
||||||
PromotedText='PromotedText.wav'
|
|
||||||
# missspelled indicator
|
# missspelled indicator
|
||||||
mispell='mispell.wav'
|
mispell='mispell.wav'
|
||||||
# the for capital letter
|
# the for capital letter
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# Screen Reader Turned On or Off
|
# Screen Reader Turned On or Off
|
||||||
ScreenReaderOn=''
|
ScreenReaderOn=''
|
||||||
ScreenReaderOff=''
|
ScreenReaderOff=''
|
||||||
# PTY bypass
|
|
||||||
PTYBypass=''
|
|
||||||
# Cancel the current command
|
# Cancel the current command
|
||||||
Cancel=''
|
Cancel=''
|
||||||
# Accept command
|
# Accept command
|
||||||
@@ -50,8 +48,6 @@ ErrorBraille=''
|
|||||||
ErrorScreen=''
|
ErrorScreen=''
|
||||||
# If you cursor over an text that has attributs (like color)
|
# If you cursor over an text that has attributs (like color)
|
||||||
HasAttributes=''
|
HasAttributes=''
|
||||||
# fenrir can promote strings if they appear on the screen.
|
|
||||||
PromotedText=''
|
|
||||||
# misspelled indicator
|
# misspelled indicator
|
||||||
mispell=''
|
mispell=''
|
||||||
# the for capital letter:
|
# the for capital letter:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ src/fenrirscreenreader/
|
|||||||
│ ├── onKeyInput/ # Key input hooks
|
│ ├── onKeyInput/ # Key input hooks
|
||||||
│ └── help/ # Tutorial system
|
│ └── help/ # Tutorial system
|
||||||
├── drivers/ # Driver implementations
|
├── drivers/ # Driver implementations
|
||||||
│ ├── inputDriver/ # Input drivers (evdev, pty, atspi)
|
│ ├── inputDriver/ # Input drivers (evdev, x11)
|
||||||
│ ├── screenDriver/ # Screen drivers (vcsa, pty)
|
│ ├── screenDriver/ # Screen drivers (vcsa, pty)
|
||||||
│ ├── speechDriver/ # Speech drivers (speechd, generic)
|
│ ├── speechDriver/ # Speech drivers (speechd, generic)
|
||||||
│ └── soundDriver/ # Sound drivers (generic, gstreamer)
|
│ └── soundDriver/ # Sound drivers (generic, gstreamer)
|
||||||
@@ -36,8 +36,7 @@ Fenrir uses a pluggable driver architecture:
|
|||||||
|
|
||||||
1. **Input Drivers**: Capture keyboard input
|
1. **Input Drivers**: Capture keyboard input
|
||||||
- evdevDriver: Linux evdev (recommended)
|
- evdevDriver: Linux evdev (recommended)
|
||||||
- ptyDriver: Terminal emulation
|
- x11Driver: X11 terminal-scoped input
|
||||||
- atspiDriver: AT-SPI for desktop
|
|
||||||
|
|
||||||
2. **Screen Drivers**: Read screen content
|
2. **Screen Drivers**: Read screen content
|
||||||
- vcsaDriver: Linux VCSA devices
|
- vcsaDriver: Linux VCSA devices
|
||||||
@@ -83,7 +82,6 @@ Fenrir supports various event hooks:
|
|||||||
- **onCursorChange**: Triggered when cursor moves
|
- **onCursorChange**: Triggered when cursor moves
|
||||||
- **onScreenUpdate**: Triggered on screen content changes
|
- **onScreenUpdate**: Triggered on screen content changes
|
||||||
- **onKeyInput**: Triggered on key presses
|
- **onKeyInput**: Triggered on key presses
|
||||||
- **onByteInput**: Triggered on byte-level input
|
|
||||||
- **onScreenChanged**: Triggered when switching screens
|
- **onScreenChanged**: Triggered when switching screens
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Diff Review Mode
|
||||||
|
|
||||||
|
Diff review mode provides read-only navigation for unified and classic diff files with speech-first output.
|
||||||
|
|
||||||
|
## Quick Workflow
|
||||||
|
|
||||||
|
1. Copy the full absolute path of a diff file to the Fenrir clipboard.
|
||||||
|
2. Press `Fenrir + Ctrl + D` to toggle diff mode on.
|
||||||
|
3. Review the diff using the keys below.
|
||||||
|
4. Press `Esc` to leave diff mode.
|
||||||
|
|
||||||
|
If the clipboard does not contain a full absolute file path, Fenrir announces an error and does not enter diff mode.
|
||||||
|
|
||||||
|
## Keys (Active Only In Diff Mode)
|
||||||
|
|
||||||
|
- `h` - Next hunk
|
||||||
|
- `Shift + h` - Previous hunk
|
||||||
|
- `f` - Next file section
|
||||||
|
- `Shift + f` - Previous file section
|
||||||
|
- `a` - Next added line
|
||||||
|
- `Shift + a` - Previous added line
|
||||||
|
- `d` - Next removed line
|
||||||
|
- `Shift + d` - Previous removed line
|
||||||
|
- `Up` - Previous line
|
||||||
|
- `Down` - Next line
|
||||||
|
- `Left` - Previous character
|
||||||
|
- `Right` - Next character
|
||||||
|
- `Ctrl + Left` - Previous word
|
||||||
|
- `Ctrl + Right` - Next word
|
||||||
|
- `s` - Diff summary
|
||||||
|
- `F1` - Speak key help
|
||||||
|
- `Esc` - Exit diff mode
|
||||||
|
|
||||||
|
## Speech Behavior
|
||||||
|
|
||||||
|
- Added and removed content is spoken as `Added:` and `Removed:` lines.
|
||||||
|
- Marker-only lines are normalized for speech:
|
||||||
|
- `+++` is spoken as `added`
|
||||||
|
- `---` is spoken as `removed`
|
||||||
|
- Classic diff markers are spoken in plain language:
|
||||||
|
- `17c17` -> `line 17 changed`
|
||||||
|
- `17d16` -> `line 17 deleted`
|
||||||
|
- `16a17` -> `line 17 added`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Diff mode is read-only and does not modify the diff file.
|
||||||
|
- Normal Fenrir key bindings are restored when diff mode exits.
|
||||||
+20
-18
@@ -13,13 +13,14 @@ fenrir \- A modern, modular console screen reader for Linux
|
|||||||
.IR SECTION#SETTING=VALUE;.. ]
|
.IR SECTION#SETTING=VALUE;.. ]
|
||||||
.RB [ \-d ]
|
.RB [ \-d ]
|
||||||
.RB [ \-p ]
|
.RB [ \-p ]
|
||||||
.RB [ \-e ]
|
.RB [ \-x ]
|
||||||
.RB [ \-E ]
|
.RB [ \-\-x11-window-id
|
||||||
|
.IR WINDOWID ]
|
||||||
.RB [ \-F ]
|
.RB [ \-F ]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Fenrir is a modern, modular, flexible and fast console screen reader written in Python 3.
|
Fenrir is a modern, modular, flexible and fast console screen reader written in Python 3.
|
||||||
It provides spoken feedback for Linux console applications and supports multiple interface types including TTY, terminal emulators, and desktop environments.
|
It provides spoken feedback for Linux console applications and supports Linux TTYs plus X11 terminal mode.
|
||||||
|
|
||||||
Fenrir features a modular driver architecture supporting multiple speech synthesizers, sound systems, input methods, and screen reading techniques. It includes advanced features like review mode, multiple clipboards, spell checking, bookmarks, and configurable key bindings.
|
Fenrir features a modular driver architecture supporting multiple speech synthesizers, sound systems, input methods, and screen reading techniques. It includes advanced features like review mode, multiple clipboards, spell checking, bookmarks, and configurable key bindings.
|
||||||
|
|
||||||
@@ -53,14 +54,6 @@ Enable debug mode. Debug information will be logged to /var/log/fenrir.log.
|
|||||||
.BR \-p ", " \-\-print
|
.BR \-p ", " \-\-print
|
||||||
Print debug messages to screen in addition to logging them.
|
Print debug messages to screen in addition to logging them.
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR \-e ", " \-\-emulated-pty
|
|
||||||
Use PTY emulation with escape sequences for input. This enables usage in desktop/X11/Wayland environments and terminal emulators.
|
|
||||||
|
|
||||||
.TP
|
|
||||||
.BR \-E ", " \-\-emulated-evdev
|
|
||||||
Use PTY emulation with evdev for input (single instance mode).
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BR \-F ", " \-\-force-all-screens
|
.BR \-F ", " \-\-force-all-screens
|
||||||
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
|
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
|
||||||
@@ -239,9 +232,7 @@ debugDriver - Debug/testing
|
|||||||
.IP \[bu] 4
|
.IP \[bu] 4
|
||||||
evdevDriver - Linux evdev (recommended for Linux)
|
evdevDriver - Linux evdev (recommended for Linux)
|
||||||
.IP \[bu] 4
|
.IP \[bu] 4
|
||||||
ptyDriver - Terminal emulation (cross-platform)
|
x11Driver - X11 terminal-scoped input for fenrir -x
|
||||||
.IP \[bu] 4
|
|
||||||
atspiDriver - AT-SPI for desktop environments
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Screen Drivers:
|
.B Screen Drivers:
|
||||||
@@ -267,10 +258,6 @@ Start Fenrir as a daemon with default settings.
|
|||||||
.B fenrir -f -d
|
.B fenrir -f -d
|
||||||
Run Fenrir in foreground with debug output.
|
Run Fenrir in foreground with debug output.
|
||||||
|
|
||||||
.TP
|
|
||||||
.B fenrir -e
|
|
||||||
Run Fenrir with PTY emulation for desktop/terminal use.
|
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B fenrir -o "speech#rate=0.8;sound#volume=0.5"
|
.B fenrir -o "speech#rate=0.8;sound#volume=0.5"
|
||||||
Override speech rate and sound volume settings.
|
Override speech rate and sound volume settings.
|
||||||
@@ -300,6 +287,19 @@ enableCommandRemote=True
|
|||||||
|
|
||||||
.SS Using socat with Unix Sockets
|
.SS Using socat with Unix Sockets
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B Instance Discovery:
|
||||||
|
.EX
|
||||||
|
# List registered Fenrir instances and their socket paths
|
||||||
|
echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
.EE
|
||||||
|
|
||||||
|
In X terminal mode (fenrir -x), multiple Fenrir instances can run at the
|
||||||
|
same time. Each instance has a private socket at
|
||||||
|
/tmp/fenrirscreenreader-<pid>.sock, and one instance may also own the
|
||||||
|
standard control socket. Use ls or "command ls" on the standard socket to
|
||||||
|
find the private socket for a specific instance.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Basic Speech Control:
|
.B Basic Speech Control:
|
||||||
.EX
|
.EX
|
||||||
@@ -355,6 +355,8 @@ command say <text> - Speak the specified text
|
|||||||
command interrupt - Stop current speech
|
command interrupt - Stop current speech
|
||||||
.IP \[bu] 2
|
.IP \[bu] 2
|
||||||
command tempdisablespeech - Disable speech until next key press
|
command tempdisablespeech - Disable speech until next key press
|
||||||
|
.IP \[bu] 2
|
||||||
|
ls / list / command ls / command list - List registered Fenrir instances
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B Settings Commands:
|
.B Settings Commands:
|
||||||
|
|||||||
+50
-91
@@ -1095,23 +1095,6 @@ announce=True
|
|||||||
interrupt=False
|
interrupt=False
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Promoted List
|
|
||||||
|
|
||||||
Promoted Lists are a nice feature if you are away from your computer or
|
|
||||||
performing more longer tasks. you can define a list of words which you
|
|
||||||
want to hear a sound icon for after a period of inactivity. Example if
|
|
||||||
the word "Chrys" appears after 120 Seconds of inactivity:
|
|
||||||
|
|
||||||
....
|
|
||||||
[promote]
|
|
||||||
enabled=True
|
|
||||||
inactiveTimeoutSec=120
|
|
||||||
list=Chrys
|
|
||||||
....
|
|
||||||
|
|
||||||
See section link:#Promote[Promote] in `+settings.conf+` for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
=== Dictionary
|
=== Dictionary
|
||||||
|
|
||||||
You can make use of different kinds of built-in dictionary's. A
|
You can make use of different kinds of built-in dictionary's. A
|
||||||
@@ -1231,12 +1214,6 @@ Enable debug mode. Debug information will be logged.
|
|||||||
`+-p, --print+`::
|
`+-p, --print+`::
|
||||||
Print debug messages to screen in addition to logging them.
|
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+`::
|
`+-F, --force-all-screens+`::
|
||||||
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
|
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
|
||||||
|
|
||||||
@@ -1286,14 +1263,27 @@ Enable remote control in settings.conf:
|
|||||||
[remote]
|
[remote]
|
||||||
enable=True
|
enable=True
|
||||||
driver=unixDriver
|
driver=unixDriver
|
||||||
enableSettingsRemote=True
|
enable_settings_remote=True
|
||||||
enableCommandRemote=True
|
enable_command_remote=True
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Using socat with Unix Sockets
|
==== Using socat with Unix Sockets
|
||||||
|
|
||||||
The `+socat+` command provides the easiest way to send commands to Fenrir:
|
The `+socat+` command provides the easiest way to send commands to Fenrir:
|
||||||
|
|
||||||
|
===== Instance Discovery
|
||||||
|
|
||||||
|
....
|
||||||
|
# List registered Fenrir instances and their socket paths
|
||||||
|
echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
....
|
||||||
|
|
||||||
|
In X terminal mode (`+fenrir -x+`), multiple Fenrir instances can run at the
|
||||||
|
same time. Each instance has a private socket at
|
||||||
|
`+/tmp/fenrirscreenreader-<pid>.sock+`, and one instance may also own the
|
||||||
|
standard control socket. Use `+ls+` or `+command ls+` on the standard socket to
|
||||||
|
find the private socket for a specific instance.
|
||||||
|
|
||||||
===== Basic Speech Control
|
===== Basic Speech Control
|
||||||
|
|
||||||
....
|
....
|
||||||
@@ -1317,13 +1307,13 @@ echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenr
|
|||||||
echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Change punctuation level (none/some/most/all)
|
# Change punctuation level (none/some/most/all)
|
||||||
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Voice and TTS control
|
# Voice and TTS control
|
||||||
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Multiple settings at once
|
# 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
|
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Reset all settings to defaults
|
# Reset all settings to defaults
|
||||||
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
@@ -1355,6 +1345,7 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea
|
|||||||
* `+command say <text>+` - Speak the specified text
|
* `+command say <text>+` - Speak the specified text
|
||||||
* `+command interrupt+` - Stop current speech
|
* `+command interrupt+` - Stop current speech
|
||||||
* `+command tempdisablespeech+` - Disable speech until next key press
|
* `+command tempdisablespeech+` - Disable speech until next key press
|
||||||
|
* `+ls+` / `+list+` / `+command ls+` / `+command list+` - List registered Fenrir instances
|
||||||
|
|
||||||
*Clipboard Commands:*
|
*Clipboard Commands:*
|
||||||
|
|
||||||
@@ -1394,8 +1385,8 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea
|
|||||||
* `+speech#module=module_name+` - TTS module (e.g., "espeak-ng")
|
* `+speech#module=module_name+` - TTS module (e.g., "espeak-ng")
|
||||||
|
|
||||||
*General Settings:*
|
*General Settings:*
|
||||||
* `+general#punctuationLevel=none/some/most/all+` - Punctuation verbosity
|
* `+general#punctuation_level=none/some/most/all+` - Punctuation verbosity
|
||||||
* `+general#autoSpellCheck=True/False+` - Automatic spell checking
|
* `+general#auto_spell_check=True/False+` - Automatic spell checking
|
||||||
* `+general#emoticons=True/False+` - Enable emoticon replacement
|
* `+general#emoticons=True/False+` - Enable emoticon replacement
|
||||||
|
|
||||||
*Sound Settings:*
|
*Sound Settings:*
|
||||||
@@ -1407,11 +1398,11 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea
|
|||||||
* `+focus#highlight=True/False+` - Follow text highlighting
|
* `+focus#highlight=True/False+` - Follow text highlighting
|
||||||
|
|
||||||
*Keyboard Settings:*
|
*Keyboard Settings:*
|
||||||
* `+keyboard#charEchoMode=0-2+` - Character echo (0=none, 1=always, 2=capslock only)
|
* `+keyboard#char_echo_mode=0-2+` - Character echo (0=none, 1=always, 2=capslock only)
|
||||||
* `+keyboard#wordEcho=True/False+` - Echo complete words
|
* `+keyboard#word_echo=True/False+` - Echo complete words
|
||||||
|
|
||||||
*Screen Settings:*
|
*Screen Settings:*
|
||||||
* `+screen#ignoreScreen=1,2,3+` - TTY screens to ignore
|
* `+screen#ignore_screen=1,2,3+` - TTY screens to ignore
|
||||||
|
|
||||||
==== settings.conf syntax
|
==== settings.conf syntax
|
||||||
|
|
||||||
@@ -1511,14 +1502,14 @@ frequencies.
|
|||||||
sound file.
|
sound file.
|
||||||
|
|
||||||
....
|
....
|
||||||
genericPlayFileCommand=<your command for playing a file>
|
generic_play_file_command=<your command for playing a file>
|
||||||
....
|
....
|
||||||
|
|
||||||
`+genericFrequencyCommand+` defines the command that is used playing
|
`+genericFrequencyCommand+` defines the command that is used playing
|
||||||
frequencies.
|
frequencies.
|
||||||
|
|
||||||
....
|
....
|
||||||
genericFrequencyCommand=<your command for playing a frequence>
|
generic_frequency_command=<your command for playing a frequence>
|
||||||
....
|
....
|
||||||
|
|
||||||
The following variables are substituted in `+genericPlayFileCommand+`
|
The following variables are substituted in `+genericPlayFileCommand+`
|
||||||
@@ -1532,13 +1523,13 @@ and `+genericFrequencyCommand+`:
|
|||||||
Example genericPlayFileCommand (default)
|
Example genericPlayFileCommand (default)
|
||||||
|
|
||||||
....
|
....
|
||||||
genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
|
generic_play_file_command=play -q -v fenrirVolume fenrirSoundFile
|
||||||
....
|
....
|
||||||
|
|
||||||
Example genericFrequencyCommand (default)
|
Example genericFrequencyCommand (default)
|
||||||
|
|
||||||
....
|
....
|
||||||
genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
|
generic_frequency_command=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Speech
|
==== Speech
|
||||||
@@ -1579,7 +1570,7 @@ Values: Range Minimum:`+0.0+` is lowest, Maximum:`+1.0+` is highest.
|
|||||||
A Pitch for capital letters can be set.
|
A Pitch for capital letters can be set.
|
||||||
|
|
||||||
....
|
....
|
||||||
capitalPitch=0.9
|
capital_pitch=0.9
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: Range Minimum:`+0.0+` is lowest, Maximum:`+1.0+` is highest.
|
Values: Range Minimum:`+0.0+` is lowest, Maximum:`+1.0+` is highest.
|
||||||
@@ -1681,13 +1672,14 @@ the pico module:
|
|||||||
language=de-DE
|
language=de-DE
|
||||||
....
|
....
|
||||||
|
|
||||||
Read new text as it occurs autoReadIncoming=True Values: on=`+True+`,
|
Read new text as it occurs auto_read_incoming=True Values: on=`+True+`,
|
||||||
off=`+False+`
|
off=`+False+`
|
||||||
|
|
||||||
==== Screen
|
==== Screen
|
||||||
|
|
||||||
The settings for screens, (TTY, PTY) are configured in the `+[screen]+`
|
The settings for screen access are configured in the `+[screen]+`
|
||||||
section.
|
section. `+vcsaDriver+` is used for Linux TTYs, and `+ptyDriver+` is
|
||||||
|
used by X11 terminal mode.
|
||||||
|
|
||||||
The driver to get the information from the screen:
|
The driver to get the information from the screen:
|
||||||
|
|
||||||
@@ -1711,7 +1703,7 @@ Values:`+cp850+` is used for Western languages like USA or Europe.
|
|||||||
The driver updates Fenrir with changes on the screen.
|
The driver updates Fenrir with changes on the screen.
|
||||||
|
|
||||||
....
|
....
|
||||||
screenUpdateDelay=0.05
|
screen_update_delay=0.05
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: in Seconds
|
Values: in Seconds
|
||||||
@@ -1776,7 +1768,7 @@ Gives Fenrir exclusive access to the keyboard and lets it control
|
|||||||
keystrokes. This is needed to intercept Fenrir related shortcuts.
|
keystrokes. This is needed to intercept Fenrir related shortcuts.
|
||||||
|
|
||||||
....
|
....
|
||||||
grabDevices=True
|
grab_devices=True
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1785,7 +1777,7 @@ The following makes sense if you are using a second screenreader and
|
|||||||
want to have some hooked events. Fenrir ignores all shortcuts then.
|
want to have some hooked events. Fenrir ignores all shortcuts then.
|
||||||
|
|
||||||
....
|
....
|
||||||
ignoreShortcuts=False
|
ignore_shortcuts=False
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1793,7 +1785,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
The current keyboard layout used for shortcuts.
|
The current keyboard layout used for shortcuts.
|
||||||
|
|
||||||
....
|
....
|
||||||
keyboardLayout=desktop
|
keyboard_layout=desktop
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: An absolute Path to a Keyboard definition file or a Filename
|
Values: An absolute Path to a Keyboard definition file or a Filename
|
||||||
@@ -1810,7 +1802,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
Announce deleted characters
|
Announce deleted characters
|
||||||
|
|
||||||
....
|
....
|
||||||
charDeleteEcho=True
|
char_delete_echo=True
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1818,7 +1810,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
Announce word after pressing space
|
Announce word after pressing space
|
||||||
|
|
||||||
....
|
....
|
||||||
wordEcho=False
|
word_echo=False
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1826,7 +1818,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
Interrupt speech on any keypress
|
Interrupt speech on any keypress
|
||||||
|
|
||||||
....
|
....
|
||||||
interruptOnKeyPress=False
|
interrupt_on_key_press=False
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1834,7 +1826,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
You can filter the keys that speech should interrupt
|
You can filter the keys that speech should interrupt
|
||||||
|
|
||||||
....
|
....
|
||||||
interruptOnKeyPressFilter=
|
interrupt_on_key_press_filter=
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: (List) empty = all keys, otherwise interrupt with specified keys
|
Values: (List) empty = all keys, otherwise interrupt with specified keys
|
||||||
@@ -1842,7 +1834,7 @@ Values: (List) empty = all keys, otherwise interrupt with specified keys
|
|||||||
The timeout that is used for double tap shortcuts
|
The timeout that is used for double tap shortcuts
|
||||||
|
|
||||||
....
|
....
|
||||||
doubleTapTimeout=0.2
|
double_tap_timeout=0.2
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: Seconds
|
Values: Seconds
|
||||||
@@ -1862,7 +1854,7 @@ Values: off=0, error=1, warning=2, info=3
|
|||||||
the current punctuation and dict file in use:
|
the current punctuation and dict file in use:
|
||||||
|
|
||||||
....
|
....
|
||||||
punctuationProfile=default
|
punctuation_profile=default
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: Text, see available profiles in `+/etc/fenrir/punctuation+` or
|
Values: Text, see available profiles in `+/etc/fenrir/punctuation+` or
|
||||||
@@ -1871,7 +1863,7 @@ in `+sourceTree/config/punctuation+`
|
|||||||
The current punctuation level in use:
|
The current punctuation level in use:
|
||||||
|
|
||||||
....
|
....
|
||||||
punctuationLevel=some
|
punctuation_level=some
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: Text, See available levels in the used punctuation file.
|
Values: Text, See available levels in the used punctuation file.
|
||||||
@@ -1879,7 +1871,7 @@ Values: Text, See available levels in the used punctuation file.
|
|||||||
Respect pause for punctuations:
|
Respect pause for punctuations:
|
||||||
|
|
||||||
....
|
....
|
||||||
respectPunctuationPause=True
|
respect_punctuation_pause=True
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1887,7 +1879,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
Add a pause on Line break:
|
Add a pause on Line break:
|
||||||
|
|
||||||
....
|
....
|
||||||
newLinePause=True
|
new_line_pause=True
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1905,7 +1897,7 @@ Values: Text, Systemfilepath
|
|||||||
The number of available clipboards:
|
The number of available clipboards:
|
||||||
|
|
||||||
....
|
....
|
||||||
numberOfClipboards=10
|
number_of_clipboards=10
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: Integer, 1 - 999
|
Values: Integer, 1 - 999
|
||||||
@@ -1921,7 +1913,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
Define the current Fenrir keys:
|
Define the current Fenrir keys:
|
||||||
|
|
||||||
....
|
....
|
||||||
fenrirKeys=KEY_KP0,KEY_META,KEY_INSERT
|
fenrir_keys=KEY_KP0,KEY_META,KEY_INSERT
|
||||||
....
|
....
|
||||||
|
|
||||||
Values, Text list, separated by comma.
|
Values, Text list, separated by comma.
|
||||||
@@ -1955,7 +1947,7 @@ https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior[d
|
|||||||
Enable or Disable spellcheck whilst typing:
|
Enable or Disable spellcheck whilst typing:
|
||||||
|
|
||||||
....
|
....
|
||||||
autoSpellCheck=True
|
auto_spell_check=True
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
@@ -1963,7 +1955,7 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
The use of the dictionary with spellcheck:
|
The use of the dictionary with spellcheck:
|
||||||
|
|
||||||
....
|
....
|
||||||
spellCheckLanguage=en_US
|
spell_check_language=en_US
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: Text, see aspell dictionary's.
|
Values: Text, see aspell dictionary's.
|
||||||
@@ -2044,44 +2036,11 @@ Values: on=`+True+`, off=`+False+`
|
|||||||
Leave the review mode when changing the screen (From TTY3 to TTY4):
|
Leave the review mode when changing the screen (From TTY3 to TTY4):
|
||||||
|
|
||||||
....
|
....
|
||||||
leaveReviewOnScreenChange=True
|
leave_review_on_screen_change=True
|
||||||
....
|
....
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
|
|
||||||
==== Promote
|
|
||||||
|
|
||||||
"Promoted Lists" are configured in the section `+[promote]+`. Turn
|
|
||||||
Promoted Lists" on or off:
|
|
||||||
|
|
||||||
....
|
|
||||||
enabled=True
|
|
||||||
....
|
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
|
||||||
|
|
||||||
The minimum time interval of inactivity to activate promoting. By
|
|
||||||
default it promotes after 120 Seconds inactivity:
|
|
||||||
|
|
||||||
....
|
|
||||||
inactiveTimeoutSec=120
|
|
||||||
....
|
|
||||||
|
|
||||||
Values: in Seconds
|
|
||||||
|
|
||||||
Define a list of promoted words comma seperated:
|
|
||||||
|
|
||||||
....
|
|
||||||
list=
|
|
||||||
....
|
|
||||||
|
|
||||||
Values: text (comma seperated) Example to promote the word "nickname" or
|
|
||||||
a bash prompt:
|
|
||||||
|
|
||||||
....
|
|
||||||
list=nickname,$:,#:
|
|
||||||
....
|
|
||||||
|
|
||||||
==== Time
|
==== Time
|
||||||
|
|
||||||
The automated time announcement is configured in the section `+[time]+`.
|
The automated time announcement is configured in the section `+[time]+`.
|
||||||
|
|||||||
+50
-14
@@ -102,17 +102,17 @@ volume=0.7
|
|||||||
|
|
||||||
[keyboard]
|
[keyboard]
|
||||||
driver=evdevDriver
|
driver=evdevDriver
|
||||||
keyboardLayout=desktop
|
keyboard_layout=desktop
|
||||||
|
|
||||||
[screen]
|
[screen]
|
||||||
driver=vcsaDriver
|
driver=vcsaDriver
|
||||||
ignoreScreen=
|
ignore_screen=
|
||||||
|
|
||||||
[remote]
|
[remote]
|
||||||
enable=True
|
enable=True
|
||||||
driver=unixDriver
|
driver=unixDriver
|
||||||
enableSettingsRemote=True
|
enable_settings_remote=True
|
||||||
enableCommandRemote=True
|
enable_command_remote=True
|
||||||
```
|
```
|
||||||
|
|
||||||
## Remote Control
|
## Remote Control
|
||||||
@@ -126,12 +126,24 @@ Enable remote control in settings:
|
|||||||
enable=True
|
enable=True
|
||||||
driver=unixDriver # or tcpDriver
|
driver=unixDriver # or tcpDriver
|
||||||
port=22447 # for TCP driver
|
port=22447 # for TCP driver
|
||||||
enableSettingsRemote=True # allow settings changes
|
enable_settings_remote=True # allow settings changes
|
||||||
enableCommandRemote=True # allow command execution
|
enable_command_remote=True # allow command execution
|
||||||
```
|
```
|
||||||
|
|
||||||
### Basic Usage with socat
|
### Basic Usage with socat
|
||||||
|
|
||||||
|
#### Instance Discovery
|
||||||
|
```bash
|
||||||
|
# List registered Fenrir instances and their socket paths
|
||||||
|
echo "ls" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
In X terminal mode (`fenrir -x`), multiple Fenrir instances can run at the same
|
||||||
|
time. Each instance has a private socket at `/tmp/fenrirscreenreader-<pid>.sock`,
|
||||||
|
and one instance may also own the standard control socket. Use `ls` or
|
||||||
|
`command ls` on the standard socket to find the private socket for a specific
|
||||||
|
instance.
|
||||||
|
|
||||||
#### Speech Control
|
#### Speech Control
|
||||||
```bash
|
```bash
|
||||||
# Interrupt current speech
|
# Interrupt current speech
|
||||||
@@ -155,14 +167,14 @@ echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreade
|
|||||||
echo "setting set speech#volume=0.9" | 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)
|
# Change punctuation level (none/some/most/all)
|
||||||
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Voice and TTS control
|
# Voice and TTS control
|
||||||
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
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
|
echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Multiple settings at once
|
# 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
|
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Reset all settings
|
# Reset all settings
|
||||||
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
@@ -193,6 +205,7 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea
|
|||||||
- `command say <text>` - Speak text
|
- `command say <text>` - Speak text
|
||||||
- `command interrupt` - Stop speech
|
- `command interrupt` - Stop speech
|
||||||
- `command tempdisablespeech` - Disable until next key
|
- `command tempdisablespeech` - Disable until next key
|
||||||
|
- `ls` / `list` / `command ls` / `command list` - List registered Fenrir instances
|
||||||
|
|
||||||
**Settings Commands:**
|
**Settings Commands:**
|
||||||
- `setting set <section>#<key>=<value>` - Change setting
|
- `setting set <section>#<key>=<value>` - Change setting
|
||||||
@@ -208,11 +221,11 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea
|
|||||||
- `command resetvmenu` - Reset virtual menu
|
- `command resetvmenu` - Reset virtual menu
|
||||||
|
|
||||||
**Key Settings You Can Change:**
|
**Key Settings You Can Change:**
|
||||||
- Punctuation level: `setting set general#punctuationLevel=all`
|
- Punctuation level: `setting set general#punctuation_level=all`
|
||||||
- Speech parameters: `setting set speech#rate=0.8;speech#pitch=0.6`
|
- Speech parameters: `setting set speech#rate=0.8;speech#pitch=0.6`
|
||||||
- Voice selection: `setting set speech#voice=en-us+f3`
|
- Voice selection: `setting set speech#voice=en-us+f3`
|
||||||
- Character echo: `setting set keyboard#charEchoMode=1`
|
- Character echo: `setting set keyboard#char_echo_mode=1`
|
||||||
- Screen ignore: `setting set screen#ignoreScreen=1,2,3`
|
- Screen ignore: `setting set screen#ignore_screen=1,2,3`
|
||||||
|
|
||||||
### Scripting Integration
|
### Scripting Integration
|
||||||
|
|
||||||
@@ -318,7 +331,7 @@ Fenrir automatically detects and provides audio feedback for progress indicators
|
|||||||
|
|
||||||
### Input Drivers
|
### Input Drivers
|
||||||
- **evdevDriver** - Linux evdev (recommended for Linux)
|
- **evdevDriver** - Linux evdev (recommended for Linux)
|
||||||
- **ptyDriver** - Terminal emulation (cross-platform)
|
- **x11Driver** - X11 terminal-scoped input for `fenrir -x`
|
||||||
|
|
||||||
### Screen Drivers
|
### Screen Drivers
|
||||||
- **vcsaDriver** - Linux VCSA devices (TTY)
|
- **vcsaDriver** - Linux VCSA devices (TTY)
|
||||||
@@ -341,11 +354,33 @@ fenrir [OPTIONS]
|
|||||||
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings
|
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings
|
||||||
- `-d, --debug` - Enable debug mode
|
- `-d, --debug` - Enable debug mode
|
||||||
- `-p, --print` - Print debug to screen
|
- `-p, --print` - Print debug to screen
|
||||||
- `-e, --emulated-pty` - PTY emulation for desktop use
|
- `-x, --x11` - PTY + X11 keyboard input scoped to the terminal window
|
||||||
- `-E, --emulated-evdev` - PTY + evdev emulation
|
- `--x11-window-id WINDOWID` - X11 window id to use for `--x11` terminal mode
|
||||||
- `-F, --force-all-screens` - Ignore ignoreScreen setting
|
- `-F, --force-all-screens` - Ignore ignoreScreen setting
|
||||||
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s), can be used multiple times
|
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s), can be used multiple times
|
||||||
|
|
||||||
|
### X11 Terminal Mode
|
||||||
|
|
||||||
|
Use `fenrir -x` to run Fenrir in an X11 terminal as a normal user:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fenrir -x
|
||||||
|
```
|
||||||
|
|
||||||
|
This mode uses PTY screen monitoring and X11 keyboard input. It is intended for GUI terminals such as xterm and VTE-based terminals, while keeping Fenrir key handling scoped to that terminal window. Desktop screen readers keep their global bindings outside the focused terminal.
|
||||||
|
|
||||||
|
Fenrir normally detects the target terminal from `WINDOWID` or the active X11 window. If needed, pass a window id explicitly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fenrir -x --x11-window-id 0x123456
|
||||||
|
```
|
||||||
|
|
||||||
|
X11 terminal mode uses the same keyboard layout files as TTY Fenrir. Supported Fenrir keys include numpad Insert/`KEY_KP0`, CapsLock/`KEY_CAPSLOCK`, Insert/`KEY_INSERT`, and Super/Windows/`KEY_META`. Compose script keys are exposed as `KEY_COMPOSE`.
|
||||||
|
|
||||||
|
This mode requires `python-xlib`.
|
||||||
|
|
||||||
|
For a dedicated PTY/terminal screen reader, see TDSR: https://github.com/tspivey/tdsr
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### No Speech
|
### No Speech
|
||||||
@@ -379,5 +414,6 @@ sudo fenrir -f -d
|
|||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [README.md](../README.md) - Installation and basic setup
|
- [README.md](../README.md) - Installation and basic setup
|
||||||
|
- [diff_review_mode.md](./diff_review_mode.md) - Diff review workflow and key bindings
|
||||||
- [settings.conf](../config/settings/settings.conf) - Configuration reference
|
- [settings.conf](../config/settings/settings.conf) - Configuration reference
|
||||||
- `man fenrir` - Manual page
|
- `man fenrir` - Manual page
|
||||||
+32
-57
@@ -729,15 +729,6 @@ Example on fix minutes in an hour. example every quarter "delaySec=0" and "onMin
|
|||||||
onMinutes=00,15,30,45
|
onMinutes=00,15,30,45
|
||||||
announce=True
|
announce=True
|
||||||
interrupt=False
|
interrupt=False
|
||||||
==== Promoted List ====
|
|
||||||
Promoted Lists are a nice feature if you are away from your computer or performing more longer tasks.
|
|
||||||
you can define a list of words which you want to hear a sound icon for after a period of inactivity.
|
|
||||||
Example if the word "Chrys" appears after 120 Seconds of inactivity:
|
|
||||||
[promote]
|
|
||||||
enabled=True
|
|
||||||
inactiveTimeoutSec=120
|
|
||||||
list=Chrys
|
|
||||||
See section [[#Promote|Promote]] in ''settings.conf'' for more information.
|
|
||||||
==== Punctuation ====
|
==== Punctuation ====
|
||||||
Fenrir handles punctuation levels and names for you with several provided dictionaries.
|
Fenrir handles punctuation levels and names for you with several provided dictionaries.
|
||||||
|
|
||||||
@@ -867,9 +858,9 @@ Values: ''0.0'' is quietest, ''1.0'' is loudest.
|
|||||||
The generic sound driver uses shell commands for play sound and frequencies.
|
The generic sound driver uses shell commands for play sound and frequencies.
|
||||||
|
|
||||||
''genericPlayFileCommand'' defines the command that is used to play a sound file.
|
''genericPlayFileCommand'' defines the command that is used to play a sound file.
|
||||||
genericPlayFileCommand=<your command for playing a file>
|
generic_play_file_command=<your command for playing a file>
|
||||||
''genericFrequencyCommand'' defines the command that is used playing frequencies.
|
''genericFrequencyCommand'' defines the command that is used playing frequencies.
|
||||||
genericFrequencyCommand=<your command for playing a frequence>
|
generic_frequency_command=<your command for playing a frequence>
|
||||||
|
|
||||||
The following variables are substituted in ''genericPlayFileCommand'' and ''genericFrequencyCommand'':
|
The following variables are substituted in ''genericPlayFileCommand'' and ''genericFrequencyCommand'':
|
||||||
* ''fenrirVolume'' = the current volume setting
|
* ''fenrirVolume'' = the current volume setting
|
||||||
@@ -878,9 +869,9 @@ The following variables are substituted in ''genericPlayFileCommand'' and ''gene
|
|||||||
* ''fenrirDuration'' = the duration of the frequency
|
* ''fenrirDuration'' = the duration of the frequency
|
||||||
|
|
||||||
Example genericPlayFileCommand (default)
|
Example genericPlayFileCommand (default)
|
||||||
genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
|
generic_play_file_command=play -q -v fenrirVolume fenrirSoundFile
|
||||||
Example genericFrequencyCommand (default)
|
Example genericFrequencyCommand (default)
|
||||||
genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
|
generic_frequency_command=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
|
||||||
==== Speech ====
|
==== Speech ====
|
||||||
Speech is configured in section ''[speech]''.
|
Speech is configured in section ''[speech]''.
|
||||||
Turn speech on or off:
|
Turn speech on or off:
|
||||||
@@ -910,7 +901,7 @@ Pitch controls the pitch of the voice.
|
|||||||
Values: Range Minimum:''0.0'' is lowest, Maximum:''1.0'' is highest.
|
Values: Range Minimum:''0.0'' is lowest, Maximum:''1.0'' is highest.
|
||||||
|
|
||||||
A Pitch for capital letters can be set.
|
A Pitch for capital letters can be set.
|
||||||
capitalPitch=0.9
|
capital_pitch=0.9
|
||||||
Values: Range Minimum:''0.0'' is lowest, Maximum:''1.0'' is highest.
|
Values: Range Minimum:''0.0'' is lowest, Maximum:''1.0'' is highest.
|
||||||
|
|
||||||
The Volume controls the loudness of the voice.
|
The Volume controls the loudness of the voice.
|
||||||
@@ -931,7 +922,7 @@ Select the language you want Fenrir to use.
|
|||||||
Values: Text, see your TTS synths documentation what is available.
|
Values: Text, see your TTS synths documentation what is available.
|
||||||
|
|
||||||
Read new text as it occurs
|
Read new text as it occurs
|
||||||
autoReadIncoming=True
|
auto_read_incoming=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
=== Generic Driver ===
|
=== Generic Driver ===
|
||||||
@@ -948,16 +939,16 @@ The following variables are substituted in ''genericSpeechCommand'':
|
|||||||
* ''fenrirRate'' = is replaced with the current speed (speech rate)
|
* ''fenrirRate'' = is replaced with the current speed (speech rate)
|
||||||
|
|
||||||
Example genericSpeechCommand (default):
|
Example genericSpeechCommand (default):
|
||||||
genericSpeechCommand=espeak -a fenrirVolume -s fenrirRate -p fenrirPitch -v fenrirVoice "fenrirText"
|
generic_speech_command=espeak -a fenrirVolume -s fenrirRate -p fenrirPitch -v fenrirVoice "fenrirText"
|
||||||
|
|
||||||
These are the minimum and maximum values of the TTS system used in genericSpeechCommand. They are needed to calculate the abstract range in volume, rate and pitch 0.0 - 1.0.
|
These are the minimum and maximum values of the TTS system used in genericSpeechCommand. They are needed to calculate the abstract range in volume, rate and pitch 0.0 - 1.0.
|
||||||
|
|
||||||
FenrirMinVolume=0
|
FenrirMinVolume=0
|
||||||
fenrirMaxVolume=200
|
fenrir_max_volume=200
|
||||||
fenrirMinPitch=0
|
fenrir_min_pitch=0
|
||||||
fenrirMaxPitch=99
|
fenrir_max_pitch=99
|
||||||
fenrirMinRate=80
|
fenrir_min_rate=80
|
||||||
fenrirMaxRate=450
|
fenrir_max_rate=450
|
||||||
|
|
||||||
The current volume, pitch and rate is calculated like this
|
The current volume, pitch and rate is calculated like this
|
||||||
value = min + [volume,pitch,rate] * (min - max )
|
value = min + [volume,pitch,rate] * (min - max )
|
||||||
@@ -1022,7 +1013,8 @@ Values: Integer,
|
|||||||
* ''0'' = display size
|
* ''0'' = display size
|
||||||
* ''>0'' number of cells
|
* ''>0'' number of cells
|
||||||
==== Screen ====
|
==== Screen ====
|
||||||
The settings for screens, (TTY, PTY) are configured in the ''[screen]'' section.
|
The settings for screen access are configured in the ''[screen]'' section.
|
||||||
|
''vcsaDriver'' is used for Linux TTYs, and ''ptyDriver'' is used by X11 terminal mode.
|
||||||
|
|
||||||
The driver to get the information from the screen:
|
The driver to get the information from the screen:
|
||||||
driver=vcsaDriver
|
driver=vcsaDriver
|
||||||
@@ -1033,7 +1025,7 @@ The encoding of the screen
|
|||||||
Values:''cp850'' is used for Western languages like USA or Europe.
|
Values:''cp850'' is used for Western languages like USA or Europe.
|
||||||
|
|
||||||
The driver updates Fenrir with changes on the screen.
|
The driver updates Fenrir with changes on the screen.
|
||||||
screenUpdateDelay=0.05
|
screen_update_delay=0.05
|
||||||
Values: in Seconds
|
Values: in Seconds
|
||||||
|
|
||||||
If you want Fenrir to not be active on any screen for various reasons. Maybe an X server or Wayland is running on that screen. You can make Fenrir ignore it or multiple screens seperated by '','' with:
|
If you want Fenrir to not be active on any screen for various reasons. Maybe an X server or Wayland is running on that screen. You can make Fenrir ignore it or multiple screens seperated by '','' with:
|
||||||
@@ -1064,15 +1056,15 @@ Values:
|
|||||||
* ''<Device Name>'' just use the device with the given name.
|
* ''<Device Name>'' just use the device with the given name.
|
||||||
|
|
||||||
Gives Fenrir exclusive access to the keyboard and lets it control keystrokes. This is needed to intercept Fenrir related shortcuts.
|
Gives Fenrir exclusive access to the keyboard and lets it control keystrokes. This is needed to intercept Fenrir related shortcuts.
|
||||||
grabDevices=True
|
grab_devices=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
The following makes sense if you are using a second screenreader and want to have some hooked events. Fenrir ignores all shortcuts then.
|
The following makes sense if you are using a second screenreader and want to have some hooked events. Fenrir ignores all shortcuts then.
|
||||||
ignoreShortcuts=False
|
ignore_shortcuts=False
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
The current keyboard layout used for shortcuts.
|
The current keyboard layout used for shortcuts.
|
||||||
keyboardLayout=desktop
|
keyboard_layout=desktop
|
||||||
Values: An absolute Path to a Keyboard definition file or a Filename without extension located in ''/etc/fenrir/keyboard''
|
Values: An absolute Path to a Keyboard definition file or a Filename without extension located in ''/etc/fenrir/keyboard''
|
||||||
|
|
||||||
Announce characters while typing.
|
Announce characters while typing.
|
||||||
@@ -1080,23 +1072,23 @@ Announce characters while typing.
|
|||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Announce deleted characters
|
Announce deleted characters
|
||||||
charDeleteEcho=True
|
char_delete_echo=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Announce word after pressing space
|
Announce word after pressing space
|
||||||
wordEcho=False
|
word_echo=False
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Interrupt speech on any keypress
|
Interrupt speech on any keypress
|
||||||
interruptOnKeyPress=False
|
interrupt_on_key_press=False
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
You can filter the keys that speech should interrupt
|
You can filter the keys that speech should interrupt
|
||||||
interruptOnKeyPressFilter=
|
interrupt_on_key_press_filter=
|
||||||
Values: (List) empty = all keys, otherwise interrupt with specified keys
|
Values: (List) empty = all keys, otherwise interrupt with specified keys
|
||||||
|
|
||||||
The timeout that is used for double tap shortcuts
|
The timeout that is used for double tap shortcuts
|
||||||
doubleTapTimeout=0.2
|
double_tap_timeout=0.2
|
||||||
Values: Seconds
|
Values: Seconds
|
||||||
==== General ====
|
==== General ====
|
||||||
Overall settings can be configured from the section ''[general]''.
|
Overall settings can be configured from the section ''[general]''.
|
||||||
@@ -1106,19 +1098,19 @@ Set the current debug level:
|
|||||||
Values: off=0, error=1, warning=2, info=3
|
Values: off=0, error=1, warning=2, info=3
|
||||||
|
|
||||||
the current punctuation and dict file in use:
|
the current punctuation and dict file in use:
|
||||||
punctuationProfile=default
|
punctuation_profile=default
|
||||||
Values: Text, see available profiles in ''/etc/fenrir/punctuation'' or in ''sourceTree/config/punctuation''
|
Values: Text, see available profiles in ''/etc/fenrir/punctuation'' or in ''sourceTree/config/punctuation''
|
||||||
|
|
||||||
The current punctuation level in use:
|
The current punctuation level in use:
|
||||||
punctuationLevel=some
|
punctuation_level=some
|
||||||
Values: Text, See available levels in the used punctuation file.
|
Values: Text, See available levels in the used punctuation file.
|
||||||
|
|
||||||
Respect pause for punctuations:
|
Respect pause for punctuations:
|
||||||
respectPunctuationPause=True
|
respect_punctuation_pause=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Add a pause on Line break:
|
Add a pause on Line break:
|
||||||
newLinePause=True
|
new_line_pause=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Specify the path where the clipboard should be exported to.
|
Specify the path where the clipboard should be exported to.
|
||||||
@@ -1128,7 +1120,7 @@ The variable ''$user'' is replaced by the current logged username.
|
|||||||
Values: Text, Systemfilepath
|
Values: Text, Systemfilepath
|
||||||
|
|
||||||
The number of available clipboards:
|
The number of available clipboards:
|
||||||
numberOfClipboards=10
|
number_of_clipboards=10
|
||||||
Values: Integer, 1 - 999
|
Values: Integer, 1 - 999
|
||||||
|
|
||||||
Replace emoticons like :) or ;) with text insertions:
|
Replace emoticons like :) or ;) with text insertions:
|
||||||
@@ -1136,7 +1128,7 @@ Replace emoticons like :) or ;) with text insertions:
|
|||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Define the current Fenrir keys:
|
Define the current Fenrir keys:
|
||||||
fenrirKeys=KEY_KP0,KEY_META,KEY_INSERT
|
fenrir_keys=KEY_KP0,KEY_META,KEY_INSERT
|
||||||
Values, Text list, separated by comma.
|
Values, Text list, separated by comma.
|
||||||
|
|
||||||
Define the current script keys:
|
Define the current script keys:
|
||||||
@@ -1152,11 +1144,11 @@ The date format to be used for (date command) output:
|
|||||||
Values: see python specification for [[https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior|datetime.strftime]]
|
Values: see python specification for [[https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior|datetime.strftime]]
|
||||||
|
|
||||||
Enable or Disable spellcheck whilst typing:
|
Enable or Disable spellcheck whilst typing:
|
||||||
autoSpellCheck=True
|
auto_spell_check=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
The use of the dictionary with spellcheck:
|
The use of the dictionary with spellcheck:
|
||||||
spellCheckLanguage=en_US
|
spell_check_language=en_US
|
||||||
Values: Text, see aspell dictionary's.
|
Values: Text, see aspell dictionary's.
|
||||||
|
|
||||||
Folder Path for your scripts "scriptKey" functionality:
|
Folder Path for your scripts "scriptKey" functionality:
|
||||||
@@ -1197,25 +1189,8 @@ Leave the review mode when pressing a key:
|
|||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
|
|
||||||
Leave the review mode when changing the screen (From TTY3 to TTY4):
|
Leave the review mode when changing the screen (From TTY3 to TTY4):
|
||||||
leaveReviewOnScreenChange=True
|
leave_review_on_screen_change=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
==== Promote ====
|
|
||||||
"Promoted Lists" are configured in the section ''[promote]''.
|
|
||||||
Turn Promoted Lists" on or off:
|
|
||||||
enabled=True
|
|
||||||
Values: on=''True'', off=''False''
|
|
||||||
|
|
||||||
The minimum time interval of inactivity to activate promoting.
|
|
||||||
By default it promotes after 120 Seconds inactivity:
|
|
||||||
inactiveTimeoutSec=120
|
|
||||||
Values: in Seconds
|
|
||||||
|
|
||||||
Define a list of promoted words comma seperated:
|
|
||||||
list=
|
|
||||||
Values: text (comma seperated)
|
|
||||||
Example to promote the word "nickname" or a bash prompt:
|
|
||||||
list=nickname,$:,#:
|
|
||||||
|
|
||||||
==== Time ====
|
==== Time ====
|
||||||
The automated time announcement is configured in the section ''[time]''.
|
The automated time announcement is configured in the section ''[time]''.
|
||||||
Time announcement is disabled by default.
|
Time announcement is disabled by default.
|
||||||
|
|||||||
@@ -744,7 +744,6 @@ msgid "Script file is not executable"
|
|||||||
msgstr "Файл скрипта не исполняемый"
|
msgstr "Файл скрипта не исполняемый"
|
||||||
|
|
||||||
#: ../src/fenrirscreenreader/commands/commands/temp_disable_speech.py:17
|
#: ../src/fenrirscreenreader/commands/commands/temp_disable_speech.py:17
|
||||||
#: ../src/fenrirscreenreader/commands/onByteInput/15000-enable_temp_speech.py:17
|
|
||||||
#: ../src/fenrirscreenreader/commands/onKeyInput/15000-enable_temp_speech.py:17
|
#: ../src/fenrirscreenreader/commands/onKeyInput/15000-enable_temp_speech.py:17
|
||||||
msgid "disables speech until next keypress"
|
msgid "disables speech until next keypress"
|
||||||
msgstr "Отключить речь пока не нажата следующая клавиша"
|
msgstr "Отключить речь пока не нажата следующая клавиша"
|
||||||
@@ -898,7 +897,6 @@ msgid "speech disabled"
|
|||||||
msgstr "Речь выключена"
|
msgstr "Речь выключена"
|
||||||
|
|
||||||
#: ../src/fenrirscreenreader/commands/commands/toggle_speech.py:25
|
#: ../src/fenrirscreenreader/commands/commands/toggle_speech.py:25
|
||||||
#: ../src/fenrirscreenreader/commands/onByteInput/15000-enable_temp_speech.py:24
|
|
||||||
#: ../src/fenrirscreenreader/commands/onKeyInput/15000-enable_temp_speech.py:28
|
#: ../src/fenrirscreenreader/commands/onKeyInput/15000-enable_temp_speech.py:28
|
||||||
msgid "speech enabled"
|
msgid "speech enabled"
|
||||||
msgstr "Речь включена"
|
msgstr "Речь включена"
|
||||||
@@ -1089,15 +1087,6 @@ msgstr "Мерцание"
|
|||||||
msgid "default"
|
msgid "default"
|
||||||
msgstr "По умолчанию"
|
msgstr "По умолчанию"
|
||||||
|
|
||||||
#: ../src/fenrirscreenreader/core/byteManager.py:103
|
|
||||||
#: ../src/fenrirscreenreader/core/byteManager.py:105
|
|
||||||
msgid "Sticky Mode On"
|
|
||||||
msgstr "Режим залипания включен"
|
|
||||||
|
|
||||||
#: ../src/fenrirscreenreader/core/byteManager.py:109
|
|
||||||
msgid "bypass"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/fenrirscreenreader/core/fenrirManager.py:26
|
#: ../src/fenrirscreenreader/core/fenrirManager.py:26
|
||||||
msgid "Start Fenrir"
|
msgid "Start Fenrir"
|
||||||
msgstr "fenrir запущен"
|
msgstr "fenrir запущен"
|
||||||
@@ -1216,4 +1205,3 @@ msgstr "Меню"
|
|||||||
#: ../src/fenrirscreenreader/core/vmenuManager.py:234
|
#: ../src/fenrirscreenreader/core/vmenuManager.py:234
|
||||||
msgid "Action"
|
msgid "Action"
|
||||||
msgstr "Действие"
|
msgstr "Действие"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
daemonize
|
daemonize
|
||||||
dbus-python
|
dbus-python
|
||||||
evdev
|
evdev
|
||||||
pexpect
|
|
||||||
pyenchant
|
pyenchant
|
||||||
pyperclip
|
pyperclip
|
||||||
pyte
|
pyte
|
||||||
|
|||||||
@@ -103,9 +103,11 @@ setup(
|
|||||||
"pyudev>=0.21.0",
|
"pyudev>=0.21.0",
|
||||||
"setuptools",
|
"setuptools",
|
||||||
"setproctitle",
|
"setproctitle",
|
||||||
"pexpect",
|
|
||||||
"pyte>=0.7.0",
|
"pyte>=0.7.0",
|
||||||
],
|
],
|
||||||
|
extras_require={
|
||||||
|
"x11": ["python-xlib>=0.33"],
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if not forceSettingsFlag:
|
if not forceSettingsFlag:
|
||||||
|
|||||||
+13
-8
@@ -61,14 +61,15 @@ def create_argument_parser():
|
|||||||
help='Print debug messages to screen'
|
help='Print debug messages to screen'
|
||||||
)
|
)
|
||||||
argumentParser.add_argument(
|
argumentParser.add_argument(
|
||||||
'-e', '--emulated-pty',
|
'-x', '--x11',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)'
|
help='Use PTY emulation with X11 keyboard input scoped to the terminal window'
|
||||||
)
|
)
|
||||||
argumentParser.add_argument(
|
argumentParser.add_argument(
|
||||||
'-E', '--emulated-evdev',
|
'--x11-window-id',
|
||||||
action='store_true',
|
metavar='WINDOWID',
|
||||||
help='Use PTY emulation with evdev for input (single instance)'
|
default='',
|
||||||
|
help='X11 window id to use for --x11 terminal mode'
|
||||||
)
|
)
|
||||||
argumentParser.add_argument(
|
argumentParser.add_argument(
|
||||||
'-F',
|
'-F',
|
||||||
@@ -91,8 +92,8 @@ def validate_arguments(cliArgs):
|
|||||||
if option and ('#' not in option or '=' not in option):
|
if option and ('#' not in option or '=' not in option):
|
||||||
return False, f"Invalid option format: {option}\nExpected format: SECTION#SETTING=VALUE"
|
return False, f"Invalid option format: {option}\nExpected format: SECTION#SETTING=VALUE"
|
||||||
|
|
||||||
if cliArgs.emulated_pty and cliArgs.emulated_evdev:
|
if cliArgs.x11_window_id and not cliArgs.x11:
|
||||||
return False, "Cannot use both --emulated-pty and --emulated-evdev simultaneously"
|
return False, "--x11-window-id requires --x11"
|
||||||
|
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@@ -124,6 +125,10 @@ def run_fenrir():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def should_run_foreground(cliArgs):
|
||||||
|
return cliArgs.foreground or cliArgs.x11
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global cliArgs
|
global cliArgs
|
||||||
argumentParser = create_argument_parser()
|
argumentParser = create_argument_parser()
|
||||||
@@ -135,7 +140,7 @@ def main():
|
|||||||
argumentParser.error(errorMsg)
|
argumentParser.error(errorMsg)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if cliArgs.foreground or cliArgs.emulated_pty:
|
if should_run_foreground(cliArgs):
|
||||||
# Run directly in foreground
|
# Run directly in foreground
|
||||||
run_fenrir()
|
run_fenrir()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class command:
|
|||||||
self.env["runtime"]["MemoryManager"].add_index_list(
|
self.env["runtime"]["MemoryManager"].add_index_list(
|
||||||
"clipboardHistory",
|
"clipboardHistory",
|
||||||
self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
"general", "numberOfClipboards"
|
"general", "number_of_clipboards"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ class command:
|
|||||||
def update_spell_language(self):
|
def update_spell_language(self):
|
||||||
self.spellChecker = enchant.Dict(
|
self.spellChecker = enchant.Dict(
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -48,7 +48,7 @@ class command:
|
|||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
!= self.language
|
!= self.language
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("Cycle through key echo modes: character, word, off")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
settings_manager = self.env["runtime"]["SettingsManager"]
|
||||||
|
output_manager = self.env["runtime"]["OutputManager"]
|
||||||
|
|
||||||
|
# Get current settings
|
||||||
|
char_echo_mode = settings_manager.get_setting("keyboard", "char_echo_mode")
|
||||||
|
word_echo = settings_manager.get_setting_as_bool("keyboard", "word_echo")
|
||||||
|
|
||||||
|
# Determine current state and cycle to next
|
||||||
|
# States: character (char=1, word=False) -> word (char=0, word=True) -> off (char=0, word=False)
|
||||||
|
if char_echo_mode == "1" and not word_echo:
|
||||||
|
# Currently character echo, switch to word echo
|
||||||
|
settings_manager.set_setting("keyboard", "char_echo_mode", "0")
|
||||||
|
settings_manager.set_setting("keyboard", "word_echo", "True")
|
||||||
|
output_manager.present_text(
|
||||||
|
_("Echo by word"), interrupt=True
|
||||||
|
)
|
||||||
|
elif word_echo:
|
||||||
|
# Currently word echo, switch to off
|
||||||
|
settings_manager.set_setting("keyboard", "char_echo_mode", "0")
|
||||||
|
settings_manager.set_setting("keyboard", "word_echo", "False")
|
||||||
|
output_manager.present_text(
|
||||||
|
_("Echo off"), interrupt=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Currently off (or caps mode), switch to character echo
|
||||||
|
settings_manager.set_setting("keyboard", "char_echo_mode", "1")
|
||||||
|
settings_manager.set_setting("keyboard", "word_echo", "False")
|
||||||
|
output_manager.present_text(
|
||||||
|
_("Echo by character"), interrupt=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -59,7 +59,7 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_layout = self.env["runtime"]["SettingsManager"].get_setting(
|
current_layout = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"keyboard", "keyboardLayout"
|
"keyboard", "keyboard_layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Extract layout name from full path if needed
|
# Extract layout name from full path if needed
|
||||||
@@ -83,7 +83,7 @@ class command:
|
|||||||
|
|
||||||
# Update setting and reload shortcuts
|
# Update setting and reload shortcuts
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
"keyboard", "keyboardLayout", next_layout
|
"keyboard", "keyboard_layout", next_layout
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reload shortcuts with new layout
|
# Reload shortcuts with new layout
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("speak diff review key help")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].present_key_help()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next added diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_added()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next character in diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_char()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next diff file section")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_file_section()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next diff hunk")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_hunk()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_line()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next removed diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_removed()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to next word in diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].next_word()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous added diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_added()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous character in diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_char()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous diff file section")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_file_section()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous diff hunk")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_hunk()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_line()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous removed diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_removed()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("move to previous word in diff line")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].prev_word()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("speak diff summary")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].present_summary()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("exit diff review mode")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not self.env["runtime"]["DiffReviewManager"].is_active():
|
||||||
|
return
|
||||||
|
self.env["runtime"]["DiffReviewManager"].disable_mode()
|
||||||
|
self.env["runtime"]["DiffReviewManager"].present_mode_message(
|
||||||
|
_("Diff review mode disabled."),
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -26,7 +26,7 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
clipboard_file_path = self.env["runtime"][
|
clipboard_file_path = self.env["runtime"][
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
].get_setting("general", "clipboardExportPath")
|
].get_setting("general", "clipboard_export_path")
|
||||||
clipboard_file_path = clipboard_file_path.replace(
|
clipboard_file_path = clipboard_file_path.replace(
|
||||||
"$user", self.env["general"]["curr_user"]
|
"$user", self.env["general"]["curr_user"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class command:
|
|||||||
return _("sends the following keypress to the terminal or application")
|
return _("sends the following keypress to the terminal or application")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.env["input"]["keyForeward"] = 3
|
self.env["input"]["key_forward"] = 3
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Forward next keypress"), interrupt=True
|
_("Forward next keypress"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
clipboard_file_path = self.env["runtime"][
|
clipboard_file_path = self.env["runtime"][
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
].get_setting("general", "clipboardExportPath")
|
].get_setting("general", "clipboard_export_path")
|
||||||
clipboard_file_path = clipboard_file_path.replace(
|
clipboard_file_path = clipboard_file_path.replace(
|
||||||
"$user", self.env["general"]["curr_user"]
|
"$user", self.env["general"]["curr_user"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class command:
|
|||||||
self.env["runtime"]["MemoryManager"].add_index_list(
|
self.env["runtime"]["MemoryManager"].add_index_list(
|
||||||
"clipboardHistory",
|
"clipboardHistory",
|
||||||
self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
"general", "numberOfClipboards"
|
"general", "number_of_clipboards"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -18,17 +18,17 @@ class command:
|
|||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
# Use commandBuffer like other commands
|
# Use commandBuffer like other commands
|
||||||
if "progressMonitoring" not in self.env["commandBuffer"]:
|
if "progress_monitoring" not in self.env["commandBuffer"]:
|
||||||
# Check if progress monitoring should be enabled by default from
|
# Check if progress monitoring should be enabled by default from
|
||||||
# settings
|
# settings
|
||||||
try:
|
try:
|
||||||
default_enabled = self.env["runtime"][
|
default_enabled = self.env["runtime"][
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
].get_setting_as_bool("sound", "progressMonitoring")
|
].get_setting_as_bool("sound", "progress_monitoring")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# If setting doesn't exist, default to False
|
# If setting doesn't exist, default to False
|
||||||
default_enabled = False
|
default_enabled = False
|
||||||
self.env["commandBuffer"]["progressMonitoring"] = default_enabled
|
self.env["commandBuffer"]["progress_monitoring"] = default_enabled
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = 0
|
self.env["commandBuffer"]["lastProgressTime"] = 0
|
||||||
self.env["commandBuffer"]["lastProgressValue"] = -1
|
self.env["commandBuffer"]["lastProgressValue"] = -1
|
||||||
|
|
||||||
@@ -40,12 +40,12 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# Check if commandBuffer exists
|
# Check if commandBuffer exists
|
||||||
if "progressMonitoring" not in self.env["commandBuffer"]:
|
if "progress_monitoring" not in self.env["commandBuffer"]:
|
||||||
self.env["commandBuffer"]["progressMonitoring"] = False
|
self.env["commandBuffer"]["progress_monitoring"] = False
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = 0
|
self.env["commandBuffer"]["lastProgressTime"] = 0
|
||||||
self.env["commandBuffer"]["lastProgressValue"] = -1
|
self.env["commandBuffer"]["lastProgressValue"] = -1
|
||||||
|
|
||||||
if self.env["commandBuffer"]["progressMonitoring"]:
|
if self.env["commandBuffer"]["progress_monitoring"]:
|
||||||
self.stop_progress_monitoring()
|
self.stop_progress_monitoring()
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Progress monitoring disabled"), interrupt=True
|
_("Progress monitoring disabled"), interrupt=True
|
||||||
@@ -57,17 +57,17 @@ class command:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def start_progress_monitoring(self):
|
def start_progress_monitoring(self):
|
||||||
self.env["commandBuffer"]["progressMonitoring"] = True
|
self.env["commandBuffer"]["progress_monitoring"] = True
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = time.time()
|
self.env["commandBuffer"]["lastProgressTime"] = time.time()
|
||||||
self.env["commandBuffer"]["lastProgressValue"] = -1
|
self.env["commandBuffer"]["lastProgressValue"] = -1
|
||||||
# Don't control speech - let user decide with silence_until_prompt
|
# Don't control speech - let user decide with silence_until_prompt
|
||||||
|
|
||||||
def stop_progress_monitoring(self):
|
def stop_progress_monitoring(self):
|
||||||
self.env["commandBuffer"]["progressMonitoring"] = False
|
self.env["commandBuffer"]["progress_monitoring"] = False
|
||||||
# Don't control speech - progress monitor is beep-only
|
# Don't control speech - progress monitor is beep-only
|
||||||
|
|
||||||
def detect_progress(self, text):
|
def detect_progress(self, text):
|
||||||
if not self.env["commandBuffer"]["progressMonitoring"]:
|
if not self.env["commandBuffer"]["progress_monitoring"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Skip progress detection if current screen looks like a prompt
|
# Skip progress detection if current screen looks like a prompt
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ class command:
|
|||||||
def update_spell_language(self):
|
def update_spell_language(self):
|
||||||
self.spellChecker = enchant.Dict(
|
self.spellChecker = enchant.Dict(
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -51,7 +51,7 @@ class command:
|
|||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
!= self.language
|
!= self.language
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ class command:
|
|||||||
return _("Interrupts the current presentation")
|
return _("Interrupts the current presentation")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if len(self.env["input"]["prevDeepestInput"]) > len(
|
if len(self.env["input"]["prev_deepest_input"]) > len(
|
||||||
self.env["input"]["currInput"]
|
self.env["input"]["curr_input"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
self.env["runtime"]["OutputManager"].interrupt_output()
|
self.env["runtime"]["OutputManager"].interrupt_output()
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ class command:
|
|||||||
return
|
return
|
||||||
self.spellChecker = enchant.Dict(
|
self.spellChecker = enchant.Dict(
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -51,7 +51,7 @@ class command:
|
|||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
!= self.language
|
!= self.language
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
"general",
|
"general",
|
||||||
"autoPresentIndent",
|
"auto_present_indent",
|
||||||
str(
|
str(
|
||||||
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoPresentIndent"
|
"general", "auto_present_indent"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoPresentIndent"
|
"general", "auto_present_indent"
|
||||||
):
|
):
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("autoindent enabled"), sound_icon="", interrupt=True
|
_("autoindent enabled"), sound_icon="", interrupt=True
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
"speech",
|
"speech",
|
||||||
"autoReadIncoming",
|
"auto_read_incoming",
|
||||||
str(
|
str(
|
||||||
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"speech", "autoReadIncoming"
|
"speech", "auto_read_incoming"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"speech", "autoReadIncoming"
|
"speech", "auto_read_incoming"
|
||||||
):
|
):
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("autoread enabled"), sound_icon="", interrupt=True
|
_("autoread enabled"), sound_icon="", interrupt=True
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
self.env["runtime"]["SettingsManager"].set_setting(
|
||||||
"general",
|
"general",
|
||||||
"autoSpellCheck",
|
"auto_spell_check",
|
||||||
str(
|
str(
|
||||||
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoSpellCheck"
|
"general", "auto_spell_check"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoSpellCheck"
|
"general", "auto_spell_check"
|
||||||
):
|
):
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("auto spellcheck enabled"), sound_icon="", interrupt=True
|
_("auto spellcheck enabled"), sound_icon="", interrupt=True
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("toggle diff review mode")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.env["runtime"]["DiffReviewManager"].toggle_mode()
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -25,7 +25,7 @@ class command:
|
|||||||
if self.env["runtime"]["PunctuationManager"].cycle_punctuation():
|
if self.env["runtime"]["PunctuationManager"].cycle_punctuation():
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "punctuationLevel"
|
"general", "punctuation_level"
|
||||||
),
|
),
|
||||||
interrupt=True,
|
interrupt=True,
|
||||||
ignore_punctuation=True,
|
ignore_punctuation=True,
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Fenrir TTY screen reader
|
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
|
||||||
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
class command:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
|
||||||
"keyboard", "interruptOnKeyPress"
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if self.env["runtime"]["InputManager"].no_key_pressed():
|
|
||||||
return
|
|
||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
|
||||||
return
|
|
||||||
if len(self.env["input"]["currInput"]) <= len(
|
|
||||||
self.env["input"]["prevInput"]
|
|
||||||
):
|
|
||||||
return
|
|
||||||
# if the filter is set
|
|
||||||
if (
|
|
||||||
self.env["runtime"]["SettingsManager"]
|
|
||||||
.get_setting("keyboard", "interruptOnKeyPressFilter")
|
|
||||||
.strip()
|
|
||||||
!= ""
|
|
||||||
):
|
|
||||||
filter_list = (
|
|
||||||
self.env["runtime"]["SettingsManager"]
|
|
||||||
.get_setting("keyboard", "interruptOnKeyPressFilter")
|
|
||||||
.split(",")
|
|
||||||
)
|
|
||||||
for currInput in self.env["input"]["currInput"]:
|
|
||||||
if currInput not in filter_list:
|
|
||||||
return
|
|
||||||
self.env["runtime"]["OutputManager"].interrupt_output()
|
|
||||||
|
|
||||||
def set_callback(self, callback):
|
|
||||||
pass
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Fenrir TTY screen reader
|
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
|
||||||
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
class command:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return _("disables speech until next keypress")
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self.env["commandBuffer"]["enableSpeechOnKeypress"]:
|
|
||||||
return
|
|
||||||
self.env["runtime"]["SettingsManager"].set_setting(
|
|
||||||
"speech",
|
|
||||||
"enabled",
|
|
||||||
str(self.env["commandBuffer"]["enableSpeechOnKeypress"]),
|
|
||||||
)
|
|
||||||
self.env["commandBuffer"]["enableSpeechOnKeypress"] = False
|
|
||||||
# Also disable prompt watching since speech was manually re-enabled
|
|
||||||
if "silenceUntilPrompt" in self.env["commandBuffer"]:
|
|
||||||
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
|
||||||
_("speech enabled"), sound_icon="SpeechOn", interrupt=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_callback(self, callback):
|
|
||||||
pass
|
|
||||||
@@ -24,14 +24,14 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
# enabled?
|
# enabled?
|
||||||
active = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
active = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
"keyboard", "charEchoMode"
|
"keyboard", "char_echo_mode"
|
||||||
)
|
)
|
||||||
# 0 = off
|
# 0 = off
|
||||||
if active == 0:
|
if active == 0:
|
||||||
return
|
return
|
||||||
# 2 = caps only
|
# 2 = caps only
|
||||||
if active == 2:
|
if active == 2:
|
||||||
if not self.env["input"]["newCapsLock"]:
|
if not self.env["input"]["new_caps_lock"]:
|
||||||
return
|
return
|
||||||
# big changes are no char (but the value is bigger than one maybe the
|
# big changes are no char (but the value is bigger than one maybe the
|
||||||
# differ needs longer than you can type, so a little strange random
|
# differ needs longer than you can type, so a little strange random
|
||||||
@@ -42,19 +42,10 @@ class command:
|
|||||||
)
|
)
|
||||||
if x_move > 3:
|
if x_move > 3:
|
||||||
return
|
return
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
if self.env["runtime"][
|
if self.env["runtime"][
|
||||||
"InputManager"
|
"InputManager"
|
||||||
].get_last_deepest_input() in [["KEY_TAB"]]:
|
].get_last_deepest_input() in [["KEY_TAB"]]:
|
||||||
return
|
return
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
|
||||||
"BYTE"
|
|
||||||
]:
|
|
||||||
if self.env["runtime"]["ByteManager"].get_last_byte_key() in [
|
|
||||||
b" ",
|
|
||||||
b"\t",
|
|
||||||
]:
|
|
||||||
return
|
|
||||||
# detect deletion or chilling
|
# detect deletion or chilling
|
||||||
if (
|
if (
|
||||||
self.env["screen"]["new_cursor"]["x"]
|
self.env["screen"]["new_cursor"]["x"]
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
# is it enabled?
|
# is it enabled?
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"keyboard", "wordEcho"
|
"keyboard", "word_echo"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# is navigation?
|
# is navigation?
|
||||||
|
|||||||
@@ -39,23 +39,23 @@ class command:
|
|||||||
return
|
return
|
||||||
self.spellChecker = enchant.Dict(
|
self.spellChecker = enchant.Dict(
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
self.language = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not initialized:
|
if not initialized:
|
||||||
return
|
return
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoSpellCheck"
|
"general", "auto_spell_check"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "spellCheckLanguage"
|
"general", "spell_check_language"
|
||||||
)
|
)
|
||||||
!= self.language
|
!= self.language
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"keyboard", "charDeleteEcho"
|
"keyboard", "char_delete_echo"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# detect typing or chilling
|
# detect typing or chilling
|
||||||
|
|||||||
+14
-8
@@ -46,7 +46,7 @@ class command:
|
|||||||
|
|
||||||
# echo word insteed of char
|
# echo word insteed of char
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"keyboard", "wordEcho"
|
"keyboard", "word_echo"
|
||||||
):
|
):
|
||||||
if (
|
if (
|
||||||
abs(
|
abs(
|
||||||
@@ -71,14 +71,20 @@ class command:
|
|||||||
self.env["screen"]["new_cursor"]["y"],
|
self.env["screen"]["new_cursor"]["y"],
|
||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
# Don't interrupt ongoing auto-read announcements
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
if curr_char.isspace():
|
if curr_char.isspace():
|
||||||
# Only announce spaces during pure navigation (arrow keys)
|
# Only announce spaces during pure navigation (arrow keys)
|
||||||
# Check if this is really navigation by looking at input history
|
# Check if this is really navigation by looking at input history
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
last_input = self.env["runtime"][
|
||||||
"KEY"
|
"InputManager"
|
||||||
] and self.env["runtime"]["InputManager"].get_last_deepest_input()[
|
].get_last_deepest_input()
|
||||||
0
|
if last_input and last_input[0] in [
|
||||||
] in [
|
|
||||||
"KEY_LEFT",
|
"KEY_LEFT",
|
||||||
"KEY_RIGHT",
|
"KEY_RIGHT",
|
||||||
"KEY_UP",
|
"KEY_UP",
|
||||||
@@ -87,14 +93,14 @@ class command:
|
|||||||
char_utils.present_char_for_review(
|
char_utils.present_char_for_review(
|
||||||
self.env,
|
self.env,
|
||||||
curr_char,
|
curr_char,
|
||||||
interrupt=True,
|
interrupt=do_interrupt,
|
||||||
announce_capital=True,
|
announce_capital=True,
|
||||||
flush=False,
|
flush=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
curr_char,
|
curr_char,
|
||||||
interrupt=True,
|
interrupt=do_interrupt,
|
||||||
ignore_punctuation=True,
|
ignore_punctuation=True,
|
||||||
announce_capital=True,
|
announce_capital=True,
|
||||||
flush=False,
|
flush=False,
|
||||||
|
|||||||
@@ -33,20 +33,11 @@ class command:
|
|||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
tab_detected = False
|
tab_detected = False
|
||||||
|
|
||||||
# Check KEY mode
|
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
if (self.env["runtime"]["InputManager"].get_last_deepest_input()
|
if (self.env["runtime"]["InputManager"].get_last_deepest_input()
|
||||||
in [["KEY_TAB"]]):
|
in [["KEY_TAB"]]):
|
||||||
tab_detected = True
|
tab_detected = True
|
||||||
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
||||||
|
|
||||||
# Check BYTE mode
|
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in ["BYTE"]:
|
|
||||||
for currByte in self.env["runtime"]["ByteManager"].get_last_byte_key():
|
|
||||||
if currByte == 9: # Tab character
|
|
||||||
tab_detected = True
|
|
||||||
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
|
||||||
|
|
||||||
# Check if tab was pressed recently (200ms window)
|
# Check if tab was pressed recently (200ms window)
|
||||||
if not tab_detected:
|
if not tab_detected:
|
||||||
time_since_tab = current_time - self.env["commandBuffer"]["tabCompletion"]["lastTabTime"]
|
time_since_tab = current_time - self.env["commandBuffer"]["tabCompletion"]["lastTabTime"]
|
||||||
@@ -153,10 +144,17 @@ class command:
|
|||||||
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
|
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
|
||||||
curr_delta = curr_delta.strip()
|
curr_delta = curr_delta.strip()
|
||||||
|
|
||||||
|
# Don't interrupt ongoing auto-read announcements
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
# Enhanced announcement with better handling of empty completions
|
# Enhanced announcement with better handling of empty completions
|
||||||
if curr_delta:
|
if curr_delta:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
curr_delta, interrupt=True, announce_capital=True, flush=False
|
curr_delta, interrupt=do_interrupt, announce_capital=True, flush=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
|
|||||||
@@ -66,8 +66,15 @@ class command:
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Don't interrupt ongoing auto-read announcements
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
curr_word, interrupt=True, flush=False
|
curr_word, interrupt=do_interrupt, flush=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
|
|||||||
+13
-7
@@ -30,8 +30,8 @@ class command:
|
|||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
self.lastIdent = 0
|
self.lastIdent = 0
|
||||||
return
|
return
|
||||||
# this leads to problems in vim -> status line change -> no
|
# Don't announce cursor movements when auto-read is handling incoming text
|
||||||
# announcement, so we do check the lengh as hack
|
# This prevents interrupting ongoing auto-read announcements
|
||||||
if self.env["runtime"]["ScreenManager"].is_delta():
|
if self.env["runtime"]["ScreenManager"].is_delta():
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -44,28 +44,34 @@ class command:
|
|||||||
self.env["screen"]["new_cursor"]["y"],
|
self.env["screen"]["new_cursor"]["y"],
|
||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
# Don't interrupt ongoing auto-read announcements with cursor movement
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
if curr_line.isspace():
|
if curr_line.isspace():
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("blank"), sound_icon="EmptyLine", interrupt=True, flush=False
|
_("blank"), sound_icon="EmptyLine", interrupt=do_interrupt, flush=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# ident
|
# ident
|
||||||
curr_ident = len(curr_line) - len(curr_line.lstrip())
|
curr_ident = len(curr_line) - len(curr_line.lstrip())
|
||||||
if self.lastIdent == -1:
|
if self.lastIdent == -1:
|
||||||
self.lastIdent = curr_ident
|
self.lastIdent = curr_ident
|
||||||
do_interrupt = True
|
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoPresentIndent"
|
"general", "auto_present_indent"
|
||||||
):
|
):
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
"general", "autoPresentIndentMode"
|
"general", "auto_present_indent_mode"
|
||||||
) in [0, 1]:
|
) in [0, 1]:
|
||||||
if self.lastIdent != curr_ident:
|
if self.lastIdent != curr_ident:
|
||||||
self.env["runtime"]["OutputManager"].play_frequence(
|
self.env["runtime"]["OutputManager"].play_frequence(
|
||||||
curr_ident * 50, 0.1, interrupt=do_interrupt
|
curr_ident * 50, 0.1, interrupt=do_interrupt
|
||||||
)
|
)
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
"general", "autoPresentIndentMode"
|
"general", "auto_present_indent_mode"
|
||||||
) in [0, 2]:
|
) in [0, 2]:
|
||||||
if self.lastIdent != curr_ident:
|
if self.lastIdent != curr_ident:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
|||||||
@@ -68,10 +68,10 @@ class command:
|
|||||||
|
|
||||||
# Only beep/announce if indentation level has changed
|
# Only beep/announce if indentation level has changed
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "autoPresentIndent"
|
"general", "auto_present_indent"
|
||||||
) and self.lastIdent != curr_ident:
|
) and self.lastIdent != curr_ident:
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
"general", "autoPresentIndentMode"
|
"general", "auto_present_indent_mode"
|
||||||
) in [0, 1]:
|
) in [0, 1]:
|
||||||
self.env["runtime"]["OutputManager"].play_frequence(
|
self.env["runtime"]["OutputManager"].play_frequence(
|
||||||
curr_ident * 50, 0.1, interrupt=False
|
curr_ident * 50, 0.1, interrupt=False
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"review", "leaveReviewOnCursorChange"
|
"review", "leave_review_on_cursor_change"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["runtime"]["CursorManager"].is_review_mode():
|
if self.env["runtime"]["CursorManager"].is_review_mode():
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
if "pendingPromptText" not in self.env["commandBuffer"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
pending_text = self.env["commandBuffer"]["pendingPromptText"]
|
||||||
|
if not pending_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
pending_time = self.env["commandBuffer"].get("pendingPromptTime", 0)
|
||||||
|
delay = self.env["runtime"]["SettingsManager"].get_setting_as_float(
|
||||||
|
"speech", "batch_flush_interval"
|
||||||
|
)
|
||||||
|
if time.time() - pending_time < delay:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
pending_text, interrupt=False, flush=False
|
||||||
|
)
|
||||||
|
self.env["commandBuffer"]["pendingPromptText"] = ""
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -5,11 +5,16 @@
|
|||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
ANNOUNCEMENT_LOCK_TIMEOUT_SEC = 5.0
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
@@ -26,6 +31,59 @@ class command:
|
|||||||
def get_description(self):
|
def get_description(self):
|
||||||
return "No Description found"
|
return "No Description found"
|
||||||
|
|
||||||
|
def _get_announcement_lock_path(self):
|
||||||
|
return os.path.join(
|
||||||
|
tempfile.gettempdir(),
|
||||||
|
f"fenrirscreenreader-{os.getuid()}-time-announcement.lock",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _try_create_announcement_lock(self, announcement_slot, now):
|
||||||
|
lock_path = self._get_announcement_lock_path()
|
||||||
|
try:
|
||||||
|
lock_fd = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
|
||||||
|
except FileExistsError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with os.fdopen(lock_fd, "w", encoding="utf-8") as lock_file:
|
||||||
|
lock_file.write(f"{os.getpid()} {announcement_slot} {now}\n")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _read_announcement_lock_slot(self, lock_path):
|
||||||
|
with open(lock_path, "r", encoding="utf-8") as lock_file:
|
||||||
|
lock_content = lock_file.readline().strip().split()
|
||||||
|
if len(lock_content) < 2:
|
||||||
|
return ""
|
||||||
|
return lock_content[1]
|
||||||
|
|
||||||
|
def _claim_announcement_lock(self, announcement_slot):
|
||||||
|
now = time.time()
|
||||||
|
if self._try_create_announcement_lock(announcement_slot, now):
|
||||||
|
return True
|
||||||
|
|
||||||
|
lock_path = self._get_announcement_lock_path()
|
||||||
|
try:
|
||||||
|
lock_slot = self._read_announcement_lock_slot(lock_path)
|
||||||
|
lock_stat = os.stat(lock_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return self._try_create_announcement_lock(announcement_slot, now)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if lock_slot == announcement_slot:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not lock_slot and now - lock_stat.st_mtime < ANNOUNCEMENT_LOCK_TIMEOUT_SEC:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.unlink(lock_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return self._try_create_announcement_lock(announcement_slot, now)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"time", "enabled"
|
"time", "enabled"
|
||||||
@@ -50,6 +108,7 @@ class command:
|
|||||||
if delay_sec > 0:
|
if delay_sec > 0:
|
||||||
if int((now - self.last_time).total_seconds()) < delay_sec:
|
if int((now - self.last_time).total_seconds()) < delay_sec:
|
||||||
return
|
return
|
||||||
|
announcement_slot = f"delay:{int(now.timestamp()) // delay_sec}"
|
||||||
else:
|
else:
|
||||||
# should announce?
|
# should announce?
|
||||||
if not str(now.minute).zfill(2) in on_minutes:
|
if not str(now.minute).zfill(2) in on_minutes:
|
||||||
@@ -58,6 +117,7 @@ class command:
|
|||||||
if now.hour == self.last_time.hour:
|
if now.hour == self.last_time.hour:
|
||||||
if now.minute == self.last_time.minute:
|
if now.minute == self.last_time.minute:
|
||||||
return
|
return
|
||||||
|
announcement_slot = f"minute:{datetime.datetime.strftime(now, '%Y%m%d%H%M')}"
|
||||||
|
|
||||||
date_format = self.env["runtime"]["SettingsManager"].get_setting(
|
date_format = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "date_format"
|
"general", "date_format"
|
||||||
@@ -78,6 +138,10 @@ class command:
|
|||||||
if not (present_date or present_time):
|
if not (present_date or present_time):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not self._claim_announcement_lock(announcement_slot):
|
||||||
|
self.last_time = now
|
||||||
|
return
|
||||||
|
|
||||||
time_format = self.env["runtime"]["SettingsManager"].get_setting(
|
time_format = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
"general", "time_format"
|
"general", "time_format"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,31 +22,31 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"keyboard", "interruptOnKeyPress"
|
"keyboard", "interrupt_on_key_press"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["runtime"]["InputManager"].no_key_pressed():
|
if self.env["runtime"]["InputManager"].no_key_pressed():
|
||||||
return
|
return
|
||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
return
|
return
|
||||||
if len(self.env["input"]["currInput"]) <= len(
|
if len(self.env["input"]["curr_input"]) <= len(
|
||||||
self.env["input"]["prevInput"]
|
self.env["input"]["prev_input"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# if the filter is set
|
# if the filter is set
|
||||||
if (
|
if (
|
||||||
self.env["runtime"]["SettingsManager"]
|
self.env["runtime"]["SettingsManager"]
|
||||||
.get_setting("keyboard", "interruptOnKeyPressFilter")
|
.get_setting("keyboard", "interrupt_on_key_press_filter")
|
||||||
.strip()
|
.strip()
|
||||||
!= ""
|
!= ""
|
||||||
):
|
):
|
||||||
filter_list = (
|
filter_list = (
|
||||||
self.env["runtime"]["SettingsManager"]
|
self.env["runtime"]["SettingsManager"]
|
||||||
.get_setting("keyboard", "interruptOnKeyPressFilter")
|
.get_setting("keyboard", "interrupt_on_key_press_filter")
|
||||||
.split(",")
|
.split(",")
|
||||||
)
|
)
|
||||||
for currInput in self.env["input"]["currInput"]:
|
for curr_key in self.env["input"]["curr_input"]:
|
||||||
if currInput not in filter_list:
|
if curr_key not in filter_list:
|
||||||
return
|
return
|
||||||
self.env["runtime"]["OutputManager"].interrupt_output()
|
self.env["runtime"]["OutputManager"].interrupt_output()
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
if self.env["runtime"]["InputManager"].no_key_pressed():
|
if self.env["runtime"]["InputManager"].no_key_pressed():
|
||||||
return
|
return
|
||||||
if len(self.env["input"]["prevInput"]) > 0:
|
if len(self.env["input"]["prev_input"]) > 0:
|
||||||
return
|
return
|
||||||
if not self.env["commandBuffer"]["enableSpeechOnKeypress"]:
|
if not self.env["commandBuffer"]["enableSpeechOnKeypress"]:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if (
|
if (
|
||||||
self.env["input"]["oldCapsLock"]
|
self.env["input"]["old_caps_lock"]
|
||||||
== self.env["input"]["newCapsLock"]
|
== self.env["input"]["new_caps_lock"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["input"]["newCapsLock"]:
|
if self.env["input"]["new_caps_lock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Capslock on"), interrupt=True
|
_("Capslock on"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if (
|
if (
|
||||||
self.env["input"]["oldScrollLock"]
|
self.env["input"]["old_scroll_lock"]
|
||||||
== self.env["input"]["newScrollLock"]
|
== self.env["input"]["new_scroll_lock"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["input"]["newScrollLock"]:
|
if self.env["input"]["new_scroll_lock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Scrolllock on"), interrupt=True
|
_("Scrolllock on"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class command:
|
|||||||
return "No description found"
|
return "No description found"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]:
|
if self.env["input"]["old_num_lock"] == self.env["input"]["new_num_lock"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Only announce numlock changes if an actual numlock key was pressed
|
# Only announce numlock changes if an actual numlock key was pressed
|
||||||
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
|
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
|
||||||
current_input = self.env["input"]["currInput"]
|
current_input = self.env["input"]["curr_input"]
|
||||||
|
|
||||||
# Check if this is a genuine numlock key press by verifying:
|
# Check if this is a genuine numlock key press by verifying:
|
||||||
# 1. KEY_NUMLOCK is in the current input sequence
|
# 1. KEY_NUMLOCK is in the current input sequence
|
||||||
@@ -40,7 +40,7 @@ class command:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if is_genuine_numlock:
|
if is_genuine_numlock:
|
||||||
if self.env["input"]["newNumLock"]:
|
if self.env["input"]["new_num_lock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Numlock on"), interrupt=True
|
_("Numlock on"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"review", "leaveReviewOnScreenChange"
|
"review", "leave_review_on_screen_change"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
self.env["runtime"]["CursorManager"].clear_review_cursor()
|
self.env["runtime"]["CursorManager"].clear_review_cursor()
|
||||||
|
|||||||
@@ -41,20 +41,11 @@ class command:
|
|||||||
== self.env["runtime"]["ScreenManager"].get_rows() - 1
|
== self.env["runtime"]["ScreenManager"].get_rows() - 1
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
if not (
|
if not (
|
||||||
self.env["runtime"]["InputManager"].get_last_deepest_input()
|
self.env["runtime"]["InputManager"].get_last_deepest_input()
|
||||||
in [["KEY_UP"], ["KEY_DOWN"]]
|
in [["KEY_UP"], ["KEY_DOWN"]]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
|
||||||
"BYTE"
|
|
||||||
]:
|
|
||||||
if not (
|
|
||||||
self.env["runtime"]["ByteManager"].get_last_byte_key()
|
|
||||||
in [b"^[[A", b"^[[B"]
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get the current cursor's line from both old and new content
|
# Get the current cursor's line from both old and new content
|
||||||
prev_line = self.env["screen"]["old_content_text"].split("\n")[
|
prev_line = self.env["screen"]["old_content_text"].split("\n")[
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class command:
|
|||||||
# Only run if progress monitoring is enabled
|
# Only run if progress monitoring is enabled
|
||||||
try:
|
try:
|
||||||
if (
|
if (
|
||||||
"progressMonitoring" in self.env["commandBuffer"]
|
"progress_monitoring" in self.env["commandBuffer"]
|
||||||
and self.env["commandBuffer"]["progressMonitoring"]
|
and self.env["commandBuffer"]["progress_monitoring"]
|
||||||
):
|
):
|
||||||
# Check if current line is a prompt - if so, reset progress
|
# Check if current line is a prompt - if so, reset progress
|
||||||
# state
|
# state
|
||||||
@@ -40,13 +40,19 @@ class command:
|
|||||||
):
|
):
|
||||||
self.detect_progress(self.env["screen"]["new_delta"])
|
self.detect_progress(self.env["screen"]["new_delta"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Silently ignore errors to avoid disrupting normal operation
|
# Log errors for debugging instead of silently ignoring
|
||||||
pass
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Progress detector error: " + str(e),
|
||||||
|
debug.DebugLevel.ERROR,
|
||||||
|
)
|
||||||
|
|
||||||
def is_real_progress_update(self):
|
def is_real_progress_update(self):
|
||||||
"""Check if this is a real progress update vs screen change/window switch"""
|
"""Check if this is a real progress update vs screen change/window switch"""
|
||||||
# If the screen/application changed, it's not a progress update
|
# If the screen/application changed, it's not a progress update
|
||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Progress filter: screen change detected", debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If there was a large cursor movement, it's likely navigation, not
|
# If there was a large cursor movement, it's likely navigation, not
|
||||||
@@ -62,6 +68,10 @@ class command:
|
|||||||
)
|
)
|
||||||
# Large movements suggest navigation, not progress output
|
# Large movements suggest navigation, not progress output
|
||||||
if y_move > 2 or x_move > 20:
|
if y_move > 2 or x_move > 20:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"Progress filter: large cursor move y={y_move} x={x_move}",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if delta is too large (screen change) vs small incremental
|
# Check if delta is too large (screen change) vs small incremental
|
||||||
@@ -71,16 +81,27 @@ class command:
|
|||||||
if (
|
if (
|
||||||
delta_length > 200
|
delta_length > 200
|
||||||
): # Allow longer progress lines like Claude Code's status
|
): # Allow longer progress lines like Claude Code's status
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"Progress filter: delta too long ({delta_length})",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If delta contains newlines and is substantial, let incoming handler
|
# If delta contains newlines and is substantial, let incoming handler
|
||||||
# deal with it to avoid interfering with multi-line text output
|
# deal with it to avoid interfering with multi-line text output
|
||||||
if '\n' in delta_text and delta_length > 50:
|
if '\n' in delta_text and delta_length > 50:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"Progress filter: multiline delta ({delta_length} chars)",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if current line looks like a prompt - progress unlikely during
|
# Check if current line looks like a prompt - progress unlikely during
|
||||||
# prompts
|
# prompts
|
||||||
if self.is_current_line_prompt():
|
if self.is_current_line_prompt():
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Progress filter: prompt detected", debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -284,10 +305,61 @@ class command:
|
|||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 6: Claude Code progress indicators
|
# Pattern 6: Claude Code working indicators (various symbols + activity text + "esc/ctrl+c to interrupt")
|
||||||
claude_progress_match = re.search(r'[·✶✢✻✽*].*?\(esc to interrupt[^)]*\)', text)
|
# Matches any: [symbol] [Task description]… (... to interrupt ...)
|
||||||
|
# Symbols include: * ✢ ✽ ✶ ✻ · • ◦ ○ ● ◆ and similar decorative characters
|
||||||
|
# Example: ✽ Reviewing script for issues… (ctrl+c to interrupt · 33s · ↑ 1.6k tokens · thought for 4s)
|
||||||
|
claude_progress_match = re.search(
|
||||||
|
r'[*✢✽✶✻·•◦○●◆]\s+\w+.*?…\s*\(.*(?:esc|ctrl\+c) to interrupt.*\)',
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
if claude_progress_match:
|
if claude_progress_match:
|
||||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing Claude Code activity beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
|
self.play_activity_beep()
|
||||||
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pattern 6b: Claude Code tool invocation indicators (● Tool Name(...))
|
||||||
|
# Example: ● Web Search("query here")
|
||||||
|
tool_invocation_match = re.search(
|
||||||
|
r'[●○◉•◦]\s+(?:Web\s*Search|Read|Write|Edit|Bash|Glob|Grep|Task|WebFetch)\s*\(',
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if tool_invocation_match:
|
||||||
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing Claude Code tool invocation beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
|
self.play_activity_beep()
|
||||||
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pattern 6c: Bullet/white bullet activity lines (•/◦ ...)
|
||||||
|
bullet_activity_match = re.search(
|
||||||
|
(
|
||||||
|
r'^\s*[•◦]\s+.*(?:…|\.{3,}|\b(?:thinking|working|processing|'
|
||||||
|
r'analyzing|searching|reading|writing|planning|running|'
|
||||||
|
r'executing|updating|building|installing|compiling|downloading|'
|
||||||
|
r'reviewing|generating|responding|applying|fixing|editing|'
|
||||||
|
r'creating|preparing|checking|opening|loading|fetching|'
|
||||||
|
r'retrieving|scanning|indexing|summarizing)\b)'
|
||||||
|
),
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if bullet_activity_match:
|
||||||
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing bullet activity beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
self.play_activity_beep()
|
self.play_activity_beep()
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
@@ -327,6 +399,23 @@ class command:
|
|||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Pattern 10: Task status indicators (● Task Output, ○ Task Running, etc.)
|
||||||
|
# Matches bullet points with task-related status text
|
||||||
|
task_status_match = re.search(
|
||||||
|
r'[●○◉]\s+(?:Task\s+)?(?:Output|Running|Pending|Working|Processing)\s+[a-zA-Z0-9]+',
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if task_status_match:
|
||||||
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.5:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing task status activity beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
|
self.play_activity_beep()
|
||||||
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
|
return
|
||||||
|
|
||||||
def play_progress_tone(self, percentage):
|
def play_progress_tone(self, percentage):
|
||||||
# Map 0-100% to 400-1200Hz frequency range
|
# Map 0-100% to 400-1200Hz frequency range
|
||||||
frequency = 400 + (percentage * 8)
|
frequency = 400 + (percentage * 8)
|
||||||
|
|||||||
@@ -158,6 +158,16 @@ class command:
|
|||||||
|
|
||||||
def _restore_speech(self):
|
def _restore_speech(self):
|
||||||
"""Helper method to restore speech when prompt is detected"""
|
"""Helper method to restore speech when prompt is detected"""
|
||||||
|
# If speech is already enabled, just clear flags to avoid unnecessary
|
||||||
|
# interrupts on prompt return
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "enabled"
|
||||||
|
):
|
||||||
|
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
||||||
|
if "enableSpeechOnKeypress" in self.env["commandBuffer"]:
|
||||||
|
self.env["commandBuffer"]["enableSpeechOnKeypress"] = False
|
||||||
|
return
|
||||||
|
|
||||||
# Disable silence mode
|
# Disable silence mode
|
||||||
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
||||||
# Also disable the keypress-based speech restoration since we're
|
# Also disable the keypress-based speech restoration since we're
|
||||||
|
|||||||
@@ -4,13 +4,18 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
import difflib
|
||||||
import time
|
import time
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
class command:
|
class command:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self._update_times = []
|
||||||
|
self._line_count_times = []
|
||||||
|
self._batched_text = []
|
||||||
|
self._last_flush_time = 0
|
||||||
|
self._in_flood_mode = False
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
@@ -21,6 +26,73 @@ class command:
|
|||||||
def get_description(self):
|
def get_description(self):
|
||||||
return _("Announces incoming text changes")
|
return _("Announces incoming text changes")
|
||||||
|
|
||||||
|
def _reset_flood_state(self):
|
||||||
|
self._update_times = []
|
||||||
|
self._line_count_times = []
|
||||||
|
self._batched_text = []
|
||||||
|
self._last_flush_time = 0
|
||||||
|
self._in_flood_mode = False
|
||||||
|
|
||||||
|
def _is_rapid_updates(self):
|
||||||
|
current_time = time.time()
|
||||||
|
window = self.env["runtime"]["SettingsManager"].get_setting_as_float(
|
||||||
|
"speech", "rapid_update_window"
|
||||||
|
)
|
||||||
|
threshold = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
|
"speech", "rapid_update_threshold"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._update_times = [
|
||||||
|
ts for ts in self._update_times if current_time - ts < window
|
||||||
|
]
|
||||||
|
self._update_times.append(current_time)
|
||||||
|
|
||||||
|
return len(self._update_times) >= threshold
|
||||||
|
|
||||||
|
def _is_high_volume(self, delta_text):
|
||||||
|
current_time = time.time()
|
||||||
|
window = self.env["runtime"]["SettingsManager"].get_setting_as_float(
|
||||||
|
"speech", "rapid_update_window"
|
||||||
|
)
|
||||||
|
threshold = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
|
"speech", "flood_line_threshold"
|
||||||
|
)
|
||||||
|
|
||||||
|
line_count = max(1, delta_text.count("\n") + 1)
|
||||||
|
self._line_count_times = [
|
||||||
|
(ts, count)
|
||||||
|
for ts, count in self._line_count_times
|
||||||
|
if current_time - ts < window
|
||||||
|
]
|
||||||
|
self._line_count_times.append((current_time, line_count))
|
||||||
|
|
||||||
|
total_lines = sum(count for _, count in self._line_count_times)
|
||||||
|
return total_lines >= threshold
|
||||||
|
|
||||||
|
def _add_to_batch(self, text):
|
||||||
|
new_lines = text.splitlines()
|
||||||
|
if text.endswith("\n"):
|
||||||
|
new_lines.append("")
|
||||||
|
self._batched_text.extend(new_lines)
|
||||||
|
|
||||||
|
max_lines = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
|
"speech", "max_batch_lines"
|
||||||
|
)
|
||||||
|
if len(self._batched_text) > max_lines:
|
||||||
|
self._batched_text = self._batched_text[-max_lines:]
|
||||||
|
|
||||||
|
def _flush_batch(self):
|
||||||
|
if not self._batched_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = "\n".join(self._batched_text)
|
||||||
|
self._batched_text = []
|
||||||
|
self._last_flush_time = time.time()
|
||||||
|
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
text, interrupt=False, flush=False
|
||||||
|
)
|
||||||
|
|
||||||
def _was_handled_by_tab_completion(self, delta_text):
|
def _was_handled_by_tab_completion(self, delta_text):
|
||||||
"""Check if this delta was already handled by tab completion to avoid duplicates"""
|
"""Check if this delta was already handled by tab completion to avoid duplicates"""
|
||||||
if "tabCompletion" not in self.env["commandBuffer"]:
|
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||||
@@ -40,9 +112,74 @@ class command:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _normalize_line(self, line):
|
||||||
|
return " ".join(line.split())
|
||||||
|
|
||||||
|
def _is_subsequence(self, subset_lines, source_lines):
|
||||||
|
subset_index = 0
|
||||||
|
for source_line in source_lines:
|
||||||
|
if source_line == subset_lines[subset_index]:
|
||||||
|
subset_index += 1
|
||||||
|
if subset_index == len(subset_lines):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _prefer_inserted_lower_screen_text(self, delta_text):
|
||||||
|
delta_lines = [
|
||||||
|
line for line in delta_text.splitlines() if line.strip() != ""
|
||||||
|
]
|
||||||
|
if len(delta_lines) < 2:
|
||||||
|
return delta_text
|
||||||
|
|
||||||
|
old_lines = self.env["screen"]["old_content_text"].split("\n")
|
||||||
|
new_lines = self.env["screen"]["new_content_text"].split("\n")
|
||||||
|
if len(old_lines) != len(new_lines) or len(new_lines) < 4:
|
||||||
|
return delta_text
|
||||||
|
|
||||||
|
normalized_old_lines = [
|
||||||
|
self._normalize_line(line) for line in old_lines
|
||||||
|
]
|
||||||
|
normalized_new_lines = [
|
||||||
|
self._normalize_line(line) for line in new_lines
|
||||||
|
]
|
||||||
|
matcher = difflib.SequenceMatcher(
|
||||||
|
None, normalized_old_lines, normalized_new_lines, autojunk=False
|
||||||
|
)
|
||||||
|
|
||||||
|
top_lines_changed = False
|
||||||
|
inserted_lines = []
|
||||||
|
lower_screen_start = max(2, len(new_lines) // 2)
|
||||||
|
|
||||||
|
for tag, old_start, old_end, new_start, new_end in matcher.get_opcodes():
|
||||||
|
if tag in {"replace", "delete"} and old_start < 2:
|
||||||
|
top_lines_changed = True
|
||||||
|
if tag != "insert" or new_start < lower_screen_start:
|
||||||
|
continue
|
||||||
|
inserted_lines.extend(
|
||||||
|
line for line in new_lines[new_start:new_end] if line.strip() != ""
|
||||||
|
)
|
||||||
|
|
||||||
|
if not top_lines_changed or not inserted_lines:
|
||||||
|
return delta_text
|
||||||
|
|
||||||
|
normalized_delta_lines = [
|
||||||
|
self._normalize_line(line) for line in delta_lines
|
||||||
|
]
|
||||||
|
normalized_inserted_lines = [
|
||||||
|
self._normalize_line(line) for line in inserted_lines
|
||||||
|
]
|
||||||
|
if not self._is_subsequence(
|
||||||
|
normalized_inserted_lines, normalized_delta_lines
|
||||||
|
):
|
||||||
|
return delta_text
|
||||||
|
if normalized_delta_lines == normalized_inserted_lines:
|
||||||
|
return delta_text
|
||||||
|
|
||||||
|
return "\n".join(inserted_lines)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"speech", "autoReadIncoming"
|
"speech", "auto_read_incoming"
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# is there something to read?
|
# is there something to read?
|
||||||
@@ -51,6 +188,9 @@ class command:
|
|||||||
|
|
||||||
delta_text = self.env["screen"]["new_delta"]
|
delta_text = self.env["screen"]["new_delta"]
|
||||||
|
|
||||||
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
|
self._reset_flood_state()
|
||||||
|
|
||||||
# Skip if tab completion already handled this delta
|
# Skip if tab completion already handled this delta
|
||||||
if self._was_handled_by_tab_completion(delta_text):
|
if self._was_handled_by_tab_completion(delta_text):
|
||||||
return
|
return
|
||||||
@@ -71,7 +211,31 @@ class command:
|
|||||||
# <= 2:
|
# <= 2:
|
||||||
if "\n" not in delta_text:
|
if "\n" not in delta_text:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
rapid = self._is_rapid_updates()
|
||||||
|
high_volume = self._is_high_volume(delta_text)
|
||||||
|
|
||||||
|
if (rapid and high_volume) or self._in_flood_mode:
|
||||||
|
if not self._in_flood_mode:
|
||||||
|
self._last_flush_time = time.time()
|
||||||
|
self._in_flood_mode = True
|
||||||
|
|
||||||
|
self._add_to_batch(delta_text)
|
||||||
|
|
||||||
|
interval = self.env["runtime"][
|
||||||
|
"SettingsManager"
|
||||||
|
].get_setting_as_float("speech", "batch_flush_interval")
|
||||||
|
if time.time() - self._last_flush_time >= interval:
|
||||||
|
self._flush_batch()
|
||||||
|
|
||||||
|
if not rapid or not high_volume:
|
||||||
|
if self._batched_text:
|
||||||
|
self._flush_batch()
|
||||||
|
self._in_flood_mode = False
|
||||||
|
return
|
||||||
|
|
||||||
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
||||||
|
delta_text = self._prefer_inserted_lower_screen_text(delta_text)
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
delta_text, interrupt=False, flush=False
|
delta_text, interrupt=False, flush=False
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Fenrir TTY screen reader
|
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
|
||||||
|
|
||||||
|
|
||||||
class command:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return "No Description found"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
|
||||||
"promote", "enabled"
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
self.env["runtime"]["SettingsManager"]
|
|
||||||
.get_setting("promote", "list")
|
|
||||||
.strip(" \t\n")
|
|
||||||
== ""
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if int(time.time() - self.env["input"]["lastInputTime"]) < self.env[
|
|
||||||
"runtime"
|
|
||||||
]["SettingsManager"].get_setting_as_int(
|
|
||||||
"promote", "inactiveTimeoutSec"
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
len(
|
|
||||||
self.env["runtime"]["SettingsManager"].get_setting(
|
|
||||||
"promote", "list"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
== 0
|
|
||||||
):
|
|
||||||
return
|
|
||||||
for promote in (
|
|
||||||
self.env["runtime"]["SettingsManager"]
|
|
||||||
.get_setting("promote", "list")
|
|
||||||
.split(",")
|
|
||||||
):
|
|
||||||
if promote in self.env["screen"]["new_delta"]:
|
|
||||||
self.env["runtime"]["OutputManager"].play_sound_icon(
|
|
||||||
"PromotedText"
|
|
||||||
)
|
|
||||||
self.env["input"]["lastInputTime"] = time.time()
|
|
||||||
return
|
|
||||||
|
|
||||||
def set_callback(self, callback):
|
|
||||||
pass
|
|
||||||
@@ -134,7 +134,7 @@ class config_command:
|
|||||||
|
|
||||||
self.config.add_section("keyboard")
|
self.config.add_section("keyboard")
|
||||||
self.config.set("keyboard", "driver", "evdevDriver")
|
self.config.set("keyboard", "driver", "evdevDriver")
|
||||||
self.config.set("keyboard", "keyboardLayout", "desktop")
|
self.config.set("keyboard", "keyboard_layout", "desktop")
|
||||||
|
|
||||||
self.config.add_section("screen")
|
self.config.add_section("screen")
|
||||||
self.config.set("screen", "driver", "vcsaDriver")
|
self.config.set("screen", "driver", "vcsaDriver")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set punctuation to All (every punctuation mark)"
|
return "Set punctuation to All (every punctuation mark)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_level = self.get_setting("general", "punctuationLevel", "some")
|
current_level = self.get_setting("general", "punctuation_level", "some")
|
||||||
|
|
||||||
if current_level.lower() == "all":
|
if current_level.lower() == "all":
|
||||||
self.present_text("Punctuation level already set to All")
|
self.present_text("Punctuation level already set to All")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("general", "punctuationLevel", "all")
|
success = self.set_setting("general", "punctuation_level", "all")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Punctuation level set to All - every punctuation mark will be spoken")
|
self.present_text("Punctuation level set to All - every punctuation mark will be spoken")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set punctuation to Most (detailed punctuation)"
|
return "Set punctuation to Most (detailed punctuation)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_level = self.get_setting("general", "punctuationLevel", "some")
|
current_level = self.get_setting("general", "punctuation_level", "some")
|
||||||
|
|
||||||
if current_level.lower() == "most":
|
if current_level.lower() == "most":
|
||||||
self.present_text("Punctuation level already set to Most")
|
self.present_text("Punctuation level already set to Most")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("general", "punctuationLevel", "most")
|
success = self.set_setting("general", "punctuation_level", "most")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Punctuation level set to Most - detailed punctuation will be spoken")
|
self.present_text("Punctuation level set to Most - detailed punctuation will be spoken")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set punctuation to None (no punctuation spoken)"
|
return "Set punctuation to None (no punctuation spoken)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_level = self.get_setting("general", "punctuationLevel", "some")
|
current_level = self.get_setting("general", "punctuation_level", "some")
|
||||||
|
|
||||||
if current_level.lower() == "none":
|
if current_level.lower() == "none":
|
||||||
self.present_text("Punctuation level already set to None")
|
self.present_text("Punctuation level already set to None")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("general", "punctuationLevel", "none")
|
success = self.set_setting("general", "punctuation_level", "none")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Punctuation level set to None - no punctuation will be spoken")
|
self.present_text("Punctuation level set to None - no punctuation will be spoken")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set punctuation to Some (basic punctuation only)"
|
return "Set punctuation to Some (basic punctuation only)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_level = self.get_setting("general", "punctuationLevel", "some")
|
current_level = self.get_setting("general", "punctuation_level", "some")
|
||||||
|
|
||||||
if current_level.lower() == "some":
|
if current_level.lower() == "some":
|
||||||
self.present_text("Punctuation level already set to Some")
|
self.present_text("Punctuation level already set to Some")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("general", "punctuationLevel", "some")
|
success = self.set_setting("general", "punctuation_level", "some")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Punctuation level set to Some - basic punctuation will be spoken")
|
self.present_text("Punctuation level set to Some - basic punctuation will be spoken")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set character echo to Always (echo all typed characters)"
|
return "Set character echo to Always (echo all typed characters)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
current_mode = self.get_setting("keyboard", "char_echo_mode", "1")
|
||||||
|
|
||||||
if current_mode == "1":
|
if current_mode == "1":
|
||||||
self.present_text("Character echo already set to Always")
|
self.present_text("Character echo already set to Always")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("keyboard", "charEchoMode", "1")
|
success = self.set_setting("keyboard", "char_echo_mode", "1")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Character echo set to Always - all typed characters will be spoken")
|
self.present_text("Character echo set to Always - all typed characters will be spoken")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set character echo to Caps Lock (echo only when caps lock is on)"
|
return "Set character echo to Caps Lock (echo only when caps lock is on)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
current_mode = self.get_setting("keyboard", "char_echo_mode", "1")
|
||||||
|
|
||||||
if current_mode == "2":
|
if current_mode == "2":
|
||||||
self.present_text("Character echo already set to Caps Lock mode")
|
self.present_text("Character echo already set to Caps Lock mode")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("keyboard", "charEchoMode", "2")
|
success = self.set_setting("keyboard", "char_echo_mode", "2")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Character echo set to Caps Lock mode - characters will be spoken only when caps lock is on")
|
self.present_text("Character echo set to Caps Lock mode - characters will be spoken only when caps lock is on")
|
||||||
|
|||||||
+2
-2
@@ -21,13 +21,13 @@ class command(config_command):
|
|||||||
return "Set character echo to None (no character echo)"
|
return "Set character echo to None (no character echo)"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
current_mode = self.get_setting("keyboard", "char_echo_mode", "1")
|
||||||
|
|
||||||
if current_mode == "0":
|
if current_mode == "0":
|
||||||
self.present_text("Character echo already set to None")
|
self.present_text("Character echo already set to None")
|
||||||
return
|
return
|
||||||
|
|
||||||
success = self.set_setting("keyboard", "charEchoMode", "0")
|
success = self.set_setting("keyboard", "char_echo_mode", "0")
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.present_text("Character echo set to None - no typed characters will be spoken")
|
self.present_text("Character echo set to None - no typed characters will be spoken")
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ class command(config_command):
|
|||||||
return "Toggle exclusive keyboard access"
|
return "Toggle exclusive keyboard access"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_state = self.getBooleanSetting("keyboard", "grabDevices", True)
|
current_state = self.getBooleanSetting("keyboard", "grab_devices", True)
|
||||||
new_state = self.toggleBooleanSetting("keyboard", "grabDevices")
|
new_state = self.toggleBooleanSetting("keyboard", "grab_devices")
|
||||||
|
|
||||||
if new_state != current_state:
|
if new_state != current_state:
|
||||||
state_text = "enabled" if new_state else "disabled"
|
state_text = "enabled" if new_state else "disabled"
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ class command(config_command):
|
|||||||
return "Toggle word echo when pressing space"
|
return "Toggle word echo when pressing space"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
current_state = self.getBooleanSetting("keyboard", "wordEcho", False)
|
current_state = self.getBooleanSetting("keyboard", "word_echo", False)
|
||||||
new_state = self.toggleBooleanSetting("keyboard", "wordEcho")
|
new_state = self.toggleBooleanSetting("keyboard", "word_echo")
|
||||||
|
|
||||||
if new_state != current_state:
|
if new_state != current_state:
|
||||||
state_text = "enabled" if new_state else "disabled"
|
state_text = "enabled" if new_state else "disabled"
|
||||||
|
|||||||
+7
-7
@@ -108,7 +108,7 @@ class command(config_command):
|
|||||||
"rate": "0.5",
|
"rate": "0.5",
|
||||||
"pitch": "0.5",
|
"pitch": "0.5",
|
||||||
"volume": "1.0",
|
"volume": "1.0",
|
||||||
"autoReadIncoming": "True",
|
"auto_read_incoming": "True",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Basic sound defaults
|
# Basic sound defaults
|
||||||
@@ -123,10 +123,10 @@ class command(config_command):
|
|||||||
self.config["keyboard"] = {
|
self.config["keyboard"] = {
|
||||||
"driver": "evdevDriver",
|
"driver": "evdevDriver",
|
||||||
"device": "ALL",
|
"device": "ALL",
|
||||||
"keyboardLayout": "desktop",
|
"keyboard_layout": "desktop",
|
||||||
"charEchoMode": "1",
|
"char_echo_mode": "1",
|
||||||
"wordEcho": "False",
|
"word_echo": "False",
|
||||||
"charDeleteEcho": "True",
|
"char_delete_echo": "True",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Basic screen defaults
|
# Basic screen defaults
|
||||||
@@ -137,9 +137,9 @@ class command(config_command):
|
|||||||
|
|
||||||
# Basic general defaults
|
# Basic general defaults
|
||||||
self.config["general"] = {
|
self.config["general"] = {
|
||||||
"punctuationLevel": "some",
|
"punctuation_level": "some",
|
||||||
"debug_level": "0",
|
"debug_level": "0",
|
||||||
"numberOfClipboards": "50",
|
"number_of_clipboards": "50",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Write the configuration
|
# Write the configuration
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class command(config_command):
|
|||||||
self.present_text(f"Current screen driver: {current_description}")
|
self.present_text(f"Current screen driver: {current_description}")
|
||||||
|
|
||||||
# Cycle through the available drivers
|
# Cycle through the available drivers
|
||||||
drivers = ["vcsaDriver", "ptyDriver", "dummyDriver", "debugDriver"]
|
drivers = ["vcsaDriver", "ptyDriver", "dummyDriver"]
|
||||||
try:
|
try:
|
||||||
current_index = drivers.index(current_driver)
|
current_index = drivers.index(current_driver)
|
||||||
next_index = (current_index + 1) % len(drivers)
|
next_index = (current_index + 1) % len(drivers)
|
||||||
|
|||||||
@@ -29,12 +29,7 @@ class command:
|
|||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
"Okay, loading the information about Nano.", interrupt=True
|
"Okay, loading the information about Nano.", interrupt=True
|
||||||
)
|
)
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
self.env["runtime"]["InputManager"].send_keys(self.key_macro)
|
self.env["runtime"]["InputManager"].send_keys(self.key_macro)
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
|
||||||
"BYTE"
|
|
||||||
]:
|
|
||||||
self.env["runtime"]["ByteManager"].send_bytes(self.byteMakro)
|
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -29,12 +29,7 @@ class command:
|
|||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
"Okay, you will now be asked to save your work.", interrupt=True
|
"Okay, you will now be asked to save your work.", interrupt=True
|
||||||
)
|
)
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
self.env["runtime"]["InputManager"].send_keys(self.key_macro)
|
self.env["runtime"]["InputManager"].send_keys(self.key_macro)
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
|
||||||
"BYTE"
|
|
||||||
]:
|
|
||||||
self.env["runtime"]["ByteManager"].send_bytes(self.byteMakro)
|
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ class command:
|
|||||||
# self.key_macro = [[1,'KEY_LEFTCTRL'],[1,'KEY_O'],[0.05,'SLEEP'],[0,'KEY_O'],[0,'KEY_LEFTCTRL']]
|
# self.key_macro = [[1,'KEY_LEFTCTRL'],[1,'KEY_O'],[0.05,'SLEEP'],[0,'KEY_O'],[0,'KEY_LEFTCTRL']]
|
||||||
# self.key_macro = [[1,'KEY_LEFTSHIFT'],[1,'KEY_LEFTCTRL'],[1,'KEY_N'],[0.05,'SLEEP'],[0,'KEY_N'],[0,'KEY_LEFTCTRL'],[0,'KEY_LEFTSHIFT']]
|
# self.key_macro = [[1,'KEY_LEFTSHIFT'],[1,'KEY_LEFTCTRL'],[1,'KEY_N'],[0.05,'SLEEP'],[0,'KEY_N'],[0,'KEY_LEFTCTRL'],[0,'KEY_LEFTSHIFT']]
|
||||||
self.key_macro = []
|
self.key_macro = []
|
||||||
self.byteMakro = []
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
pass
|
pass
|
||||||
@@ -27,12 +26,7 @@ class command:
|
|||||||
return "No description found"
|
return "No description found"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
self.env["runtime"]["InputManager"].send_keys(self.key_macro)
|
self.env["runtime"]["InputManager"].send_keys(self.key_macro)
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
|
||||||
"BYTE"
|
|
||||||
]:
|
|
||||||
self.env["runtime"]["ByteManager"].send_bytes(self.byteMakro)
|
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Fenrir TTY screen reader
|
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from fenrirscreenreader.core import debug
|
|
||||||
from fenrirscreenreader.core.eventData import FenrirEventType
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
currentdir = os.path.dirname(
|
|
||||||
os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
|
||||||
)
|
|
||||||
fenrir_path = os.path.dirname(currentdir)
|
|
||||||
|
|
||||||
|
|
||||||
class ByteManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.switchCtrlModeOnce = 0
|
|
||||||
self.controlMode = True
|
|
||||||
self.repeat = 1
|
|
||||||
self.lastInputTime = time.time()
|
|
||||||
self.lastByteKey = b""
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def unify_escape_seq(self, escapeSequence):
|
|
||||||
converted_escape_sequence = escapeSequence
|
|
||||||
if converted_escape_sequence[0] == 27:
|
|
||||||
converted_escape_sequence = b"^[" + converted_escape_sequence[1:]
|
|
||||||
if len(converted_escape_sequence) > 1:
|
|
||||||
if (
|
|
||||||
converted_escape_sequence[0] == 94
|
|
||||||
and converted_escape_sequence[1] == 91
|
|
||||||
):
|
|
||||||
converted_escape_sequence = (
|
|
||||||
b"^[" + converted_escape_sequence[2:]
|
|
||||||
)
|
|
||||||
return converted_escape_sequence
|
|
||||||
|
|
||||||
def handle_byte_stream(self, event_data, sep=b"\x1b"):
|
|
||||||
buffer = event_data
|
|
||||||
# handle prefix
|
|
||||||
end_index = buffer.find(sep)
|
|
||||||
if end_index > 0:
|
|
||||||
curr_sequence = buffer[:end_index]
|
|
||||||
buffer = buffer[end_index:]
|
|
||||||
self.handle_single_byte_sequence(curr_sequence)
|
|
||||||
# special handlig for none found (performance)
|
|
||||||
elif end_index == -1:
|
|
||||||
self.handle_single_byte_sequence(buffer)
|
|
||||||
return
|
|
||||||
# handle outstanding sequence
|
|
||||||
while buffer != b"":
|
|
||||||
end_index = buffer[len(sep) :].find(sep)
|
|
||||||
if end_index == -1:
|
|
||||||
curr_sequence = buffer
|
|
||||||
buffer = b""
|
|
||||||
else:
|
|
||||||
curr_sequence = buffer[: end_index + len(sep)]
|
|
||||||
buffer = buffer[end_index + len(sep) :]
|
|
||||||
self.handle_single_byte_sequence(curr_sequence)
|
|
||||||
|
|
||||||
def handle_byte_input(self, event_data):
|
|
||||||
if not event_data:
|
|
||||||
return
|
|
||||||
if event_data == b"":
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
|
||||||
"handle_byte_input " + event_data.decode("utf8"),
|
|
||||||
debug.DebugLevel.INFO,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
|
||||||
"ByteManager handle_byte_input: Error decoding byte data: "
|
|
||||||
+ str(e),
|
|
||||||
debug.DebugLevel.ERROR,
|
|
||||||
)
|
|
||||||
self.handle_byte_stream(event_data)
|
|
||||||
|
|
||||||
def handle_single_byte_sequence(self, event_data):
|
|
||||||
converted_escape_sequence = self.unify_escape_seq(event_data)
|
|
||||||
|
|
||||||
if self.switchCtrlModeOnce > 0:
|
|
||||||
self.switchCtrlModeOnce -= 1
|
|
||||||
|
|
||||||
is_control_mode = False
|
|
||||||
if (
|
|
||||||
self.controlMode
|
|
||||||
and not self.switchCtrlModeOnce == 1
|
|
||||||
or not self.controlMode
|
|
||||||
):
|
|
||||||
is_control_mode = self.handle_control_mode(event_data)
|
|
||||||
|
|
||||||
is_command = False
|
|
||||||
if (
|
|
||||||
self.controlMode
|
|
||||||
and not self.switchCtrlModeOnce == 1
|
|
||||||
or not self.controlMode
|
|
||||||
and self.switchCtrlModeOnce == 1
|
|
||||||
):
|
|
||||||
if self.lastByteKey == converted_escape_sequence:
|
|
||||||
if time.time() - self.lastInputTime <= self.env["runtime"][
|
|
||||||
"SettingsManager"
|
|
||||||
].get_setting_as_float("keyboard", "doubleTapTimeout"):
|
|
||||||
self.repeat += 1
|
|
||||||
shortcut_data = b""
|
|
||||||
for i in range(self.repeat):
|
|
||||||
shortcut_data = shortcut_data + converted_escape_sequence
|
|
||||||
is_command = self.detect_byte_command(shortcut_data)
|
|
||||||
# fall back to single stroke - do we want this?
|
|
||||||
if not is_command:
|
|
||||||
is_command = self.detect_byte_command(
|
|
||||||
converted_escape_sequence
|
|
||||||
)
|
|
||||||
self.repeat = 1
|
|
||||||
if not (is_command or is_control_mode):
|
|
||||||
self.env["runtime"]["ScreenManager"].inject_text_to_screen(
|
|
||||||
event_data
|
|
||||||
)
|
|
||||||
if not is_command:
|
|
||||||
self.repeat = 1
|
|
||||||
self.lastByteKey = converted_escape_sequence
|
|
||||||
self.lastInputTime = time.time()
|
|
||||||
|
|
||||||
def get_last_byte_key(self):
|
|
||||||
return self.lastByteKey
|
|
||||||
|
|
||||||
def handle_control_mode(self, escapeSequence):
|
|
||||||
converted_escape_sequence = self.unify_escape_seq(escapeSequence)
|
|
||||||
if converted_escape_sequence == b"^[R":
|
|
||||||
self.controlMode = not self.controlMode
|
|
||||||
self.switchCtrlModeOnce = 0
|
|
||||||
if self.controlMode:
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
|
||||||
_("Sticky Mode On"),
|
|
||||||
sound_icon="Accept",
|
|
||||||
interrupt=True,
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
|
||||||
_("Sticky Mode On"),
|
|
||||||
sound_icon="Cancel",
|
|
||||||
interrupt=True,
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
if converted_escape_sequence == b"^[:":
|
|
||||||
self.switchCtrlModeOnce = 2
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
|
||||||
_("bypass"), sound_icon="PTYBypass", interrupt=True, flush=True
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def send_bytes(self, byteMacro):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def detect_byte_command(self, escapeSequence):
|
|
||||||
converted_escape_sequence = self.unify_escape_seq(escapeSequence)
|
|
||||||
command = self.env["runtime"]["InputManager"].get_command_for_shortcut(
|
|
||||||
converted_escape_sequence
|
|
||||||
)
|
|
||||||
if command != "":
|
|
||||||
self.env["runtime"]["EventManager"].put_to_event_queue(
|
|
||||||
FenrirEventType.execute_command, command
|
|
||||||
)
|
|
||||||
command = ""
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def load_byte_shortcuts(
|
|
||||||
self, kb_config_path=fenrir_path + "/../../config/keyboard/pty.conf"
|
|
||||||
):
|
|
||||||
kb_config = open(kb_config_path, "r")
|
|
||||||
while True:
|
|
||||||
line = kb_config.readline()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
line = line.replace("\n", "")
|
|
||||||
if line.replace(" ", "") == "":
|
|
||||||
continue
|
|
||||||
if line.replace(" ", "").startswith("#"):
|
|
||||||
continue
|
|
||||||
if line.count("=") != 1:
|
|
||||||
continue
|
|
||||||
values = line.split("=")
|
|
||||||
clean_shortcut = bytes(values[0], "UTF-8")
|
|
||||||
repeat = 1
|
|
||||||
if len(clean_shortcut) > 2:
|
|
||||||
if chr(clean_shortcut[1]) == ",":
|
|
||||||
try:
|
|
||||||
repeat = int(chr(clean_shortcut[0]))
|
|
||||||
clean_shortcut = clean_shortcut[2:]
|
|
||||||
except Exception as e:
|
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
|
||||||
"ByteManager load_byte_shortcuts: Error parsing repeat count: "
|
|
||||||
+ str(e),
|
|
||||||
debug.DebugLevel.ERROR,
|
|
||||||
)
|
|
||||||
repeat = 1
|
|
||||||
clean_shortcut = clean_shortcut
|
|
||||||
shortcut = b""
|
|
||||||
for i in range(repeat):
|
|
||||||
shortcut += clean_shortcut
|
|
||||||
command_name = values[1].upper()
|
|
||||||
self.env["bindings"][shortcut] = command_name
|
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
|
||||||
"Byte Shortcut: " + str(shortcut) + " command:" + command_name,
|
|
||||||
debug.DebugLevel.INFO,
|
|
||||||
on_any_level=True,
|
|
||||||
)
|
|
||||||
kb_config.close()
|
|
||||||
@@ -21,7 +21,7 @@ class CursorManager:
|
|||||||
Return False if numlock is ON (let keys type numbers)
|
Return False if numlock is ON (let keys type numbers)
|
||||||
"""
|
"""
|
||||||
# Return False if numlock is ON
|
# Return False if numlock is ON
|
||||||
return not self.env["input"]["newNumLock"]
|
return not self.env["input"]["new_num_lock"]
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
pass
|
pass
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user