39 Commits

Author SHA1 Message Date
Storm Dragon 6d4f55ffe5 A couple of socket fixes so root fenrir and user fenrir do not battle. 2026-05-07 23:37:01 -04:00
Storm Dragon 8638bca1d5 Improve socket handling for -x spawned fenrir instances. 2026-05-07 23:24:54 -04:00
Storm Dragon 0273f9b956 Make x support optional. 2026-05-07 12:50:45 -04:00
Storm Dragon 1670c5711b Few fixes with the -x X driver code. 2026-05-07 02:03:36 -04:00
Storm Dragon fed8f24126 Add X11 terminal input mode 2026-05-07 01:45:00 -04:00
Storm Dragon 40120f825c 2026.04.04 merged 2026-04-04 10:08:46 -04:00
Storm Dragon 17dea6b026 Fix IRC incoming announcements and add focus.tui default 2026-03-25 03:24:29 -04:00
Storm Dragon 57c09e0db9 Guard evdev uinput setup 2026-03-04 04:01:19 -05:00
Storm Dragon 4050c32a16 Add speech-first diff review mode with navigation and tests 2026-02-15 15:55:46 -05:00
Storm Dragon 2e10c1c43b Version bump for new release. 2026-01-28 16:41:44 -05:00
Storm Dragon 1e67876883 RC1 for next release. 2026-01-11 23:19:12 -05:00
Storm Dragon 14cf6b6088 Clarify other OS than Linux support. 2026-01-11 23:15:28 -05:00
Storm Dragon 900a027643 Merge branch 'testing' 2026-01-10 23:12:13 -05:00
Storm Dragon 0e50175463 Removed sound for echo switching. 2026-01-10 23:11:53 -05:00
Storm Dragon 7283f04778 Merge branch 'testing' 2026-01-10 23:03:50 -05:00
Storm Dragon 8d3495f74f Character echo settings toggle key added. Keyboard files updated. 2026-01-10 23:03:22 -05:00
Storm Dragon a6cd47dafc latest code. 2026-01-10 21:55:25 -05:00
Storm Dragon 0bb2e52deb Fixed fluttery caps speech shifts. 2026-01-10 20:49:22 -05:00
Storm Dragon b8eb815a86 Merged latest from testing. 2026-01-08 16:16:20 -05:00
Storm Dragon beca468338 Finally! Fixed bug that was causing interruption when prompt comes back. 2026-01-08 12:37:55 -05:00
Storm Dragon a26fe26c8c Log level now set to 0 by default so there's no longer a ton of log files created that aren't normally needed. 2026-01-05 08:32:07 -05:00
Storm Dragon 508fd11610 Redesigned the flood protection for incoming text, should hopefully be much better. 2026-01-04 00:33:06 -05:00
Storm Dragon afe0e71a1d A tiny bug fix in prompt checker. 2026-01-04 00:05:52 -05:00
Storm Dragon 9e8d0b3869 Latest changes. 2025-12-30 04:10:52 -05:00
Storm Dragon d7f86ca0de Setting added to choose caps notification type, beep, pitch, both or none. 2025-12-30 04:09:25 -05:00
Storm Dragon 49a79d2722 Removed promoted text option. It wasn't all that useful, and the new speech restore performs the same thing more affectively. 2025-12-28 19:07:54 -05:00
Storm Dragon 4ab024d115 Mostly progress bar fixes. 2025-12-22 12:51:15 -05:00
Storm Dragon c4ae27a01b More progress bar tweaks. 2025-12-20 06:59:19 -05:00
Storm Dragon 668d39b444 Merged for wider testing. 2025-12-19 12:56:33 -05:00
Storm Dragon 8b25afbf5a More progress bar updates. 2025-12-19 12:55:17 -05:00
Storm Dragon efeb040f75 Spelling error and case fixes. Everything seems to work so far. 2025-12-19 03:46:18 -05:00
Storm Dragon 7a17b36d50 More update work on readme and settings. 2025-12-19 03:07:46 -05:00
Storm Dragon 047a31b4bf Latest changes. 2025-12-19 00:11:04 -05:00
Storm Dragon 096aef9f08 Progress bar detection updates. 2025-12-14 19:05:18 -05:00
Storm Dragon 76472b83b5 Fixed a comment in settings file. 2025-12-10 20:20:56 -05:00
Storm Dragon a52bf624ec Merged changes from testing. WARNING: breaking changes, you will need to update or regenerate your settings file. Use the example provided in config/settings/settings.conf or on arch use the .pacnew as a guide. 2025-12-10 20:12:07 -05:00
Storm Dragon f4e28a246f Second pass on updating settings, missed a few the first go round. 2025-12-10 20:08:01 -05:00
Storm Dragon 560ceb26c9 Major: Convert all settings from camelCase to snake_case for PEP8 compliance (WARNING! BREAKING CHANGES)
Breaking change for v3.0 - users must update their settings.conf file.

  - Converted 45 settings across all sections
  - Updated 524 Python files, tests, and documentation
  - All tests passing, zero regressions
  - Moving forward the philosophy is clean code over backward compatibility
  - Next stable release will be considered a major release
2025-12-10 19:39:45 -05:00
Storm Dragon 61868c94e5 Experimental fix to hopefully let users who rebind their capslock key keep those settings while using Fenrir. 2025-12-10 08:22:17 -05:00
136 changed files with 4557 additions and 1772 deletions
+82 -52
View File
@@ -1,7 +1,6 @@
# Fenrir
A modern, modular, flexible and fast console screen reader.
It should run on any operating system. If you want to help, or write drivers to make it work on other systems, just let me know.
A modern, modular, flexible and fast console screen reader for Linux.
This software is licensed under the LGPL v3.
**Current maintainer:** Storm Dragon
@@ -9,7 +8,7 @@ This software is licensed under the LGPL v3.
## 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
- **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
@@ -24,12 +23,14 @@ This software is licensed under the LGPL v3.
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
## OS Requirements
## Platform Support
- Linux (ptyDriver, vcsaDriver, evdevDriver) - Primary platform with full support
- macOS (ptyDriver) - Limited support
- BSD (ptyDriver) - Limited support
- Windows (ptyDriver) - Limited support
Fenrir is a Linux screen reader. Linux is the only officially supported platform.
**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.
- Linux TTY (`vcsaDriver`, `evdevDriver`) - Full support
- X11 terminal emulators (`-x`, `ptyDriver`, `x11Driver`) - Supported
## Core Requirements
@@ -49,11 +50,8 @@ This software is licensed under the LGPL v3.
- ReadWrite permission:
- /dev/input
- /dev/uinput
2. **ptyDriver** - Terminal emulation input driver (cross-platform)
- python-pyte
3. **atspiDriver** - AT-SPI input driver for desktop environments
- python-pyatspi2
2. **x11Driver** - X11 terminal-scoped input driver for `fenrir -x`
- python-xlib
### Remote Drivers:
1. **unixDriver** - Unix socket remote control (default)
- socat (for command-line interaction)
@@ -70,7 +68,7 @@ This software is licensed under the LGPL v3.
- /dev/tty[1-64]
- /dev/vcsa[1-64]
- read logind DBUS
2. **ptyDriver** - Terminal emulation driver (cross-platform)
2. **ptyDriver** - Terminal emulation screen driver for `fenrir -x`
- python-pyte
@@ -131,8 +129,8 @@ Settings are located in:
By default Fenrir uses:
- **Sound driver**: genericDriver (via sox)
- **Speech driver**: speechdDriver (via speech-dispatcher)
- **Input driver**: evdevDriver (Linux) or ptyDriver (other platforms)
- **Screen driver**: vcsaDriver (Linux TTY) or ptyDriver (terminal emulation)
- **Input driver**: evdevDriver for Linux TTY, x11Driver for `fenrir -x`
- **Screen driver**: vcsaDriver for Linux TTY, ptyDriver for `fenrir -x`
## Audio Configuration
@@ -222,7 +220,7 @@ Fenrir supports two main keyboard layouts:
Configure in `/etc/fenrir/settings/settings.conf`:
```ini
[keyboard]
keyboardLayout=desktop # or 'laptop'
keyboard_layout=desktop # or 'laptop'
```
### First Time Setup
@@ -255,15 +253,16 @@ Enable remote control in `/etc/fenrir/settings/settings.conf`:
enable=True
driver=unixDriver # or tcpDriver
port=22447 # for TCP driver
socketFile= # custom socket path (optional)
enableSettingsRemote=True # allow settings changes
enableCommandRemote=True # allow command execution
socket_file= # custom socket path (optional)
enable_settings_remote=True # allow settings changes
enable_command_remote=True # allow command execution
```
### Remote Drivers
1. **unixDriver** (recommended): Uses Unix domain sockets
- Socket location: `/tmp/fenrirscreenreader-deamon.sock` (TTY mode) or `/tmp/fenrirscreenreader-<pid>.sock`
- 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
- Works with `socat`
@@ -276,6 +275,19 @@ enableCommandRemote=True # allow command execution
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
```bash
# 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
# Change punctuation level (none/some/most/all)
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set general#punctuationLevel=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set general#punctuation_level=all" | 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
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
# Keyboard and input settings
echo "setting set keyboard#charEchoMode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set keyboard#wordEcho=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set keyboard#char_echo_mode=1" | 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)
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
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
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
@@ -403,6 +415,9 @@ setting <action> [parameters]
**Application Commands:**
- `command quitapplication` - Quit Fenrir
**Instance Commands:**
- `ls` / `list` / `command ls` / `command list` - List registered Fenrir instances and their socket paths
#### Available Settings
**Settings Commands:**
@@ -421,7 +436,7 @@ setting <action> [parameters]
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
- `speech#autoReadIncoming=True/False` - Auto-read new text
- `speech#auto_read_incoming=True/False` - Auto-read new text
*Sound Settings:*
- `sound#enabled=True/False` - Enable/disable sound
@@ -430,32 +445,32 @@ setting <action> [parameters]
- `sound#theme=theme_name` - Sound theme
*General Settings:*
- `general#punctuationLevel=none/some/most/all` - Punctuation verbosity
- `general#debugLevel=0-3` - Debug level
- `general#punctuation_level=none/some/most/all` - Punctuation verbosity
- `general#debug_level=0-3` - Debug level
- `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#cursor=True/False` - Follow text cursor
- `focus#highlight=True/False` - Follow text highlighting
*Keyboard Settings:*
- `keyboard#charEchoMode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
- `keyboard#wordEcho=True/False` - Echo complete words
- `keyboard#charDeleteEcho=True/False` - Echo deleted characters
- `keyboard#interruptOnKeyPress=True/False` - Interrupt speech on key press
- `keyboard#char_echo_mode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
- `keyboard#word_echo=True/False` - Echo complete words
- `keyboard#char_delete_echo=True/False` - Echo deleted characters
- `keyboard#interrupt_on_key_press=True/False` - Interrupt speech on key press
*Screen Settings:*
- `screen#ignoreScreen=1,2,3` - TTY screens to ignore
- `screen#autodetectIgnoreScreen=True/False` - Auto-detect screens to ignore
- `screen#screenUpdateDelay=float` - Screen update delay
- `screen#ignore_screen=1,2,3` - TTY screens to ignore
- `screen#autodetect_ignore_screen=True/False` - Auto-detect screens to ignore
- `screen#screen_update_delay=float` - Screen update delay
*Time Settings:*
- `time#enabled=True/False` - Enable time announcements
- `time#presentTime=True/False` - Announce time
- `time#presentDate=True/False` - Announce date changes
- `time#delaySec=seconds` - Announcement interval
- `time#onMinutes=00,30` - Specific minutes to announce
- `time#present_time=True/False` - Announce time
- `time#present_date=True/False` - Announce date changes
- `time#delay_sec=seconds` - Announcement interval
- `time#on_minutes=00,30` - Specific minutes to announce
## Table Navigation
@@ -623,7 +638,7 @@ rsync -av source/ destination/ # File synchronization
### Customization
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)
- **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)
- Socket file permissions are set to write-only (0o222)
- Commands are processed with Fenrir's privileges
- Settings changes can be disabled via `enableSettingsRemote=False`
- Command execution can be disabled via `enableCommandRemote=False`
- Settings changes can be disabled via `enable_settings_remote=False`
- Command execution can be disabled via `enable_command_remote=False`
### Troubleshooting
@@ -688,7 +703,7 @@ send_fenrir_command("setting set speech#rate=0.9")
- Ensure remote driver is enabled in settings
**Commands not working:**
- Verify `enableCommandRemote=True` in settings
- Verify `enable_command_remote=True` in settings
- Check Fenrir debug logs: `/var/log/fenrir.log`
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
@@ -708,9 +723,9 @@ fenrir [OPTIONS]
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings file options
- `-d, --debug` - Enable debug mode
- `-p, --print` - Print debug messages to screen
- `-e, --emulated-pty` - Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)
- `-E, --emulated-evdev` - Use PTY emulation with evdev for input (single instance)
- `-F, --force-all-screens` - Force Fenrir to respond on all screens, ignoring ignoreScreen setting
- `-x, --x11` - Use PTY emulation with X11 keyboard input scoped to the terminal window
- `--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 ignore_screen setting
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
### Examples:
@@ -718,13 +733,16 @@ fenrir [OPTIONS]
# Run in foreground with debug output
sudo fenrir -f -d
# Use PTY emulation for desktop use
sudo fenrir -e
# Use PTY emulation with X11 terminal-scoped keybindings
fenrir -x
# Use X11 mode with an explicit terminal window id
fenrir -x --x11-window-id 0x123456
# Override settings via command line
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
# Force Fenrir to work on all screens (ignore ignoreScreen setting)
# Force Fenrir to work on all screens (ignore ignore_screen setting)
sudo fenrir -F
# Ignore specific screens
@@ -732,6 +750,18 @@ sudo fenrir --ignore-screen 1
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
Translation files are located in the `locale/` directory. To install translations:
+4 -4
View File
@@ -88,11 +88,11 @@ ls -la config/punctuation/default.conf
# Test basic functionality (ask user to run)
sudo ./src/fenrir --help
# Test in emulation mode (safer for desktop environments)
sudo ./src/fenrir -e --version
# Test version output
./src/fenrir --version
# Quick functionality test (3-5 seconds)
sudo timeout 5 ./src/fenrir -e -f || echo "Timeout reached (expected)"
# Quick console functionality test (3-5 seconds, ask user to run on a TTY)
sudo timeout 5 ./src/fenrir -f || echo "Timeout reached (expected)"
```
**Expected Result**: No immediate crashes, basic help/version output works
+10 -12
View File
@@ -50,7 +50,13 @@ def check_dependency(dep: Dependency) -> bool:
dependencyList = [
# Core dependencies
Dependency('FenrirCore', 'core', 'core',
pythonImports=['daemonize', 'enchant', 'pyperclip', 'setproctitle']),
pythonImports=[
'daemonize',
'enchant',
'pyperclip',
'setproctitle',
'xdg'
]),
# Screen drivers
Dependency('DummyScreen', 'screen', 'dummyDriver'),
@@ -58,16 +64,13 @@ dependencyList = [
pythonImports=['dbus'],
devicePaths=['/dev/vcsa']),
Dependency('PTY', 'screen', 'ptyDriver',
pythonImports=['pyte', 'xdg']),
pythonImports=['pyte']),
# Input drivers
Dependency('DummyInput', 'input', 'dummyDriver'),
Dependency('DebugInput', 'input', 'debugDriver'),
Dependency('Evdev', 'input', 'evdevDriver',
pythonImports=['evdev', 'evdev.InputDevice', 'evdev.UInput', 'pyudev']),
Dependency('PTYInput', 'input', 'ptyDriver',
pythonImports=['pyte']),
# Sound drivers
Dependency('DummySound', 'sound', 'dummyDriver'),
Dependency('DebugSound', 'sound', 'debugDriver'),
@@ -82,11 +85,7 @@ dependencyList = [
Dependency('Speechd', 'speech', 'speechdDriver',
pythonImports=['speechd']),
Dependency('GenericSpeech', 'speech', 'genericDriver',
checkCommands=['espeak-ng']),
# Additional dependencies
Dependency('Pexpect', 'core', 'pexpectDriver',
pythonImports=['pexpect'])
checkCommands=['espeak-ng'])
]
defaultModules = {
@@ -94,8 +93,7 @@ defaultModules = {
'VCSA',
'Evdev',
'GenericSpeech',
'GenericSound',
'Pexpect'
'GenericSound'
}
def check_all_dependencies():
+1 -6
View File
@@ -7,10 +7,6 @@ This directory contains keyboard layout files for Fenrir screen reader.
- **desktop.conf** - Desktop layout using numeric keypad (recommended)
- **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
@@ -36,7 +32,7 @@ To change keyboard layout, edit `/etc/fenrir/settings/settings.conf`:
```ini
[keyboard]
keyboardLayout=desktop # or laptop, nvda-desktop, nvda-laptop, pty, pty2
keyboardLayout=desktop # or laptop
```
## Available Key Constants
@@ -582,4 +578,3 @@ BTN_TRIGGER_HAPPY37
BTN_TRIGGER_HAPPY38
BTN_TRIGGER_HAPPY39
BTN_TRIGGER_HAPPY40
+2
View File
@@ -83,6 +83,8 @@ KEY_FENRIR,KEY_CTRL,KEY_P=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_BACKSLASH=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
KEY_FENRIR,KEY_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_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
+2
View File
@@ -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_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
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_Y=toggle_highlight_tracking
#=toggle_barrier
-89
View File
@@ -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
-46
View File
@@ -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
View File
@@ -3,14 +3,13 @@
enabled=True
# 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.
driver=gstreamerDriver
#driver=genericDriver
# Sound themes. These are the pack of sounds used for sound alerts.
# Sound packs may be located at /usr/share/sounds
# For system wide availability, or ~/.local/share/fenrirscreenreader/sounds
# For the current user.
theme=default
# Sound volume controls how loud the sounds for your selected soundpack are.
@@ -24,12 +23,12 @@ volume=0.7
# fenrirFrequence = the frequency to play
# fenrirDuration = the duration of the frequency
# 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
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
progressMonitoring=True
progress_monitoring=True
[speech]
# 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=0.5
# 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=1.0
@@ -65,10 +70,26 @@ volume=1.0
#language=en
# Read new text as it happens?
autoReadIncoming=True
auto_read_incoming=True
# 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
# the following variables are replaced with values
@@ -81,106 +102,149 @@ readNumbersAsDigits = False
# fenrirVolume = is replaced with the current volume
# fenrirPitch = is replaced with the current pitch
# 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
fenrirMinVolume=0
fenrirMaxVolume=200
fenrirMinPitch=0
fenrirMaxPitch=99
fenrirMinRate=80
fenrirMaxRate=450
# min and max values of the TTS system that is used in generic_speech_command
fenrir_min_volume=0
fenrir_max_volume=200
fenrir_min_pitch=0
fenrir_max_pitch=99
fenrir_min_rate=80
fenrir_max_rate=450
[screen]
# Screen driver: vcsaDriver (Linux TTY/virtual console), ptyDriver (terminal emulation)
driver=vcsaDriver
# Text encoding: auto (auto-detect), utf-8, latin1, etc.
encoding=auto
screenUpdateDelay=0.05
ignoreScreen=7
autodetectIgnoreScreen=True
# Delay in seconds between screen updates (lower = more responsive, higher = less CPU)
# Recommended: 0.05 for most systems
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]
driver=evdevDriver
# filter input devices NOMICE, ALL or a DEVICE NAME
device=ALL
# gives Fenrir exclusive access to the keyboard and lets it control keystrokes.
grabDevices=True
ignoreShortcuts=False
grab_devices=True
# 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
keyboardLayout=desktop
ignore_shortcuts=False
keyboard_layout=desktop
# echo chars while typing.
# 0 = None
# 1 = always
# 2 = only while capslock (not compatible with capslock as fenrir key)
charEchoMode=1
char_echo_mode=1
# echo deleted chars
charDeleteEcho=True
char_delete_echo=True
# echo word after pressing space
wordEcho=False
word_echo=False
# 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)
interruptOnKeyPressFilter=
interrupt_on_key_press_filter=
# timeout for double tap in sec
doubleTapTimeout=0.2
double_tap_timeout=0.2
[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
debugLevel=2
# The default is 0, no logging.
debug_level=0
# 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=File
debugFile=
punctuationProfile=default
punctuationLevel=some
respectPunctuationPause=True
debug_mode=File
debug_file=
# Punctuation settings control how punctuation is spoken during text review.
# Profile selects a punctuation definition file from config/punctuation/ (e.g., default.conf)
punctuation_profile=default
punctuation_level=some
respect_punctuation_pause=True
# Replace undefined punctuation with spaces instead of removing them
# This improves readability of text with punctuation like [X]mute, IP addresses, etc.
replaceUndefinedPunctuationWithSpace=True
newLinePause=True
numberOfClipboards=50
replace_undefined_punctuation_with_space=True
# Pause speech briefly at newline characters for better readability
new_line_pause=True
number_of_clipboards=50
# used path for "export_clipboard_to_file"
# $user is replaced by username
#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
# define the current Fenrir key
fenrirKeys=KEY_KP0,KEY_META,KEY_INSERT
scriptKeys=KEY_COMPOSE
timeFormat=%%I:%%M%%P
dateFormat=%%A, %%B %%d, %%Y
autoSpellCheck=True
spellCheckLanguage=en_US
# path for your scripts "scriptKeys" functionality
scriptPath=/usr/share/fenrirscreenreader/scripts
# overload commands, and create new one without changing Fenrir default
commandPath=
#fenrirBGColor = the backgroundcolor
#fenrirFGColor = the foregroundcolor
#fenrirUnderline = speak the underline attribute
#fenrirBold = speak the bold attribute
#fenrirBlink = speak the blink attribute
#fenrirFont = the font
#fenrirFontSize = the fontsize
attributeFormatString=Background fenrirBGColor,Foreground fenrirFGColor,fenrirUnderline,fenrirBold,fenrirBlink, Font fenrirFont,Fontsize fenrirFontSize
# present indentation
autoPresentIndent=False
# speak is only invoked on changeing ident level, sound always
# 0 = sound and speak
# 1 = sound only
# 2 = speak only
autoPresentIndentMode=1
# play a sound when attributes change
hasAttributes=True
# shell for PTY emulatiun (empty = default shell)
# Define the Fenrir modifier key(s) - used to trigger Fenrir commands
# Examples: KEY_KP0 (numpad 0), KEY_META (Super/Windows), KEY_INSERT
# Multiple keys: KEY_KP0,KEY_META,KEY_INSERT
fenrir_keys=KEY_KP0,KEY_META,KEY_INSERT
# Script key - used to execute custom scripts in script_path
script_keys=KEY_COMPOSE
# Time format using Python strftime codes (https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior)
# Default: %%I:%%M%%P (12-hour format, e.g., "06:38pm")
# 24-hour format: %%H:%%M (e.g., "18:38")
# Common codes: %%I=12hr hour, %%H=24hr hour, %%M=minute, %%S=second, %%P=am/pm lowercase, %%p=AM/PM uppercase
time_format=%%I:%%M%%P
# Date format using Python strftime codes
# Default: %%A, %%B %%d, %%Y (e.g., "Tuesday, December 10, 2024")
# ISO format: %%Y-%%m-%%d (e.g., "2024-12-10")
# Common codes: %%A=weekday name, %%B=month name, %%d=day, %%Y=year with century, %%m=month number
date_format=%%A, %%B %%d, %%Y
# Automatically spell check words when reviewing character-by-character
auto_spell_check=True
# Language for spell checking (format: language_COUNTRY, e.g., en_US, en_GB, es_ES)
spell_check_language=en_US
# path for your scripts "script_keys" functionality
script_path=/usr/share/fenrirscreenreader/scripts
# Override default commands or add custom commands without modifying Fenrir installation
# Leave empty to use default commands only
command_path=
# Format string for announcing text attributes (colors, formatting)
# Available variables:
# fenrirBGColor = the background color
# 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=
# 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]
#follow the text cursor
# Follow and announce text cursor position changes
cursor=True
#follow highlighted text changes
# Follow and announce highlighted/selected text changes (useful in menus)
highlight=False
# Suppress generic incoming announcements for full-screen TUIs that redraw often
tui=False
[remote]
enable=True
@@ -191,40 +255,43 @@ driver=unixDriver
# tcp port
port=22447
# socket filepath
socketFile=
socket_file=
# allow settings to overwrite
enableSettingsRemote=True
enable_settings_remote=True
# allow commands to be executed
enableCommandRemote=True
enable_command_remote=True
[barrier]
# Enable barrier detection - automatically detects table/box borders for improved navigation
enabled=False
leftBarriers=│└┌─
rightBarriers=│┘┐
# Characters that represent left-side barriers/borders (for table/box detection)
left_barriers=│└┌
# Characters that represent right-side barriers/borders (for table/box detection)
right_barriers=│┘┐─
[review]
lineBreak=True
endOfScreen=True
# leave the review when pressing a key
leaveReviewOnCursorChange=True
# leave the review when changing the screen
leaveReviewOnScreenChange=True
[promote]
enabled=True
inactiveTimeoutSec=120
list=
# Announce line breaks during review mode
line_break=True
# Announce when reaching the end of screen during review
end_of_screen=True
# Exit review mode when cursor position changes (e.g., when typing)
leave_review_on_cursor_change=True
# Exit review mode when switching to a different TTY/screen
leave_review_on_screen_change=True
[menu]
vmenuPath=
# quickMenu: Semicolon-separated list of settings for quick adjustment
# Custom path for VMenu (virtual menu) profiles
# 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;...
# Supported settings:
# - speech#rate, speech#pitch, speech#volume (0.0-1.0)
# - speech#module, speech#voice (speechdDriver only, auto-added)
# Note: speech#module and speech#voice are automatically added when
# speechdDriver is active. Do not add them manually.
quickMenu=speech#rate;speech#pitch;speech#volume
quick_menu=speech#rate;speech#pitch;speech#volume
[prompt]
# 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 custom prompts ending with specific strings, use patterns like: .*your_prompt_ending$
# 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)
# Format: exactMatches=prompt1,prompt2,prompt3
# Format: exact_matches=prompt1,prompt2,prompt3
# Examples:
# exactMatches=[storm@fenrir ~] $,[root@fenrir ~] #,Continue installation? [Y/n]
exactMatches=
# exact_matches=[storm@fenrir ~] $,[root@fenrir ~] #,Continue installation? [Y/n]
exact_matches=
[time]
# automatic time announcement
# Enable automatic time announcements
enabled=False
# present time
presentTime=True
# present date (on change)
presentDate=True
# present time after a given period of seconds
delaySec=0
# present time after to given minutes example every 15 minutes: 00,15,30,45
# if delaySec is >0 onMinutes is ignored
onMinutes=00,30
# announce via soundicon (not interrupting)
# Announce the current time
present_time=True
# Announce the date (only when it changes, e.g., at midnight)
present_date=True
# Announce time every N seconds (0 = disabled)
# If delay_sec > 0, on_minutes is ignored
delay_sec=0
# Announce time at specific minutes of each hour (comma-separated)
# Examples:
# 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
# interrupt current speech for time announcement
# Interrupt current speech to announce time immediately
interrupt=False
Binary file not shown.
Binary file not shown.
-4
View File
@@ -1,8 +1,6 @@
# Screen Reader Turned On or Off
ScreenReaderOn='ScreenReaderOn.wav'
ScreenReaderOff='ScreenReaderOff.wav'
# PTY bypass
PTYBypass='PTYBypass.wav'
# Cancel the current command
Cancel='Cancel.wav'
# Accept command
@@ -46,8 +44,6 @@ ErrorSpeech='ErrorSpeech.wav'
ErrorScreen='ErrorScreen.wav'
# If you cursor over an text that has attributs (like color)
HasAttributes='has_attribute.wav'
# fenrir can promote strings if they appear on the screen.
PromotedText='PromotedText.wav'
# missspelled indicator
mispell='mispell.wav'
# the for capital letter
-4
View File
@@ -1,8 +1,6 @@
# Screen Reader Turned On or Off
ScreenReaderOn=''
ScreenReaderOff=''
# PTY bypass
PTYBypass=''
# Cancel the current command
Cancel=''
# Accept command
@@ -50,8 +48,6 @@ ErrorBraille=''
ErrorScreen=''
# If you cursor over an text that has attributs (like color)
HasAttributes=''
# fenrir can promote strings if they appear on the screen.
PromotedText=''
# misspelled indicator
mispell=''
# the for capital letter:
+2 -4
View File
@@ -22,7 +22,7 @@ src/fenrirscreenreader/
│ ├── onKeyInput/ # Key input hooks
│ └── help/ # Tutorial system
├── drivers/ # Driver implementations
│ ├── inputDriver/ # Input drivers (evdev, pty, atspi)
│ ├── inputDriver/ # Input drivers (evdev, x11)
│ ├── screenDriver/ # Screen drivers (vcsa, pty)
│ ├── speechDriver/ # Speech drivers (speechd, generic)
│ └── soundDriver/ # Sound drivers (generic, gstreamer)
@@ -36,8 +36,7 @@ Fenrir uses a pluggable driver architecture:
1. **Input Drivers**: Capture keyboard input
- evdevDriver: Linux evdev (recommended)
- ptyDriver: Terminal emulation
- atspiDriver: AT-SPI for desktop
- x11Driver: X11 terminal-scoped input
2. **Screen Drivers**: Read screen content
- vcsaDriver: Linux VCSA devices
@@ -83,7 +82,6 @@ Fenrir supports various event hooks:
- **onCursorChange**: Triggered when cursor moves
- **onScreenUpdate**: Triggered on screen content changes
- **onKeyInput**: Triggered on key presses
- **onByteInput**: Triggered on byte-level input
- **onScreenChanged**: Triggered when switching screens
## Development Setup
+48
View File
@@ -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
View File
@@ -13,13 +13,14 @@ fenrir \- A modern, modular console screen reader for Linux
.IR SECTION#SETTING=VALUE;.. ]
.RB [ \-d ]
.RB [ \-p ]
.RB [ \-e ]
.RB [ \-E ]
.RB [ \-x ]
.RB [ \-\-x11-window-id
.IR WINDOWID ]
.RB [ \-F ]
.SH DESCRIPTION
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.
@@ -53,14 +54,6 @@ Enable debug mode. Debug information will be logged to /var/log/fenrir.log.
.BR \-p ", " \-\-print
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
.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.
@@ -239,9 +232,7 @@ debugDriver - Debug/testing
.IP \[bu] 4
evdevDriver - Linux evdev (recommended for Linux)
.IP \[bu] 4
ptyDriver - Terminal emulation (cross-platform)
.IP \[bu] 4
atspiDriver - AT-SPI for desktop environments
x11Driver - X11 terminal-scoped input for fenrir -x
.TP
.B Screen Drivers:
@@ -267,10 +258,6 @@ Start Fenrir as a daemon with default settings.
.B fenrir -f -d
Run Fenrir in foreground with debug output.
.TP
.B fenrir -e
Run Fenrir with PTY emulation for desktop/terminal use.
.TP
.B fenrir -o "speech#rate=0.8;sound#volume=0.5"
Override speech rate and sound volume settings.
@@ -300,6 +287,19 @@ enableCommandRemote=True
.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
.B Basic Speech Control:
.EX
@@ -355,6 +355,8 @@ command say <text> - Speak the specified text
command interrupt - Stop current speech
.IP \[bu] 2
command tempdisablespeech - Disable speech until next key press
.IP \[bu] 2
ls / list / command ls / command list - List registered Fenrir instances
.TP
.B Settings Commands:
+50 -91
View File
@@ -1095,23 +1095,6 @@ announce=True
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
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+`::
Print debug messages to screen in addition to logging them.
`+-e, --emulated-pty+`::
Use PTY emulation with escape sequences for input. This enables usage in desktop/X11/Wayland environments and terminal emulators.
`+-E, --emulated-evdev+`::
Use PTY emulation with evdev for input (single instance mode).
`+-F, --force-all-screens+`::
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
@@ -1286,14 +1263,27 @@ Enable remote control in settings.conf:
[remote]
enable=True
driver=unixDriver
enableSettingsRemote=True
enableCommandRemote=True
enable_settings_remote=True
enable_command_remote=True
....
==== Using socat with Unix Sockets
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
....
@@ -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
# 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
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Multiple settings at once
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuationLevel=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
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
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 interrupt+` - Stop current speech
* `+command tempdisablespeech+` - Disable speech until next key press
* `+ls+` / `+list+` / `+command ls+` / `+command list+` - List registered Fenrir instances
*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")
*General Settings:*
* `+general#punctuationLevel=none/some/most/all+` - Punctuation verbosity
* `+general#autoSpellCheck=True/False+` - Automatic spell checking
* `+general#punctuation_level=none/some/most/all+` - Punctuation verbosity
* `+general#auto_spell_check=True/False+` - Automatic spell checking
* `+general#emoticons=True/False+` - Enable emoticon replacement
*Sound Settings:*
@@ -1407,11 +1398,11 @@ echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-dea
* `+focus#highlight=True/False+` - Follow text highlighting
*Keyboard Settings:*
* `+keyboard#charEchoMode=0-2+` - Character echo (0=none, 1=always, 2=capslock only)
* `+keyboard#wordEcho=True/False+` - Echo complete words
* `+keyboard#char_echo_mode=0-2+` - Character echo (0=none, 1=always, 2=capslock only)
* `+keyboard#word_echo=True/False+` - Echo complete words
*Screen Settings:*
* `+screen#ignoreScreen=1,2,3+` - TTY screens to ignore
* `+screen#ignore_screen=1,2,3+` - TTY screens to ignore
==== settings.conf syntax
@@ -1511,14 +1502,14 @@ frequencies.
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=<your command for playing a frequence>
generic_frequency_command=<your command for playing a frequence>
....
The following variables are substituted in `+genericPlayFileCommand+`
@@ -1532,13 +1523,13 @@ and `+genericFrequencyCommand+`:
Example genericPlayFileCommand (default)
....
genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
generic_play_file_command=play -q -v fenrirVolume fenrirSoundFile
....
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
@@ -1579,7 +1570,7 @@ Values: Range Minimum:`+0.0+` is lowest, Maximum:`+1.0+` is highest.
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.
@@ -1681,13 +1672,14 @@ the pico module:
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+`
==== 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:
@@ -1711,7 +1703,7 @@ Values:`+cp850+` is used for Western languages like USA or Europe.
The driver updates Fenrir with changes on the screen.
....
screenUpdateDelay=0.05
screen_update_delay=0.05
....
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.
....
grabDevices=True
grab_devices=True
....
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.
....
ignoreShortcuts=False
ignore_shortcuts=False
....
Values: on=`+True+`, off=`+False+`
@@ -1793,7 +1785,7 @@ Values: on=`+True+`, off=`+False+`
The current keyboard layout used for shortcuts.
....
keyboardLayout=desktop
keyboard_layout=desktop
....
Values: An absolute Path to a Keyboard definition file or a Filename
@@ -1810,7 +1802,7 @@ Values: on=`+True+`, off=`+False+`
Announce deleted characters
....
charDeleteEcho=True
char_delete_echo=True
....
Values: on=`+True+`, off=`+False+`
@@ -1818,7 +1810,7 @@ Values: on=`+True+`, off=`+False+`
Announce word after pressing space
....
wordEcho=False
word_echo=False
....
Values: on=`+True+`, off=`+False+`
@@ -1826,7 +1818,7 @@ Values: on=`+True+`, off=`+False+`
Interrupt speech on any keypress
....
interruptOnKeyPress=False
interrupt_on_key_press=False
....
Values: on=`+True+`, off=`+False+`
@@ -1834,7 +1826,7 @@ Values: on=`+True+`, off=`+False+`
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
@@ -1842,7 +1834,7 @@ Values: (List) empty = all keys, otherwise interrupt with specified keys
The timeout that is used for double tap shortcuts
....
doubleTapTimeout=0.2
double_tap_timeout=0.2
....
Values: Seconds
@@ -1862,7 +1854,7 @@ Values: off=0, error=1, warning=2, info=3
the current punctuation and dict file in use:
....
punctuationProfile=default
punctuation_profile=default
....
Values: Text, see available profiles in `+/etc/fenrir/punctuation+` or
@@ -1871,7 +1863,7 @@ in `+sourceTree/config/punctuation+`
The current punctuation level in use:
....
punctuationLevel=some
punctuation_level=some
....
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:
....
respectPunctuationPause=True
respect_punctuation_pause=True
....
Values: on=`+True+`, off=`+False+`
@@ -1887,7 +1879,7 @@ Values: on=`+True+`, off=`+False+`
Add a pause on Line break:
....
newLinePause=True
new_line_pause=True
....
Values: on=`+True+`, off=`+False+`
@@ -1905,7 +1897,7 @@ Values: Text, Systemfilepath
The number of available clipboards:
....
numberOfClipboards=10
number_of_clipboards=10
....
Values: Integer, 1 - 999
@@ -1921,7 +1913,7 @@ Values: on=`+True+`, off=`+False+`
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.
@@ -1955,7 +1947,7 @@ https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior[d
Enable or Disable spellcheck whilst typing:
....
autoSpellCheck=True
auto_spell_check=True
....
Values: on=`+True+`, off=`+False+`
@@ -1963,7 +1955,7 @@ Values: on=`+True+`, off=`+False+`
The use of the dictionary with spellcheck:
....
spellCheckLanguage=en_US
spell_check_language=en_US
....
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):
....
leaveReviewOnScreenChange=True
leave_review_on_screen_change=True
....
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
The automated time announcement is configured in the section `+[time]+`.
+50 -14
View File
@@ -102,17 +102,17 @@ volume=0.7
[keyboard]
driver=evdevDriver
keyboardLayout=desktop
keyboard_layout=desktop
[screen]
driver=vcsaDriver
ignoreScreen=
ignore_screen=
[remote]
enable=True
driver=unixDriver
enableSettingsRemote=True
enableCommandRemote=True
enable_settings_remote=True
enable_command_remote=True
```
## Remote Control
@@ -126,12 +126,24 @@ Enable remote control in settings:
enable=True
driver=unixDriver # or tcpDriver
port=22447 # for TCP driver
enableSettingsRemote=True # allow settings changes
enableCommandRemote=True # allow command execution
enable_settings_remote=True # allow settings changes
enable_command_remote=True # allow command execution
```
### 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
```bash
# 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
# 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
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
# 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
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 interrupt` - Stop speech
- `command tempdisablespeech` - Disable until next key
- `ls` / `list` / `command ls` / `command list` - List registered Fenrir instances
**Settings Commands:**
- `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
**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`
- Voice selection: `setting set speech#voice=en-us+f3`
- Character echo: `setting set keyboard#charEchoMode=1`
- Screen ignore: `setting set screen#ignoreScreen=1,2,3`
- Character echo: `setting set keyboard#char_echo_mode=1`
- Screen ignore: `setting set screen#ignore_screen=1,2,3`
### Scripting Integration
@@ -318,7 +331,7 @@ Fenrir automatically detects and provides audio feedback for progress indicators
### Input Drivers
- **evdevDriver** - Linux evdev (recommended for Linux)
- **ptyDriver** - Terminal emulation (cross-platform)
- **x11Driver** - X11 terminal-scoped input for `fenrir -x`
### Screen Drivers
- **vcsaDriver** - Linux VCSA devices (TTY)
@@ -341,11 +354,33 @@ fenrir [OPTIONS]
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings
- `-d, --debug` - Enable debug mode
- `-p, --print` - Print debug to screen
- `-e, --emulated-pty` - PTY emulation for desktop use
- `-E, --emulated-evdev` - PTY + evdev emulation
- `-x, --x11` - PTY + X11 keyboard input scoped to the terminal window
- `--x11-window-id WINDOWID` - X11 window id to use for `--x11` terminal mode
- `-F, --force-all-screens` - Ignore ignoreScreen setting
- `-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
### No Speech
@@ -379,5 +414,6 @@ sudo fenrir -f -d
## See Also
- [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
- `man fenrir` - Manual page
+32 -57
View File
@@ -729,15 +729,6 @@ Example on fix minutes in an hour. example every quarter "delaySec=0" and "onMin
onMinutes=00,15,30,45
announce=True
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 ====
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.
''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=<your command for playing a frequence>
generic_frequency_command=<your command for playing a frequence>
The following variables are substituted in ''genericPlayFileCommand'' and ''genericFrequencyCommand'':
* ''fenrirVolume'' = the current volume setting
@@ -878,9 +869,9 @@ The following variables are substituted in ''genericPlayFileCommand'' and ''gene
* ''fenrirDuration'' = the duration of the frequency
Example genericPlayFileCommand (default)
genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
generic_play_file_command=play -q -v fenrirVolume fenrirSoundFile
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 is configured in section ''[speech]''.
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.
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.
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.
Read new text as it occurs
autoReadIncoming=True
auto_read_incoming=True
Values: on=''True'', off=''False''
=== Generic Driver ===
@@ -948,16 +939,16 @@ The following variables are substituted in ''genericSpeechCommand'':
* ''fenrirRate'' = is replaced with the current speed (speech rate)
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.
FenrirMinVolume=0
fenrirMaxVolume=200
fenrirMinPitch=0
fenrirMaxPitch=99
fenrirMinRate=80
fenrirMaxRate=450
fenrir_max_volume=200
fenrir_min_pitch=0
fenrir_max_pitch=99
fenrir_min_rate=80
fenrir_max_rate=450
The current volume, pitch and rate is calculated like this
value = min + [volume,pitch,rate] * (min - max )
@@ -1022,7 +1013,8 @@ Values: Integer,
* ''0'' = display size
* ''>0'' number of cells
==== 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:
driver=vcsaDriver
@@ -1033,7 +1025,7 @@ The encoding of the screen
Values:''cp850'' is used for Western languages like USA or Europe.
The driver updates Fenrir with changes on the screen.
screenUpdateDelay=0.05
screen_update_delay=0.05
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:
@@ -1064,15 +1056,15 @@ Values:
* ''<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.
grabDevices=True
grab_devices=True
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.
ignoreShortcuts=False
ignore_shortcuts=False
Values: on=''True'', off=''False''
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''
Announce characters while typing.
@@ -1080,23 +1072,23 @@ Announce characters while typing.
Values: on=''True'', off=''False''
Announce deleted characters
charDeleteEcho=True
char_delete_echo=True
Values: on=''True'', off=''False''
Announce word after pressing space
wordEcho=False
word_echo=False
Values: on=''True'', off=''False''
Interrupt speech on any keypress
interruptOnKeyPress=False
interrupt_on_key_press=False
Values: on=''True'', off=''False''
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
The timeout that is used for double tap shortcuts
doubleTapTimeout=0.2
double_tap_timeout=0.2
Values: Seconds
==== 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
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''
The current punctuation level in use:
punctuationLevel=some
punctuation_level=some
Values: Text, See available levels in the used punctuation file.
Respect pause for punctuations:
respectPunctuationPause=True
respect_punctuation_pause=True
Values: on=''True'', off=''False''
Add a pause on Line break:
newLinePause=True
new_line_pause=True
Values: on=''True'', off=''False''
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
The number of available clipboards:
numberOfClipboards=10
number_of_clipboards=10
Values: Integer, 1 - 999
Replace emoticons like :) or ;) with text insertions:
@@ -1136,7 +1128,7 @@ Replace emoticons like :) or ;) with text insertions:
Values: on=''True'', off=''False''
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.
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]]
Enable or Disable spellcheck whilst typing:
autoSpellCheck=True
auto_spell_check=True
Values: on=''True'', off=''False''
The use of the dictionary with spellcheck:
spellCheckLanguage=en_US
spell_check_language=en_US
Values: Text, see aspell dictionary's.
Folder Path for your scripts "scriptKey" functionality:
@@ -1197,25 +1189,8 @@ Leave the review mode when pressing a key:
Values: on=''True'', off=''False''
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''
==== 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 ====
The automated time announcement is configured in the section ''[time]''.
Time announcement is disabled by default.
-12
View File
@@ -744,7 +744,6 @@ msgid "Script file is not executable"
msgstr "Файл скрипта не исполняемый"
#: ../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
msgid "disables speech until next keypress"
msgstr "Отключить речь пока не нажата следующая клавиша"
@@ -898,7 +897,6 @@ msgid "speech disabled"
msgstr "Речь выключена"
#: ../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
msgid "speech enabled"
msgstr "Речь включена"
@@ -1089,15 +1087,6 @@ msgstr "Мерцание"
msgid "default"
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
msgid "Start Fenrir"
msgstr "fenrir запущен"
@@ -1216,4 +1205,3 @@ msgstr "Меню"
#: ../src/fenrirscreenreader/core/vmenuManager.py:234
msgid "Action"
msgstr "Действие"
-1
View File
@@ -1,7 +1,6 @@
daemonize
dbus-python
evdev
pexpect
pyenchant
pyperclip
pyte
+3 -1
View File
@@ -103,9 +103,11 @@ setup(
"pyudev>=0.21.0",
"setuptools",
"setproctitle",
"pexpect",
"pyte>=0.7.0",
],
extras_require={
"x11": ["python-xlib>=0.33"],
},
)
if not forceSettingsFlag:
+13 -8
View File
@@ -61,14 +61,15 @@ def create_argument_parser():
help='Print debug messages to screen'
)
argumentParser.add_argument(
'-e', '--emulated-pty',
'-x', '--x11',
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(
'-E', '--emulated-evdev',
action='store_true',
help='Use PTY emulation with evdev for input (single instance)'
'--x11-window-id',
metavar='WINDOWID',
default='',
help='X11 window id to use for --x11 terminal mode'
)
argumentParser.add_argument(
'-F',
@@ -91,8 +92,8 @@ def validate_arguments(cliArgs):
if option and ('#' not in option or '=' not in option):
return False, f"Invalid option format: {option}\nExpected format: SECTION#SETTING=VALUE"
if cliArgs.emulated_pty and cliArgs.emulated_evdev:
return False, "Cannot use both --emulated-pty and --emulated-evdev simultaneously"
if cliArgs.x11_window_id and not cliArgs.x11:
return False, "--x11-window-id requires --x11"
return True, None
@@ -124,6 +125,10 @@ def run_fenrir():
pass
def should_run_foreground(cliArgs):
return cliArgs.foreground or cliArgs.x11
def main():
global cliArgs
argumentParser = create_argument_parser()
@@ -135,7 +140,7 @@ def main():
argumentParser.error(errorMsg)
sys.exit(1)
if cliArgs.foreground or cliArgs.emulated_pty:
if should_run_foreground(cliArgs):
# Run directly in foreground
run_fenrir()
else:
@@ -22,7 +22,7 @@ class command:
self.env["runtime"]["MemoryManager"].add_index_list(
"clipboardHistory",
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):
self.spellChecker = enchant.Dict(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
)
self.language = self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
def run(self):
@@ -48,7 +48,7 @@ class command:
return
if (
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_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):
current_layout = self.env["runtime"]["SettingsManager"].get_setting(
"keyboard", "keyboardLayout"
"keyboard", "keyboard_layout"
)
# Extract layout name from full path if needed
@@ -83,7 +83,7 @@ class command:
# Update setting and reload shortcuts
self.env["runtime"]["SettingsManager"].set_setting(
"keyboard", "keyboardLayout", next_layout
"keyboard", "keyboard_layout", next_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):
clipboard_file_path = self.env["runtime"][
"SettingsManager"
].get_setting("general", "clipboardExportPath")
].get_setting("general", "clipboard_export_path")
clipboard_file_path = clipboard_file_path.replace(
"$user", self.env["general"]["curr_user"]
)
@@ -22,7 +22,7 @@ class command:
return _("sends the following keypress to the terminal or application")
def run(self):
self.env["input"]["keyForeward"] = 3
self.env["input"]["key_forward"] = 3
self.env["runtime"]["OutputManager"].present_text(
_("Forward next keypress"), interrupt=True
)
@@ -26,7 +26,7 @@ class command:
def run(self):
clipboard_file_path = self.env["runtime"][
"SettingsManager"
].get_setting("general", "clipboardExportPath")
].get_setting("general", "clipboard_export_path")
clipboard_file_path = clipboard_file_path.replace(
"$user", self.env["general"]["curr_user"]
)
@@ -18,7 +18,7 @@ class command:
self.env["runtime"]["MemoryManager"].add_index_list(
"clipboardHistory",
self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "numberOfClipboards"
"general", "number_of_clipboards"
),
)
@@ -18,17 +18,17 @@ class command:
def initialize(self, environment):
self.env = environment
# 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
# settings
try:
default_enabled = self.env["runtime"][
"SettingsManager"
].get_setting_as_bool("sound", "progressMonitoring")
].get_setting_as_bool("sound", "progress_monitoring")
except Exception as e:
# If setting doesn't exist, default to 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"]["lastProgressValue"] = -1
@@ -40,12 +40,12 @@ class command:
def run(self):
# Check if commandBuffer exists
if "progressMonitoring" not in self.env["commandBuffer"]:
self.env["commandBuffer"]["progressMonitoring"] = False
if "progress_monitoring" not in self.env["commandBuffer"]:
self.env["commandBuffer"]["progress_monitoring"] = False
self.env["commandBuffer"]["lastProgressTime"] = 0
self.env["commandBuffer"]["lastProgressValue"] = -1
if self.env["commandBuffer"]["progressMonitoring"]:
if self.env["commandBuffer"]["progress_monitoring"]:
self.stop_progress_monitoring()
self.env["runtime"]["OutputManager"].present_text(
_("Progress monitoring disabled"), interrupt=True
@@ -57,17 +57,17 @@ class command:
)
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"]["lastProgressValue"] = -1
# Don't control speech - let user decide with silence_until_prompt
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
def detect_progress(self, text):
if not self.env["commandBuffer"]["progressMonitoring"]:
if not self.env["commandBuffer"]["progress_monitoring"]:
return
# Skip progress detection if current screen looks like a prompt
@@ -36,11 +36,11 @@ class command:
def update_spell_language(self):
self.spellChecker = enchant.Dict(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
)
self.language = self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
def run(self):
@@ -51,7 +51,7 @@ class command:
return
if (
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
!= self.language
):
@@ -22,8 +22,8 @@ class command:
return _("Interrupts the current presentation")
def run(self):
if len(self.env["input"]["prevDeepestInput"]) > len(
self.env["input"]["currInput"]
if len(self.env["input"]["prev_deepest_input"]) > len(
self.env["input"]["curr_input"]
):
return
self.env["runtime"]["OutputManager"].interrupt_output()
@@ -36,11 +36,11 @@ class command:
return
self.spellChecker = enchant.Dict(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
)
self.language = self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
def run(self):
@@ -51,7 +51,7 @@ class command:
return
if (
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
!= self.language
):
@@ -25,15 +25,15 @@ class command:
def run(self):
self.env["runtime"]["SettingsManager"].set_setting(
"general",
"autoPresentIndent",
"auto_present_indent",
str(
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoPresentIndent"
"general", "auto_present_indent"
)
),
)
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoPresentIndent"
"general", "auto_present_indent"
):
self.env["runtime"]["OutputManager"].present_text(
_("autoindent enabled"), sound_icon="", interrupt=True
@@ -25,15 +25,15 @@ class command:
def run(self):
self.env["runtime"]["SettingsManager"].set_setting(
"speech",
"autoReadIncoming",
"auto_read_incoming",
str(
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"speech", "autoReadIncoming"
"speech", "auto_read_incoming"
)
),
)
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"speech", "autoReadIncoming"
"speech", "auto_read_incoming"
):
self.env["runtime"]["OutputManager"].present_text(
_("autoread enabled"), sound_icon="", interrupt=True
@@ -24,15 +24,15 @@ class command:
def run(self):
self.env["runtime"]["SettingsManager"].set_setting(
"general",
"autoSpellCheck",
"auto_spell_check",
str(
not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoSpellCheck"
"general", "auto_spell_check"
)
),
)
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoSpellCheck"
"general", "auto_spell_check"
):
self.env["runtime"]["OutputManager"].present_text(
_("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():
self.env["runtime"]["OutputManager"].present_text(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "punctuationLevel"
"general", "punctuation_level"
),
interrupt=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):
# enabled?
active = self.env["runtime"]["SettingsManager"].get_setting_as_int(
"keyboard", "charEchoMode"
"keyboard", "char_echo_mode"
)
# 0 = off
if active == 0:
return
# 2 = caps only
if active == 2:
if not self.env["input"]["newCapsLock"]:
if not self.env["input"]["new_caps_lock"]:
return
# 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
@@ -42,19 +42,10 @@ class command:
)
if x_move > 3:
return
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
if self.env["runtime"][
"InputManager"
].get_last_deepest_input() in [["KEY_TAB"]]:
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
if (
self.env["screen"]["new_cursor"]["x"]
@@ -26,7 +26,7 @@ class command:
def run(self):
# is it enabled?
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"keyboard", "wordEcho"
"keyboard", "word_echo"
):
return
# is navigation?
@@ -39,23 +39,23 @@ class command:
return
self.spellChecker = enchant.Dict(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
)
self.language = self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
def run(self):
if not initialized:
return
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoSpellCheck"
"general", "auto_spell_check"
):
return
if (
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
"general", "spell_check_language"
)
!= self.language
):
@@ -23,7 +23,7 @@ class command:
def run(self):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"keyboard", "charDeleteEcho"
"keyboard", "char_delete_echo"
):
return
# detect typing or chilling
@@ -46,7 +46,7 @@ class command:
# echo word insteed of char
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"keyboard", "wordEcho"
"keyboard", "word_echo"
):
if (
abs(
@@ -71,14 +71,20 @@ class command:
self.env["screen"]["new_cursor"]["y"],
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():
# Only announce spaces during pure navigation (arrow keys)
# Check if this is really navigation by looking at input history
if self.env["runtime"]["InputManager"].get_shortcut_type() in [
"KEY"
] and self.env["runtime"]["InputManager"].get_last_deepest_input()[
0
] in [
last_input = self.env["runtime"][
"InputManager"
].get_last_deepest_input()
if last_input and last_input[0] in [
"KEY_LEFT",
"KEY_RIGHT",
"KEY_UP",
@@ -87,14 +93,14 @@ class command:
char_utils.present_char_for_review(
self.env,
curr_char,
interrupt=True,
interrupt=do_interrupt,
announce_capital=True,
flush=False,
)
else:
self.env["runtime"]["OutputManager"].present_text(
curr_char,
interrupt=True,
interrupt=do_interrupt,
ignore_punctuation=True,
announce_capital=True,
flush=False,
@@ -33,20 +33,11 @@ class command:
current_time = time.time()
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()
in [["KEY_TAB"]]):
tab_detected = True
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)
if not tab_detected:
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() != ""):
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
if curr_delta:
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):
@@ -66,8 +66,15 @@ class command:
):
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(
curr_word, interrupt=True, flush=False
curr_word, interrupt=do_interrupt, flush=False
)
def set_callback(self, callback):
@@ -30,8 +30,8 @@ class command:
if self.env["runtime"]["ScreenManager"].is_screen_change():
self.lastIdent = 0
return
# this leads to problems in vim -> status line change -> no
# announcement, so we do check the lengh as hack
# Don't announce cursor movements when auto-read is handling incoming text
# This prevents interrupting ongoing auto-read announcements
if self.env["runtime"]["ScreenManager"].is_delta():
return
@@ -44,28 +44,34 @@ class command:
self.env["screen"]["new_cursor"]["y"],
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():
self.env["runtime"]["OutputManager"].present_text(
_("blank"), sound_icon="EmptyLine", interrupt=True, flush=False
_("blank"), sound_icon="EmptyLine", interrupt=do_interrupt, flush=False
)
else:
# ident
curr_ident = len(curr_line) - len(curr_line.lstrip())
if self.lastIdent == -1:
self.lastIdent = curr_ident
do_interrupt = True
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoPresentIndent"
"general", "auto_present_indent"
):
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "autoPresentIndentMode"
"general", "auto_present_indent_mode"
) in [0, 1]:
if self.lastIdent != curr_ident:
self.env["runtime"]["OutputManager"].play_frequence(
curr_ident * 50, 0.1, interrupt=do_interrupt
)
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "autoPresentIndentMode"
"general", "auto_present_indent_mode"
) in [0, 2]:
if self.lastIdent != curr_ident:
self.env["runtime"]["OutputManager"].present_text(
@@ -68,10 +68,10 @@ class command:
# Only beep/announce if indentation level has changed
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoPresentIndent"
"general", "auto_present_indent"
) and self.lastIdent != curr_ident:
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "autoPresentIndentMode"
"general", "auto_present_indent_mode"
) in [0, 1]:
self.env["runtime"]["OutputManager"].play_frequence(
curr_ident * 50, 0.1, interrupt=False
@@ -23,7 +23,7 @@ class command:
def run(self):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"review", "leaveReviewOnCursorChange"
"review", "leave_review_on_cursor_change"
):
return
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.
import datetime
import os
import tempfile
import time
from fenrirscreenreader.core.i18n import _
ANNOUNCEMENT_LOCK_TIMEOUT_SEC = 5.0
class command:
def __init__(self):
pass
@@ -26,6 +31,59 @@ class command:
def get_description(self):
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):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"time", "enabled"
@@ -50,6 +108,7 @@ class command:
if delay_sec > 0:
if int((now - self.last_time).total_seconds()) < delay_sec:
return
announcement_slot = f"delay:{int(now.timestamp()) // delay_sec}"
else:
# should announce?
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.minute == self.last_time.minute:
return
announcement_slot = f"minute:{datetime.datetime.strftime(now, '%Y%m%d%H%M')}"
date_format = self.env["runtime"]["SettingsManager"].get_setting(
"general", "date_format"
@@ -78,6 +138,10 @@ class command:
if not (present_date or present_time):
return
if not self._claim_announcement_lock(announcement_slot):
self.last_time = now
return
time_format = self.env["runtime"]["SettingsManager"].get_setting(
"general", "time_format"
)
@@ -22,31 +22,31 @@ class command:
def run(self):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"keyboard", "interruptOnKeyPress"
"keyboard", "interrupt_on_key_press"
):
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"]
if len(self.env["input"]["curr_input"]) <= len(
self.env["input"]["prev_input"]
):
return
# if the filter is set
if (
self.env["runtime"]["SettingsManager"]
.get_setting("keyboard", "interruptOnKeyPressFilter")
.get_setting("keyboard", "interrupt_on_key_press_filter")
.strip()
!= ""
):
filter_list = (
self.env["runtime"]["SettingsManager"]
.get_setting("keyboard", "interruptOnKeyPressFilter")
.get_setting("keyboard", "interrupt_on_key_press_filter")
.split(",")
)
for currInput in self.env["input"]["currInput"]:
if currInput not in filter_list:
for curr_key in self.env["input"]["curr_input"]:
if curr_key not in filter_list:
return
self.env["runtime"]["OutputManager"].interrupt_output()
@@ -24,7 +24,7 @@ class command:
def run(self):
if self.env["runtime"]["InputManager"].no_key_pressed():
return
if len(self.env["input"]["prevInput"]) > 0:
if len(self.env["input"]["prev_input"]) > 0:
return
if not self.env["commandBuffer"]["enableSpeechOnKeypress"]:
return
@@ -23,11 +23,11 @@ class command:
def run(self):
if (
self.env["input"]["oldCapsLock"]
== self.env["input"]["newCapsLock"]
self.env["input"]["old_caps_lock"]
== self.env["input"]["new_caps_lock"]
):
return
if self.env["input"]["newCapsLock"]:
if self.env["input"]["new_caps_lock"]:
self.env["runtime"]["OutputManager"].present_text(
_("Capslock on"), interrupt=True
)
@@ -23,11 +23,11 @@ class command:
def run(self):
if (
self.env["input"]["oldScrollLock"]
== self.env["input"]["newScrollLock"]
self.env["input"]["old_scroll_lock"]
== self.env["input"]["new_scroll_lock"]
):
return
if self.env["input"]["newScrollLock"]:
if self.env["input"]["new_scroll_lock"]:
self.env["runtime"]["OutputManager"].present_text(
_("Scrolllock on"), interrupt=True
)
@@ -22,12 +22,12 @@ class command:
return "No description found"
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
# Only announce numlock changes if an actual numlock key was pressed
# 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:
# 1. KEY_NUMLOCK is in the current input sequence
@@ -40,7 +40,7 @@ class command:
)
if is_genuine_numlock:
if self.env["input"]["newNumLock"]:
if self.env["input"]["new_num_lock"]:
self.env["runtime"]["OutputManager"].present_text(
_("Numlock on"), interrupt=True
)
@@ -23,7 +23,7 @@ class command:
def run(self):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"review", "leaveReviewOnScreenChange"
"review", "leave_review_on_screen_change"
):
return
self.env["runtime"]["CursorManager"].clear_review_cursor()
@@ -41,20 +41,11 @@ class command:
== self.env["runtime"]["ScreenManager"].get_rows() - 1
):
return
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
if not (
self.env["runtime"]["InputManager"].get_last_deepest_input()
in [["KEY_UP"], ["KEY_DOWN"]]
):
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
prev_line = self.env["screen"]["old_content_text"].split("\n")[
@@ -25,8 +25,8 @@ class command:
# Only run if progress monitoring is enabled
try:
if (
"progressMonitoring" in self.env["commandBuffer"]
and self.env["commandBuffer"]["progressMonitoring"]
"progress_monitoring" in self.env["commandBuffer"]
and self.env["commandBuffer"]["progress_monitoring"]
):
# Check if current line is a prompt - if so, reset progress
# state
@@ -40,13 +40,19 @@ class command:
):
self.detect_progress(self.env["screen"]["new_delta"])
except Exception as e:
# Silently ignore errors to avoid disrupting normal operation
pass
# Log errors for debugging instead of silently ignoring
self.env["runtime"]["DebugManager"].write_debug_out(
"Progress detector error: " + str(e),
debug.DebugLevel.ERROR,
)
def is_real_progress_update(self):
"""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 self.env["runtime"]["ScreenManager"].is_screen_change():
self.env["runtime"]["DebugManager"].write_debug_out(
"Progress filter: screen change detected", debug.DebugLevel.INFO
)
return False
# 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
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
# Check if delta is too large (screen change) vs small incremental
@@ -71,16 +81,27 @@ class command:
if (
delta_length > 200
): # 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
# If delta contains newlines and is substantial, let incoming handler
# deal with it to avoid interfering with multi-line text output
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
# Check if current line looks like a prompt - progress unlikely during
# prompts
if self.is_current_line_prompt():
self.env["runtime"]["DebugManager"].write_debug_out(
"Progress filter: prompt detected", debug.DebugLevel.INFO
)
return False
return True
@@ -284,10 +305,61 @@ class command:
self.env["commandBuffer"]["lastProgressTime"] = current_time
return
# Pattern 6: Claude Code progress indicators
claude_progress_match = re.search(r'[·✶✢✻✽*].*?\(esc to interrupt[^)]*\)', text)
# Pattern 6: Claude Code working indicators (various symbols + activity text + "esc/ctrl+c to interrupt")
# 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 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.env["commandBuffer"]["lastProgressTime"] = current_time
return
@@ -327,6 +399,23 @@ class command:
self.env["commandBuffer"]["lastProgressTime"] = current_time
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):
# Map 0-100% to 400-1200Hz frequency range
frequency = 400 + (percentage * 8)
@@ -158,6 +158,16 @@ class command:
def _restore_speech(self):
"""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
self.env["commandBuffer"]["silenceUntilPrompt"] = False
# Also disable the keypress-based speech restoration since we're
@@ -4,13 +4,18 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import difflib
import time
from fenrirscreenreader.core.i18n import _
class command:
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):
self.env = environment
@@ -21,6 +26,73 @@ class command:
def get_description(self):
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):
"""Check if this delta was already handled by tab completion to avoid duplicates"""
if "tabCompletion" not in self.env["commandBuffer"]:
@@ -40,9 +112,74 @@ class command:
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):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"speech", "autoReadIncoming"
"speech", "auto_read_incoming"
):
return
# is there something to read?
@@ -51,6 +188,9 @@ class command:
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
if self._was_handled_by_tab_completion(delta_text):
return
@@ -71,7 +211,31 @@ class command:
# <= 2:
if "\n" not in delta_text:
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']))
delta_text = self._prefer_inserted_lower_screen_text(delta_text)
self.env["runtime"]["OutputManager"].present_text(
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.set("keyboard", "driver", "evdevDriver")
self.config.set("keyboard", "keyboardLayout", "desktop")
self.config.set("keyboard", "keyboard_layout", "desktop")
self.config.add_section("screen")
self.config.set("screen", "driver", "vcsaDriver")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set punctuation to All (every punctuation mark)"
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":
self.present_text("Punctuation level already set to All")
return
success = self.set_setting("general", "punctuationLevel", "all")
success = self.set_setting("general", "punctuation_level", "all")
if success:
self.present_text("Punctuation level set to All - every punctuation mark will be spoken")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set punctuation to Most (detailed punctuation)"
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":
self.present_text("Punctuation level already set to Most")
return
success = self.set_setting("general", "punctuationLevel", "most")
success = self.set_setting("general", "punctuation_level", "most")
if success:
self.present_text("Punctuation level set to Most - detailed punctuation will be spoken")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set punctuation to None (no punctuation spoken)"
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":
self.present_text("Punctuation level already set to None")
return
success = self.set_setting("general", "punctuationLevel", "none")
success = self.set_setting("general", "punctuation_level", "none")
if success:
self.present_text("Punctuation level set to None - no punctuation will be spoken")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set punctuation to Some (basic punctuation only)"
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":
self.present_text("Punctuation level already set to Some")
return
success = self.set_setting("general", "punctuationLevel", "some")
success = self.set_setting("general", "punctuation_level", "some")
if success:
self.present_text("Punctuation level set to Some - basic punctuation will be spoken")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set character echo to Always (echo all typed characters)"
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":
self.present_text("Character echo already set to Always")
return
success = self.set_setting("keyboard", "charEchoMode", "1")
success = self.set_setting("keyboard", "char_echo_mode", "1")
if success:
self.present_text("Character echo set to Always - all typed characters will be spoken")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set character echo to Caps Lock (echo only when caps lock is on)"
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":
self.present_text("Character echo already set to Caps Lock mode")
return
success = self.set_setting("keyboard", "charEchoMode", "2")
success = self.set_setting("keyboard", "char_echo_mode", "2")
if success:
self.present_text("Character echo set to Caps Lock mode - characters will be spoken only when caps lock is on")
@@ -21,13 +21,13 @@ class command(config_command):
return "Set character echo to None (no character echo)"
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":
self.present_text("Character echo already set to None")
return
success = self.set_setting("keyboard", "charEchoMode", "0")
success = self.set_setting("keyboard", "char_echo_mode", "0")
if success:
self.present_text("Character echo set to None - no typed characters will be spoken")
@@ -21,8 +21,8 @@ class command(config_command):
return "Toggle exclusive keyboard access"
def run(self):
current_state = self.getBooleanSetting("keyboard", "grabDevices", True)
new_state = self.toggleBooleanSetting("keyboard", "grabDevices")
current_state = self.getBooleanSetting("keyboard", "grab_devices", True)
new_state = self.toggleBooleanSetting("keyboard", "grab_devices")
if new_state != current_state:
state_text = "enabled" if new_state else "disabled"
@@ -21,8 +21,8 @@ class command(config_command):
return "Toggle word echo when pressing space"
def run(self):
current_state = self.getBooleanSetting("keyboard", "wordEcho", False)
new_state = self.toggleBooleanSetting("keyboard", "wordEcho")
current_state = self.getBooleanSetting("keyboard", "word_echo", False)
new_state = self.toggleBooleanSetting("keyboard", "word_echo")
if new_state != current_state:
state_text = "enabled" if new_state else "disabled"
@@ -108,7 +108,7 @@ class command(config_command):
"rate": "0.5",
"pitch": "0.5",
"volume": "1.0",
"autoReadIncoming": "True",
"auto_read_incoming": "True",
}
# Basic sound defaults
@@ -123,10 +123,10 @@ class command(config_command):
self.config["keyboard"] = {
"driver": "evdevDriver",
"device": "ALL",
"keyboardLayout": "desktop",
"charEchoMode": "1",
"wordEcho": "False",
"charDeleteEcho": "True",
"keyboard_layout": "desktop",
"char_echo_mode": "1",
"word_echo": "False",
"char_delete_echo": "True",
}
# Basic screen defaults
@@ -137,9 +137,9 @@ class command(config_command):
# Basic general defaults
self.config["general"] = {
"punctuationLevel": "some",
"punctuation_level": "some",
"debug_level": "0",
"numberOfClipboards": "50",
"number_of_clipboards": "50",
}
# Write the configuration
@@ -37,7 +37,7 @@ class command(config_command):
self.present_text(f"Current screen driver: {current_description}")
# Cycle through the available drivers
drivers = ["vcsaDriver", "ptyDriver", "dummyDriver", "debugDriver"]
drivers = ["vcsaDriver", "ptyDriver", "dummyDriver"]
try:
current_index = drivers.index(current_driver)
next_index = (current_index + 1) % len(drivers)
@@ -29,12 +29,7 @@ class command:
self.env["runtime"]["OutputManager"].present_text(
"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)
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
"BYTE"
]:
self.env["runtime"]["ByteManager"].send_bytes(self.byteMakro)
def set_callback(self, callback):
pass
@@ -29,12 +29,7 @@ class command:
self.env["runtime"]["OutputManager"].present_text(
"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)
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
"BYTE"
]:
self.env["runtime"]["ByteManager"].send_bytes(self.byteMakro)
def set_callback(self, callback):
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_LEFTSHIFT'],[1,'KEY_LEFTCTRL'],[1,'KEY_N'],[0.05,'SLEEP'],[0,'KEY_N'],[0,'KEY_LEFTCTRL'],[0,'KEY_LEFTSHIFT']]
self.key_macro = []
self.byteMakro = []
def shutdown(self):
pass
@@ -27,12 +26,7 @@ class command:
return "No description found"
def run(self):
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
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):
pass
-224
View File
@@ -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()
+1 -1
View File
@@ -21,7 +21,7 @@ class CursorManager:
Return False if numlock is ON (let keys type numbers)
"""
# Return False if numlock is ON
return not self.env["input"]["newNumLock"]
return not self.env["input"]["new_num_lock"]
def shutdown(self):
pass

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