Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e10c1c43b | |||
| 1e67876883 | |||
| 14cf6b6088 | |||
| 900a027643 | |||
| 0e50175463 | |||
| 7283f04778 | |||
| 8d3495f74f | |||
| a6cd47dafc | |||
| 0bb2e52deb | |||
| b8eb815a86 | |||
| beca468338 | |||
| a26fe26c8c | |||
| 508fd11610 | |||
| afe0e71a1d | |||
| 9e8d0b3869 | |||
| d7f86ca0de | |||
| 49a79d2722 | |||
| 4ab024d115 | |||
| c4ae27a01b | |||
| 668d39b444 | |||
| 8b25afbf5a | |||
| efeb040f75 | |||
| 7a17b36d50 |
@@ -1,7 +1,6 @@
|
|||||||
# Fenrir
|
# Fenrir
|
||||||
|
|
||||||
A modern, modular, flexible and fast console screen reader.
|
A modern, modular, flexible and fast console screen reader for Linux.
|
||||||
It should run on any operating system. If you want to help, or write drivers to make it work on other systems, just let me know.
|
|
||||||
This software is licensed under the LGPL v3.
|
This software is licensed under the LGPL v3.
|
||||||
|
|
||||||
**Current maintainer:** Storm Dragon
|
**Current maintainer:** Storm Dragon
|
||||||
@@ -24,12 +23,16 @@ This software is licensed under the LGPL v3.
|
|||||||
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
|
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
|
||||||
|
|
||||||
|
|
||||||
## OS Requirements
|
## Platform Support
|
||||||
|
|
||||||
- Linux (ptyDriver, vcsaDriver, evdevDriver) - Primary platform with full support
|
Fenrir is a Linux screen reader. Linux is the only officially supported platform.
|
||||||
- macOS (ptyDriver) - Limited support
|
|
||||||
- BSD (ptyDriver) - Limited support
|
**Other platforms (macOS, BSD, Windows):** Pull requests adding support for other operating systems may be accepted provided they do not break Linux functionality. However, no special care will be taken to preserve functionality on secondary platforms. If changes to Fenrir break support on a non-Linux OS, it is the responsibility of third-party contributors to submit fixes.
|
||||||
- Windows (ptyDriver) - Limited support
|
|
||||||
|
- Linux (ptyDriver, vcsaDriver, evdevDriver) - Full support
|
||||||
|
- macOS (ptyDriver) - Community-maintained, no guarantees
|
||||||
|
- BSD (ptyDriver) - Community-maintained, no guarantees
|
||||||
|
- Windows (ptyDriver) - Community-maintained, no guarantees
|
||||||
|
|
||||||
|
|
||||||
## Core Requirements
|
## Core Requirements
|
||||||
@@ -222,7 +225,7 @@ Fenrir supports two main keyboard layouts:
|
|||||||
Configure in `/etc/fenrir/settings/settings.conf`:
|
Configure in `/etc/fenrir/settings/settings.conf`:
|
||||||
```ini
|
```ini
|
||||||
[keyboard]
|
[keyboard]
|
||||||
keyboardLayout=desktop # or 'laptop'
|
keyboard_layout=desktop # or 'laptop'
|
||||||
```
|
```
|
||||||
|
|
||||||
### First Time Setup
|
### First Time Setup
|
||||||
@@ -255,9 +258,9 @@ Enable remote control in `/etc/fenrir/settings/settings.conf`:
|
|||||||
enable=True
|
enable=True
|
||||||
driver=unixDriver # or tcpDriver
|
driver=unixDriver # or tcpDriver
|
||||||
port=22447 # for TCP driver
|
port=22447 # for TCP driver
|
||||||
socketFile= # custom socket path (optional)
|
socket_file= # custom socket path (optional)
|
||||||
enableSettingsRemote=True # allow settings changes
|
enable_settings_remote=True # allow settings changes
|
||||||
enableCommandRemote=True # allow command execution
|
enable_command_remote=True # allow command execution
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remote Drivers
|
### Remote Drivers
|
||||||
@@ -299,8 +302,8 @@ echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreade
|
|||||||
echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Change punctuation level (none/some/most/all)
|
# Change punctuation level (none/some/most/all)
|
||||||
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set general#punctuation_level=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
echo "setting set general#punctuationLevel=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set general#punctuation_level=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Voice and TTS engine control
|
# Voice and TTS engine control
|
||||||
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
@@ -311,14 +314,14 @@ echo "setting set sound#enabled=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenre
|
|||||||
echo "setting set sound#volume=0.5" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set sound#volume=0.5" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Keyboard and input settings
|
# Keyboard and input settings
|
||||||
echo "setting set keyboard#charEchoMode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set keyboard#char_echo_mode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
echo "setting set keyboard#wordEcho=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set keyboard#word_echo=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Screen control (ignore specific TTYs)
|
# Screen control (ignore specific TTYs)
|
||||||
echo "setting set screen#ignoreScreen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set screen#ignore_screen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Multiple settings at once
|
# Multiple settings at once
|
||||||
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuationLevel=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuation_level=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
|
|
||||||
# Reset all settings to defaults
|
# Reset all settings to defaults
|
||||||
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||||
@@ -421,7 +424,7 @@ setting <action> [parameters]
|
|||||||
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
|
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
|
||||||
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
|
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
|
||||||
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
|
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
|
||||||
- `speech#autoReadIncoming=True/False` - Auto-read new text
|
- `speech#auto_read_incoming=True/False` - Auto-read new text
|
||||||
|
|
||||||
*Sound Settings:*
|
*Sound Settings:*
|
||||||
- `sound#enabled=True/False` - Enable/disable sound
|
- `sound#enabled=True/False` - Enable/disable sound
|
||||||
@@ -430,32 +433,32 @@ setting <action> [parameters]
|
|||||||
- `sound#theme=theme_name` - Sound theme
|
- `sound#theme=theme_name` - Sound theme
|
||||||
|
|
||||||
*General Settings:*
|
*General Settings:*
|
||||||
- `general#punctuationLevel=none/some/most/all` - Punctuation verbosity
|
- `general#punctuation_level=none/some/most/all` - Punctuation verbosity
|
||||||
- `general#debugLevel=0-3` - Debug level
|
- `general#debug_level=0-3` - Debug level
|
||||||
- `general#emoticons=True/False` - Enable emoticon replacement
|
- `general#emoticons=True/False` - Enable emoticon replacement
|
||||||
- `general#autoSpellCheck=True/False` - Automatic spell checking
|
- `general#auto_spell_check=True/False` - Automatic spell checking
|
||||||
|
|
||||||
*Focus Settings:*
|
*Focus Settings:*
|
||||||
- `focus#cursor=True/False` - Follow text cursor
|
- `focus#cursor=True/False` - Follow text cursor
|
||||||
- `focus#highlight=True/False` - Follow text highlighting
|
- `focus#highlight=True/False` - Follow text highlighting
|
||||||
|
|
||||||
*Keyboard Settings:*
|
*Keyboard Settings:*
|
||||||
- `keyboard#charEchoMode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
|
- `keyboard#char_echo_mode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
|
||||||
- `keyboard#wordEcho=True/False` - Echo complete words
|
- `keyboard#word_echo=True/False` - Echo complete words
|
||||||
- `keyboard#charDeleteEcho=True/False` - Echo deleted characters
|
- `keyboard#char_delete_echo=True/False` - Echo deleted characters
|
||||||
- `keyboard#interruptOnKeyPress=True/False` - Interrupt speech on key press
|
- `keyboard#interrupt_on_key_press=True/False` - Interrupt speech on key press
|
||||||
|
|
||||||
*Screen Settings:*
|
*Screen Settings:*
|
||||||
- `screen#ignoreScreen=1,2,3` - TTY screens to ignore
|
- `screen#ignore_screen=1,2,3` - TTY screens to ignore
|
||||||
- `screen#autodetectIgnoreScreen=True/False` - Auto-detect screens to ignore
|
- `screen#autodetect_ignore_screen=True/False` - Auto-detect screens to ignore
|
||||||
- `screen#screenUpdateDelay=float` - Screen update delay
|
- `screen#screen_update_delay=float` - Screen update delay
|
||||||
|
|
||||||
*Time Settings:*
|
*Time Settings:*
|
||||||
- `time#enabled=True/False` - Enable time announcements
|
- `time#enabled=True/False` - Enable time announcements
|
||||||
- `time#presentTime=True/False` - Announce time
|
- `time#present_time=True/False` - Announce time
|
||||||
- `time#presentDate=True/False` - Announce date changes
|
- `time#present_date=True/False` - Announce date changes
|
||||||
- `time#delaySec=seconds` - Announcement interval
|
- `time#delay_sec=seconds` - Announcement interval
|
||||||
- `time#onMinutes=00,30` - Specific minutes to announce
|
- `time#on_minutes=00,30` - Specific minutes to announce
|
||||||
|
|
||||||
## Table Navigation
|
## Table Navigation
|
||||||
|
|
||||||
@@ -623,7 +626,7 @@ rsync -av source/ destination/ # File synchronization
|
|||||||
### Customization
|
### Customization
|
||||||
|
|
||||||
Progress monitoring can be configured through settings:
|
Progress monitoring can be configured through settings:
|
||||||
- **Default enabled**: Set `progressMonitoring=True` in sound section
|
- **Default enabled**: Set `progress_monitoring=True` in sound section
|
||||||
- **Sound integration**: Works with all sound drivers (sox, gstreamer)
|
- **Sound integration**: Works with all sound drivers (sox, gstreamer)
|
||||||
- **Remote control**: Enable/disable through remote commands
|
- **Remote control**: Enable/disable through remote commands
|
||||||
|
|
||||||
@@ -677,8 +680,8 @@ send_fenrir_command("setting set speech#rate=0.9")
|
|||||||
- TCP driver binds only to localhost (127.0.0.1)
|
- TCP driver binds only to localhost (127.0.0.1)
|
||||||
- Socket file permissions are set to write-only (0o222)
|
- Socket file permissions are set to write-only (0o222)
|
||||||
- Commands are processed with Fenrir's privileges
|
- Commands are processed with Fenrir's privileges
|
||||||
- Settings changes can be disabled via `enableSettingsRemote=False`
|
- Settings changes can be disabled via `enable_settings_remote=False`
|
||||||
- Command execution can be disabled via `enableCommandRemote=False`
|
- Command execution can be disabled via `enable_command_remote=False`
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
@@ -688,7 +691,7 @@ send_fenrir_command("setting set speech#rate=0.9")
|
|||||||
- Ensure remote driver is enabled in settings
|
- Ensure remote driver is enabled in settings
|
||||||
|
|
||||||
**Commands not working:**
|
**Commands not working:**
|
||||||
- Verify `enableCommandRemote=True` in settings
|
- Verify `enable_command_remote=True` in settings
|
||||||
- Check Fenrir debug logs: `/var/log/fenrir.log`
|
- Check Fenrir debug logs: `/var/log/fenrir.log`
|
||||||
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
|
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
|
||||||
|
|
||||||
@@ -710,7 +713,7 @@ fenrir [OPTIONS]
|
|||||||
- `-p, --print` - Print debug messages to screen
|
- `-p, --print` - Print debug messages to screen
|
||||||
- `-e, --emulated-pty` - Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)
|
- `-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)
|
- `-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
|
- `-F, --force-all-screens` - Force Fenrir to respond on all screens, ignoring ignore_screen setting
|
||||||
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
|
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
|
||||||
|
|
||||||
### Examples:
|
### Examples:
|
||||||
@@ -724,7 +727,7 @@ sudo fenrir -e
|
|||||||
# Override settings via command line
|
# Override settings via command line
|
||||||
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
|
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
|
||||||
|
|
||||||
# Force Fenrir to work on all screens (ignore ignoreScreen setting)
|
# Force Fenrir to work on all screens (ignore ignore_screen setting)
|
||||||
sudo fenrir -F
|
sudo fenrir -F
|
||||||
|
|
||||||
# Ignore specific screens
|
# Ignore specific screens
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ KEY_FENRIR,KEY_CTRL,KEY_P=toggle_punctuation_level
|
|||||||
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
||||||
KEY_FENRIR,KEY_BACKSLASH=toggle_output
|
KEY_FENRIR,KEY_BACKSLASH=toggle_output
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
|
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
|
||||||
|
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_E=cycle_key_echo
|
||||||
key_FENRIR,KEY_KPENTER=toggle_auto_read
|
key_FENRIR,KEY_KPENTER=toggle_auto_read
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
||||||
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
|
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
|
|||||||
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
|
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
|
||||||
KEY_FENRIR,KEY_SHIFT,KEY_E=toggle_emoticons
|
KEY_FENRIR,KEY_SHIFT,KEY_E=toggle_emoticons
|
||||||
|
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_E=cycle_key_echo
|
||||||
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
|
||||||
KEY_FENRIR,KEY_Y=toggle_highlight_tracking
|
KEY_FENRIR,KEY_Y=toggle_highlight_tracking
|
||||||
#=toggle_barrier
|
#=toggle_barrier
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
enabled=True
|
enabled=True
|
||||||
|
|
||||||
# Select the driver used to play sounds, choices are genericDriver and gstreamerDriver.
|
# Select the driver used to play sounds, choices are genericDriver and gstreamerDriver.
|
||||||
|
# Generic driver uses fewer dependencies but spawns a process for each sound played including progress bar beeps
|
||||||
# Gstreamer is the default.
|
# Gstreamer is the default.
|
||||||
driver=gstreamerDriver
|
driver=gstreamerDriver
|
||||||
#driver=genericDriver
|
#driver=genericDriver
|
||||||
|
|
||||||
# Sound themes. These are the pack of sounds used for sound alerts.
|
# Sound themes. These are the pack of sounds used for sound alerts.
|
||||||
# Sound packs may be located at /usr/share/sounds
|
# Sound packs may be located at /usr/share/sounds
|
||||||
# For system wide availability, or ~/.local/share/fenrirscreenreader/sounds
|
|
||||||
# For the current user.
|
|
||||||
theme=default
|
theme=default
|
||||||
|
|
||||||
# Sound volume controls how loud the sounds for your selected soundpack are.
|
# Sound volume controls how loud the sounds for your selected soundpack are.
|
||||||
@@ -46,6 +45,12 @@ rate=0.5
|
|||||||
pitch=0.5
|
pitch=0.5
|
||||||
# Pitch for capital letters
|
# Pitch for capital letters
|
||||||
capital_pitch=0.9
|
capital_pitch=0.9
|
||||||
|
# How to indicate capital letters:
|
||||||
|
# pitch = change speech pitch (uses capital_pitch value)
|
||||||
|
# beep = play Caps.wav sound icon
|
||||||
|
# both = play beep AND change pitch
|
||||||
|
# none = no special indication
|
||||||
|
capital_indicator=pitch
|
||||||
|
|
||||||
# Volume controls the loudness of the voice, select from 0, quietest, to 1.0, loudest.
|
# Volume controls the loudness of the voice, select from 0, quietest, to 1.0, loudest.
|
||||||
volume=1.0
|
volume=1.0
|
||||||
@@ -70,6 +75,22 @@ auto_read_incoming=True
|
|||||||
# Speak individual numbers instead of whole string.
|
# Speak individual numbers instead of whole string.
|
||||||
read_numbers_as_digits = False
|
read_numbers_as_digits = False
|
||||||
|
|
||||||
|
# Flood control: batch rapid updates instead of speaking each one
|
||||||
|
# Number of updates within rapid_update_window to trigger batching
|
||||||
|
rapid_update_threshold=5
|
||||||
|
|
||||||
|
# Time window (seconds) for detecting rapid updates
|
||||||
|
rapid_update_window=0.3
|
||||||
|
|
||||||
|
# How often to speak batched content (seconds)
|
||||||
|
batch_flush_interval=0.5
|
||||||
|
|
||||||
|
# Maximum lines to keep when batching (keeps newest, drops oldest)
|
||||||
|
max_batch_lines=100
|
||||||
|
|
||||||
|
# Only enable flood control if this many new lines appear in the window
|
||||||
|
flood_line_threshold=500
|
||||||
|
|
||||||
# genericSpeechCommand is the command that is executed for talking
|
# genericSpeechCommand is the command that is executed for talking
|
||||||
# the following variables are replaced with values
|
# the following variables are replaced with values
|
||||||
# fenrirText = is the text that should be spoken
|
# fenrirText = is the text that should be spoken
|
||||||
@@ -110,8 +131,10 @@ driver=evdevDriver
|
|||||||
device=ALL
|
device=ALL
|
||||||
# gives Fenrir exclusive access to the keyboard and lets it control keystrokes.
|
# gives Fenrir exclusive access to the keyboard and lets it control keystrokes.
|
||||||
grab_devices=True
|
grab_devices=True
|
||||||
ignore_shortcuts=False
|
# Ignore shortcut bindings and pass all keys through without processing Fenrir commands.
|
||||||
|
# When True, Fenrir will only monitor screen content without intercepting keyboard input.
|
||||||
# the current shortcut layout located in /etc/fenrirscreenreader/keyboard
|
# the current shortcut layout located in /etc/fenrirscreenreader/keyboard
|
||||||
|
ignore_shortcuts=False
|
||||||
keyboard_layout=desktop
|
keyboard_layout=desktop
|
||||||
# echo chars while typing.
|
# echo chars while typing.
|
||||||
# 0 = None
|
# 0 = None
|
||||||
@@ -130,14 +153,17 @@ interrupt_on_key_press_filter=
|
|||||||
double_tap_timeout=0.2
|
double_tap_timeout=0.2
|
||||||
|
|
||||||
[general]
|
[general]
|
||||||
# Debug levels: 0=DEACTIVE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
|
# Debug levels: 0=NONE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
|
||||||
# For production use, WARNING (2) provides good balance of useful info without spam
|
# For production use, WARNING (2) provides good balance of useful info without spam
|
||||||
debug_level=2
|
# The default is 0, no logging.
|
||||||
|
debug_level=0
|
||||||
# debugMode sets where the debug output should send to:
|
# debugMode sets where the debug output should send to:
|
||||||
# debugMode=File writes to debug_file (Default:/tmp/fenrir-PID.log)
|
# debugMode=File writes to debug_file (Default:/tmp/fenrir-PID.log)
|
||||||
# debugMode=Print just prints on the screen
|
# debugMode=Print just prints on the screen
|
||||||
debug_mode=File
|
debug_mode=File
|
||||||
debug_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_profile=default
|
||||||
punctuation_level=some
|
punctuation_level=some
|
||||||
respect_punctuation_pause=True
|
respect_punctuation_pause=True
|
||||||
@@ -240,15 +266,6 @@ leave_review_on_cursor_change=True
|
|||||||
# Exit review mode when switching to a different TTY/screen
|
# Exit review mode when switching to a different TTY/screen
|
||||||
leave_review_on_screen_change=True
|
leave_review_on_screen_change=True
|
||||||
|
|
||||||
[promote]
|
|
||||||
# Enable promoting (announcing) important text updates automatically
|
|
||||||
enabled=True
|
|
||||||
# Seconds of inactivity before promoting text updates (prevents spam during active typing)
|
|
||||||
inactive_timeout_sec=120
|
|
||||||
# Comma-separated list of text patterns to promote when detected
|
|
||||||
# Leave empty to disable pattern-based promotion
|
|
||||||
list=
|
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
# Custom path for VMenu (virtual menu) profiles
|
# Custom path for VMenu (virtual menu) profiles
|
||||||
# Leave empty to use default location (/etc/fenrirscreenreader/vmenu-profiles/)
|
# Leave empty to use default location (/etc/fenrirscreenreader/vmenu-profiles/)
|
||||||
|
|||||||
Binary file not shown.
@@ -46,8 +46,6 @@ ErrorSpeech='ErrorSpeech.wav'
|
|||||||
ErrorScreen='ErrorScreen.wav'
|
ErrorScreen='ErrorScreen.wav'
|
||||||
# If you cursor over an text that has attributs (like color)
|
# If you cursor over an text that has attributs (like color)
|
||||||
HasAttributes='has_attribute.wav'
|
HasAttributes='has_attribute.wav'
|
||||||
# fenrir can promote strings if they appear on the screen.
|
|
||||||
PromotedText='PromotedText.wav'
|
|
||||||
# missspelled indicator
|
# missspelled indicator
|
||||||
mispell='mispell.wav'
|
mispell='mispell.wav'
|
||||||
# the for capital letter
|
# the for capital letter
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ ErrorBraille=''
|
|||||||
ErrorScreen=''
|
ErrorScreen=''
|
||||||
# If you cursor over an text that has attributs (like color)
|
# If you cursor over an text that has attributs (like color)
|
||||||
HasAttributes=''
|
HasAttributes=''
|
||||||
# fenrir can promote strings if they appear on the screen.
|
|
||||||
PromotedText=''
|
|
||||||
# misspelled indicator
|
# misspelled indicator
|
||||||
mispell=''
|
mispell=''
|
||||||
# the for capital letter:
|
# the for capital letter:
|
||||||
|
|||||||
@@ -1095,23 +1095,6 @@ announce=True
|
|||||||
interrupt=False
|
interrupt=False
|
||||||
....
|
....
|
||||||
|
|
||||||
==== Promoted List
|
|
||||||
|
|
||||||
Promoted Lists are a nice feature if you are away from your computer or
|
|
||||||
performing more longer tasks. you can define a list of words which you
|
|
||||||
want to hear a sound icon for after a period of inactivity. Example if
|
|
||||||
the word "Chrys" appears after 120 Seconds of inactivity:
|
|
||||||
|
|
||||||
....
|
|
||||||
[promote]
|
|
||||||
enabled=True
|
|
||||||
inactive_timeout_sec=120
|
|
||||||
list=Chrys
|
|
||||||
....
|
|
||||||
|
|
||||||
See section link:#Promote[Promote] in `+settings.conf+` for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
=== Dictionary
|
=== Dictionary
|
||||||
|
|
||||||
You can make use of different kinds of built-in dictionary's. A
|
You can make use of different kinds of built-in dictionary's. A
|
||||||
@@ -2049,39 +2032,6 @@ leave_review_on_screen_change=True
|
|||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
Values: on=`+True+`, off=`+False+`
|
||||||
|
|
||||||
==== Promote
|
|
||||||
|
|
||||||
"Promoted Lists" are configured in the section `+[promote]+`. Turn
|
|
||||||
Promoted Lists" on or off:
|
|
||||||
|
|
||||||
....
|
|
||||||
enabled=True
|
|
||||||
....
|
|
||||||
|
|
||||||
Values: on=`+True+`, off=`+False+`
|
|
||||||
|
|
||||||
The minimum time interval of inactivity to activate promoting. By
|
|
||||||
default it promotes after 120 Seconds inactivity:
|
|
||||||
|
|
||||||
....
|
|
||||||
inactive_timeout_sec=120
|
|
||||||
....
|
|
||||||
|
|
||||||
Values: in Seconds
|
|
||||||
|
|
||||||
Define a list of promoted words comma seperated:
|
|
||||||
|
|
||||||
....
|
|
||||||
list=
|
|
||||||
....
|
|
||||||
|
|
||||||
Values: text (comma seperated) Example to promote the word "nickname" or
|
|
||||||
a bash prompt:
|
|
||||||
|
|
||||||
....
|
|
||||||
list=nickname,$:,#:
|
|
||||||
....
|
|
||||||
|
|
||||||
==== Time
|
==== Time
|
||||||
|
|
||||||
The automated time announcement is configured in the section `+[time]+`.
|
The automated time announcement is configured in the section `+[time]+`.
|
||||||
|
|||||||
@@ -729,15 +729,6 @@ Example on fix minutes in an hour. example every quarter "delaySec=0" and "onMin
|
|||||||
onMinutes=00,15,30,45
|
onMinutes=00,15,30,45
|
||||||
announce=True
|
announce=True
|
||||||
interrupt=False
|
interrupt=False
|
||||||
==== Promoted List ====
|
|
||||||
Promoted Lists are a nice feature if you are away from your computer or performing more longer tasks.
|
|
||||||
you can define a list of words which you want to hear a sound icon for after a period of inactivity.
|
|
||||||
Example if the word "Chrys" appears after 120 Seconds of inactivity:
|
|
||||||
[promote]
|
|
||||||
enabled=True
|
|
||||||
inactive_timeout_sec=120
|
|
||||||
list=Chrys
|
|
||||||
See section [[#Promote|Promote]] in ''settings.conf'' for more information.
|
|
||||||
==== Punctuation ====
|
==== Punctuation ====
|
||||||
Fenrir handles punctuation levels and names for you with several provided dictionaries.
|
Fenrir handles punctuation levels and names for you with several provided dictionaries.
|
||||||
|
|
||||||
@@ -1199,23 +1190,6 @@ Values: on=''True'', off=''False''
|
|||||||
Leave the review mode when changing the screen (From TTY3 to TTY4):
|
Leave the review mode when changing the screen (From TTY3 to TTY4):
|
||||||
leave_review_on_screen_change=True
|
leave_review_on_screen_change=True
|
||||||
Values: on=''True'', off=''False''
|
Values: on=''True'', off=''False''
|
||||||
==== Promote ====
|
|
||||||
"Promoted Lists" are configured in the section ''[promote]''.
|
|
||||||
Turn Promoted Lists" on or off:
|
|
||||||
enabled=True
|
|
||||||
Values: on=''True'', off=''False''
|
|
||||||
|
|
||||||
The minimum time interval of inactivity to activate promoting.
|
|
||||||
By default it promotes after 120 Seconds inactivity:
|
|
||||||
inactive_timeout_sec=120
|
|
||||||
Values: in Seconds
|
|
||||||
|
|
||||||
Define a list of promoted words comma seperated:
|
|
||||||
list=
|
|
||||||
Values: text (comma seperated)
|
|
||||||
Example to promote the word "nickname" or a bash prompt:
|
|
||||||
list=nickname,$:,#:
|
|
||||||
|
|
||||||
==== Time ====
|
==== Time ====
|
||||||
The automated time announcement is configured in the section ''[time]''.
|
The automated time announcement is configured in the section ''[time]''.
|
||||||
Time announcement is disabled by default.
|
Time announcement is disabled by default.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -22,7 +22,7 @@ class command:
|
|||||||
return _("sends the following keypress to the terminal or application")
|
return _("sends the following keypress to the terminal or application")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.env["input"]["keyForeward"] = 3
|
self.env["input"]["key_forward"] = 3
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Forward next keypress"), interrupt=True
|
_("Forward next keypress"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ class command:
|
|||||||
return _("Interrupts the current presentation")
|
return _("Interrupts the current presentation")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if len(self.env["input"]["prevDeepestInput"]) > len(
|
if len(self.env["input"]["prev_deepest_input"]) > len(
|
||||||
self.env["input"]["currInput"]
|
self.env["input"]["curr_input"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
self.env["runtime"]["OutputManager"].interrupt_output()
|
self.env["runtime"]["OutputManager"].interrupt_output()
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class command:
|
|||||||
return
|
return
|
||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
return
|
return
|
||||||
if len(self.env["input"]["currInput"]) <= len(
|
if len(self.env["input"]["curr_input"]) <= len(
|
||||||
self.env["input"]["prevInput"]
|
self.env["input"]["prev_input"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# if the filter is set
|
# if the filter is set
|
||||||
@@ -46,8 +46,8 @@ class command:
|
|||||||
.get_setting("keyboard", "interrupt_on_key_press_filter")
|
.get_setting("keyboard", "interrupt_on_key_press_filter")
|
||||||
.split(",")
|
.split(",")
|
||||||
)
|
)
|
||||||
for currInput in self.env["input"]["currInput"]:
|
for curr_key in self.env["input"]["curr_input"]:
|
||||||
if currInput not in filter_list:
|
if curr_key not in filter_list:
|
||||||
return
|
return
|
||||||
self.env["runtime"]["OutputManager"].interrupt_output()
|
self.env["runtime"]["OutputManager"].interrupt_output()
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class command:
|
|||||||
return
|
return
|
||||||
# 2 = caps only
|
# 2 = caps only
|
||||||
if active == 2:
|
if active == 2:
|
||||||
if not self.env["input"]["newCapsLock"]:
|
if not self.env["input"]["new_caps_lock"]:
|
||||||
return
|
return
|
||||||
# big changes are no char (but the value is bigger than one maybe the
|
# big changes are no char (but the value is bigger than one maybe the
|
||||||
# differ needs longer than you can type, so a little strange random
|
# differ needs longer than you can type, so a little strange random
|
||||||
|
|||||||
+9
-2
@@ -71,6 +71,13 @@ class command:
|
|||||||
self.env["screen"]["new_cursor"]["y"],
|
self.env["screen"]["new_cursor"]["y"],
|
||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
# Don't interrupt ongoing auto-read announcements
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
if curr_char.isspace():
|
if curr_char.isspace():
|
||||||
# Only announce spaces during pure navigation (arrow keys)
|
# Only announce spaces during pure navigation (arrow keys)
|
||||||
# Check if this is really navigation by looking at input history
|
# Check if this is really navigation by looking at input history
|
||||||
@@ -87,14 +94,14 @@ class command:
|
|||||||
char_utils.present_char_for_review(
|
char_utils.present_char_for_review(
|
||||||
self.env,
|
self.env,
|
||||||
curr_char,
|
curr_char,
|
||||||
interrupt=True,
|
interrupt=do_interrupt,
|
||||||
announce_capital=True,
|
announce_capital=True,
|
||||||
flush=False,
|
flush=False,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
curr_char,
|
curr_char,
|
||||||
interrupt=True,
|
interrupt=do_interrupt,
|
||||||
ignore_punctuation=True,
|
ignore_punctuation=True,
|
||||||
announce_capital=True,
|
announce_capital=True,
|
||||||
flush=False,
|
flush=False,
|
||||||
|
|||||||
@@ -153,10 +153,17 @@ class command:
|
|||||||
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
|
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
|
||||||
curr_delta = curr_delta.strip()
|
curr_delta = curr_delta.strip()
|
||||||
|
|
||||||
|
# Don't interrupt ongoing auto-read announcements
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
# Enhanced announcement with better handling of empty completions
|
# Enhanced announcement with better handling of empty completions
|
||||||
if curr_delta:
|
if curr_delta:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
curr_delta, interrupt=True, announce_capital=True, flush=False
|
curr_delta, interrupt=do_interrupt, announce_capital=True, flush=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
|
|||||||
@@ -66,8 +66,15 @@ class command:
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Don't interrupt ongoing auto-read announcements
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
curr_word, interrupt=True, flush=False
|
curr_word, interrupt=do_interrupt, flush=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
|
|||||||
+10
-4
@@ -30,8 +30,8 @@ class command:
|
|||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
self.lastIdent = 0
|
self.lastIdent = 0
|
||||||
return
|
return
|
||||||
# this leads to problems in vim -> status line change -> no
|
# Don't announce cursor movements when auto-read is handling incoming text
|
||||||
# announcement, so we do check the lengh as hack
|
# This prevents interrupting ongoing auto-read announcements
|
||||||
if self.env["runtime"]["ScreenManager"].is_delta():
|
if self.env["runtime"]["ScreenManager"].is_delta():
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -44,16 +44,22 @@ class command:
|
|||||||
self.env["screen"]["new_cursor"]["y"],
|
self.env["screen"]["new_cursor"]["y"],
|
||||||
self.env["screen"]["new_content_text"],
|
self.env["screen"]["new_content_text"],
|
||||||
)
|
)
|
||||||
|
# Don't interrupt ongoing auto-read announcements with cursor movement
|
||||||
|
do_interrupt = True
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "auto_read_incoming"
|
||||||
|
):
|
||||||
|
do_interrupt = False
|
||||||
|
|
||||||
if curr_line.isspace():
|
if curr_line.isspace():
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("blank"), sound_icon="EmptyLine", interrupt=True, flush=False
|
_("blank"), sound_icon="EmptyLine", interrupt=do_interrupt, flush=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# ident
|
# ident
|
||||||
curr_ident = len(curr_line) - len(curr_line.lstrip())
|
curr_ident = len(curr_line) - len(curr_line.lstrip())
|
||||||
if self.lastIdent == -1:
|
if self.lastIdent == -1:
|
||||||
self.lastIdent = curr_ident
|
self.lastIdent = curr_ident
|
||||||
do_interrupt = True
|
|
||||||
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
"general", "auto_present_indent"
|
"general", "auto_present_indent"
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -29,8 +29,8 @@ class command:
|
|||||||
return
|
return
|
||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
return
|
return
|
||||||
if len(self.env["input"]["currInput"]) <= len(
|
if len(self.env["input"]["curr_input"]) <= len(
|
||||||
self.env["input"]["prevInput"]
|
self.env["input"]["prev_input"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
# if the filter is set
|
# if the filter is set
|
||||||
@@ -45,8 +45,8 @@ class command:
|
|||||||
.get_setting("keyboard", "interrupt_on_key_press_filter")
|
.get_setting("keyboard", "interrupt_on_key_press_filter")
|
||||||
.split(",")
|
.split(",")
|
||||||
)
|
)
|
||||||
for currInput in self.env["input"]["currInput"]:
|
for curr_key in self.env["input"]["curr_input"]:
|
||||||
if currInput not in filter_list:
|
if curr_key not in filter_list:
|
||||||
return
|
return
|
||||||
self.env["runtime"]["OutputManager"].interrupt_output()
|
self.env["runtime"]["OutputManager"].interrupt_output()
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class command:
|
|||||||
def run(self):
|
def run(self):
|
||||||
if self.env["runtime"]["InputManager"].no_key_pressed():
|
if self.env["runtime"]["InputManager"].no_key_pressed():
|
||||||
return
|
return
|
||||||
if len(self.env["input"]["prevInput"]) > 0:
|
if len(self.env["input"]["prev_input"]) > 0:
|
||||||
return
|
return
|
||||||
if not self.env["commandBuffer"]["enableSpeechOnKeypress"]:
|
if not self.env["commandBuffer"]["enableSpeechOnKeypress"]:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if (
|
if (
|
||||||
self.env["input"]["oldCapsLock"]
|
self.env["input"]["old_caps_lock"]
|
||||||
== self.env["input"]["newCapsLock"]
|
== self.env["input"]["new_caps_lock"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["input"]["newCapsLock"]:
|
if self.env["input"]["new_caps_lock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Capslock on"), interrupt=True
|
_("Capslock on"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ class command:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if (
|
if (
|
||||||
self.env["input"]["oldScrollLock"]
|
self.env["input"]["old_scroll_lock"]
|
||||||
== self.env["input"]["newScrollLock"]
|
== self.env["input"]["new_scroll_lock"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
if self.env["input"]["newScrollLock"]:
|
if self.env["input"]["new_scroll_lock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Scrolllock on"), interrupt=True
|
_("Scrolllock on"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class command:
|
|||||||
return "No description found"
|
return "No description found"
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]:
|
if self.env["input"]["old_num_lock"] == self.env["input"]["new_num_lock"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Only announce numlock changes if an actual numlock key was pressed
|
# Only announce numlock changes if an actual numlock key was pressed
|
||||||
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
|
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
|
||||||
current_input = self.env["input"]["currInput"]
|
current_input = self.env["input"]["curr_input"]
|
||||||
|
|
||||||
# Check if this is a genuine numlock key press by verifying:
|
# Check if this is a genuine numlock key press by verifying:
|
||||||
# 1. KEY_NUMLOCK is in the current input sequence
|
# 1. KEY_NUMLOCK is in the current input sequence
|
||||||
@@ -40,7 +40,7 @@ class command:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if is_genuine_numlock:
|
if is_genuine_numlock:
|
||||||
if self.env["input"]["newNumLock"]:
|
if self.env["input"]["new_num_lock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Numlock on"), interrupt=True
|
_("Numlock on"), interrupt=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,13 +40,19 @@ class command:
|
|||||||
):
|
):
|
||||||
self.detect_progress(self.env["screen"]["new_delta"])
|
self.detect_progress(self.env["screen"]["new_delta"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Silently ignore errors to avoid disrupting normal operation
|
# Log errors for debugging instead of silently ignoring
|
||||||
pass
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Progress detector error: " + str(e),
|
||||||
|
debug.DebugLevel.ERROR,
|
||||||
|
)
|
||||||
|
|
||||||
def is_real_progress_update(self):
|
def is_real_progress_update(self):
|
||||||
"""Check if this is a real progress update vs screen change/window switch"""
|
"""Check if this is a real progress update vs screen change/window switch"""
|
||||||
# If the screen/application changed, it's not a progress update
|
# If the screen/application changed, it's not a progress update
|
||||||
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Progress filter: screen change detected", debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If there was a large cursor movement, it's likely navigation, not
|
# If there was a large cursor movement, it's likely navigation, not
|
||||||
@@ -62,6 +68,10 @@ class command:
|
|||||||
)
|
)
|
||||||
# Large movements suggest navigation, not progress output
|
# Large movements suggest navigation, not progress output
|
||||||
if y_move > 2 or x_move > 20:
|
if y_move > 2 or x_move > 20:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"Progress filter: large cursor move y={y_move} x={x_move}",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if delta is too large (screen change) vs small incremental
|
# Check if delta is too large (screen change) vs small incremental
|
||||||
@@ -71,16 +81,27 @@ class command:
|
|||||||
if (
|
if (
|
||||||
delta_length > 200
|
delta_length > 200
|
||||||
): # Allow longer progress lines like Claude Code's status
|
): # Allow longer progress lines like Claude Code's status
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"Progress filter: delta too long ({delta_length})",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If delta contains newlines and is substantial, let incoming handler
|
# If delta contains newlines and is substantial, let incoming handler
|
||||||
# deal with it to avoid interfering with multi-line text output
|
# deal with it to avoid interfering with multi-line text output
|
||||||
if '\n' in delta_text and delta_length > 50:
|
if '\n' in delta_text and delta_length > 50:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
f"Progress filter: multiline delta ({delta_length} chars)",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if current line looks like a prompt - progress unlikely during
|
# Check if current line looks like a prompt - progress unlikely during
|
||||||
# prompts
|
# prompts
|
||||||
if self.is_current_line_prompt():
|
if self.is_current_line_prompt():
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Progress filter: prompt detected", debug.DebugLevel.INFO
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -284,14 +305,61 @@ class command:
|
|||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 6: Claude/Codex working indicators (bullets + "esc to interrupt")
|
# 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(
|
claude_progress_match = re.search(
|
||||||
r'^[\s]*[•◦][^\n]*\(\s*(?:\d+m\s+)?\d+s?\s+•\s+esc to interrupt[^)]*\)',
|
r'[*✢✽✶✻·•◦○●◆]\s+\w+.*?…\s*\(.*(?:esc|ctrl\+c) to interrupt.*\)',
|
||||||
text,
|
text,
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
if claude_progress_match:
|
if claude_progress_match:
|
||||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing Claude Code activity beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
|
self.play_activity_beep()
|
||||||
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pattern 6b: Claude Code tool invocation indicators (● Tool Name(...))
|
||||||
|
# Example: ● Web Search("query here")
|
||||||
|
tool_invocation_match = re.search(
|
||||||
|
r'[●○◉•◦]\s+(?:Web\s*Search|Read|Write|Edit|Bash|Glob|Grep|Task|WebFetch)\s*\(',
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if tool_invocation_match:
|
||||||
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing Claude Code tool invocation beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
|
self.play_activity_beep()
|
||||||
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pattern 6c: Bullet/white bullet activity lines (•/◦ ...)
|
||||||
|
bullet_activity_match = re.search(
|
||||||
|
(
|
||||||
|
r'^\s*[•◦]\s+.*(?:…|\.{3,}|\b(?:thinking|working|processing|'
|
||||||
|
r'analyzing|searching|reading|writing|planning|running|'
|
||||||
|
r'executing|updating|building|installing|compiling|downloading|'
|
||||||
|
r'reviewing|generating|responding|applying|fixing|editing|'
|
||||||
|
r'creating|preparing|checking|opening|loading|fetching|'
|
||||||
|
r'retrieving|scanning|indexing|summarizing)\b)'
|
||||||
|
),
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if bullet_activity_match:
|
||||||
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing bullet activity beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
self.play_activity_beep()
|
self.play_activity_beep()
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
@@ -331,6 +399,23 @@ class command:
|
|||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Pattern 10: Task status indicators (● Task Output, ○ Task Running, etc.)
|
||||||
|
# Matches bullet points with task-related status text
|
||||||
|
task_status_match = re.search(
|
||||||
|
r'[●○◉]\s+(?:Task\s+)?(?:Output|Running|Pending|Working|Processing)\s+[a-zA-Z0-9]+',
|
||||||
|
text,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if task_status_match:
|
||||||
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.5:
|
||||||
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
|
"Playing task status activity beep",
|
||||||
|
debug.DebugLevel.INFO,
|
||||||
|
)
|
||||||
|
self.play_activity_beep()
|
||||||
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
|
return
|
||||||
|
|
||||||
def play_progress_tone(self, percentage):
|
def play_progress_tone(self, percentage):
|
||||||
# Map 0-100% to 400-1200Hz frequency range
|
# Map 0-100% to 400-1200Hz frequency range
|
||||||
frequency = 400 + (percentage * 8)
|
frequency = 400 + (percentage * 8)
|
||||||
|
|||||||
@@ -158,6 +158,16 @@ class command:
|
|||||||
|
|
||||||
def _restore_speech(self):
|
def _restore_speech(self):
|
||||||
"""Helper method to restore speech when prompt is detected"""
|
"""Helper method to restore speech when prompt is detected"""
|
||||||
|
# If speech is already enabled, just clear flags to avoid unnecessary
|
||||||
|
# interrupts on prompt return
|
||||||
|
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
|
"speech", "enabled"
|
||||||
|
):
|
||||||
|
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
||||||
|
if "enableSpeechOnKeypress" in self.env["commandBuffer"]:
|
||||||
|
self.env["commandBuffer"]["enableSpeechOnKeypress"] = False
|
||||||
|
return
|
||||||
|
|
||||||
# Disable silence mode
|
# Disable silence mode
|
||||||
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
self.env["commandBuffer"]["silenceUntilPrompt"] = False
|
||||||
# Also disable the keypress-based speech restoration since we're
|
# Also disable the keypress-based speech restoration since we're
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ from fenrirscreenreader.core.i18n import _
|
|||||||
|
|
||||||
class command:
|
class command:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
self._update_times = []
|
||||||
|
self._line_count_times = []
|
||||||
|
self._batched_text = []
|
||||||
|
self._last_flush_time = 0
|
||||||
|
self._in_flood_mode = False
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
@@ -21,6 +25,73 @@ class command:
|
|||||||
def get_description(self):
|
def get_description(self):
|
||||||
return _("Announces incoming text changes")
|
return _("Announces incoming text changes")
|
||||||
|
|
||||||
|
def _reset_flood_state(self):
|
||||||
|
self._update_times = []
|
||||||
|
self._line_count_times = []
|
||||||
|
self._batched_text = []
|
||||||
|
self._last_flush_time = 0
|
||||||
|
self._in_flood_mode = False
|
||||||
|
|
||||||
|
def _is_rapid_updates(self):
|
||||||
|
current_time = time.time()
|
||||||
|
window = self.env["runtime"]["SettingsManager"].get_setting_as_float(
|
||||||
|
"speech", "rapid_update_window"
|
||||||
|
)
|
||||||
|
threshold = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
|
"speech", "rapid_update_threshold"
|
||||||
|
)
|
||||||
|
|
||||||
|
self._update_times = [
|
||||||
|
ts for ts in self._update_times if current_time - ts < window
|
||||||
|
]
|
||||||
|
self._update_times.append(current_time)
|
||||||
|
|
||||||
|
return len(self._update_times) >= threshold
|
||||||
|
|
||||||
|
def _is_high_volume(self, delta_text):
|
||||||
|
current_time = time.time()
|
||||||
|
window = self.env["runtime"]["SettingsManager"].get_setting_as_float(
|
||||||
|
"speech", "rapid_update_window"
|
||||||
|
)
|
||||||
|
threshold = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
|
"speech", "flood_line_threshold"
|
||||||
|
)
|
||||||
|
|
||||||
|
line_count = max(1, delta_text.count("\n") + 1)
|
||||||
|
self._line_count_times = [
|
||||||
|
(ts, count)
|
||||||
|
for ts, count in self._line_count_times
|
||||||
|
if current_time - ts < window
|
||||||
|
]
|
||||||
|
self._line_count_times.append((current_time, line_count))
|
||||||
|
|
||||||
|
total_lines = sum(count for _, count in self._line_count_times)
|
||||||
|
return total_lines >= threshold
|
||||||
|
|
||||||
|
def _add_to_batch(self, text):
|
||||||
|
new_lines = text.splitlines()
|
||||||
|
if text.endswith("\n"):
|
||||||
|
new_lines.append("")
|
||||||
|
self._batched_text.extend(new_lines)
|
||||||
|
|
||||||
|
max_lines = self.env["runtime"]["SettingsManager"].get_setting_as_int(
|
||||||
|
"speech", "max_batch_lines"
|
||||||
|
)
|
||||||
|
if len(self._batched_text) > max_lines:
|
||||||
|
self._batched_text = self._batched_text[-max_lines:]
|
||||||
|
|
||||||
|
def _flush_batch(self):
|
||||||
|
if not self._batched_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = "\n".join(self._batched_text)
|
||||||
|
self._batched_text = []
|
||||||
|
self._last_flush_time = time.time()
|
||||||
|
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
text, interrupt=False, flush=False
|
||||||
|
)
|
||||||
|
|
||||||
def _was_handled_by_tab_completion(self, delta_text):
|
def _was_handled_by_tab_completion(self, delta_text):
|
||||||
"""Check if this delta was already handled by tab completion to avoid duplicates"""
|
"""Check if this delta was already handled by tab completion to avoid duplicates"""
|
||||||
if "tabCompletion" not in self.env["commandBuffer"]:
|
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||||
@@ -51,6 +122,9 @@ class command:
|
|||||||
|
|
||||||
delta_text = self.env["screen"]["new_delta"]
|
delta_text = self.env["screen"]["new_delta"]
|
||||||
|
|
||||||
|
if self.env["runtime"]["ScreenManager"].is_screen_change():
|
||||||
|
self._reset_flood_state()
|
||||||
|
|
||||||
# Skip if tab completion already handled this delta
|
# Skip if tab completion already handled this delta
|
||||||
if self._was_handled_by_tab_completion(delta_text):
|
if self._was_handled_by_tab_completion(delta_text):
|
||||||
return
|
return
|
||||||
@@ -71,6 +145,29 @@ class command:
|
|||||||
# <= 2:
|
# <= 2:
|
||||||
if "\n" not in delta_text:
|
if "\n" not in delta_text:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
rapid = self._is_rapid_updates()
|
||||||
|
high_volume = self._is_high_volume(delta_text)
|
||||||
|
|
||||||
|
if (rapid and high_volume) or self._in_flood_mode:
|
||||||
|
if not self._in_flood_mode:
|
||||||
|
self._last_flush_time = time.time()
|
||||||
|
self._in_flood_mode = True
|
||||||
|
|
||||||
|
self._add_to_batch(delta_text)
|
||||||
|
|
||||||
|
interval = self.env["runtime"][
|
||||||
|
"SettingsManager"
|
||||||
|
].get_setting_as_float("speech", "batch_flush_interval")
|
||||||
|
if time.time() - self._last_flush_time >= interval:
|
||||||
|
self._flush_batch()
|
||||||
|
|
||||||
|
if not rapid or not high_volume:
|
||||||
|
if self._batched_text:
|
||||||
|
self._flush_batch()
|
||||||
|
self._in_flood_mode = False
|
||||||
|
return
|
||||||
|
|
||||||
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
delta_text, interrupt=False, flush=False
|
delta_text, interrupt=False, flush=False
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Fenrir TTY screen reader
|
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
|
||||||
|
|
||||||
|
|
||||||
class command:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return "No Description found"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
|
||||||
"promote", "enabled"
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if (
|
|
||||||
self.env["runtime"]["SettingsManager"]
|
|
||||||
.get_setting("promote", "list")
|
|
||||||
.strip(" \t\n")
|
|
||||||
== ""
|
|
||||||
):
|
|
||||||
return
|
|
||||||
if int(time.time() - self.env["input"]["lastInputTime"]) < self.env[
|
|
||||||
"runtime"
|
|
||||||
]["SettingsManager"].get_setting_as_int(
|
|
||||||
"promote", "inactive_timeout_sec"
|
|
||||||
):
|
|
||||||
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
|
|
||||||
@@ -21,7 +21,7 @@ class CursorManager:
|
|||||||
Return False if numlock is ON (let keys type numbers)
|
Return False if numlock is ON (let keys type numbers)
|
||||||
"""
|
"""
|
||||||
# Return False if numlock is ON
|
# Return False if numlock is ON
|
||||||
return not self.env["input"]["newNumLock"]
|
return not self.env["input"]["new_num_lock"]
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ from fenrirscreenreader.core.eventData import FenrirEventType
|
|||||||
class EventManager:
|
class EventManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.running = Value(c_bool, True)
|
self.running = Value(c_bool, True)
|
||||||
# Bounded queue to prevent memory exhaustion
|
self._eventQueue = Queue()
|
||||||
self._eventQueue = Queue(maxsize=100)
|
|
||||||
self.clean_event_queue()
|
self.clean_event_queue()
|
||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
@@ -107,23 +106,5 @@ class EventManager:
|
|||||||
return False
|
return False
|
||||||
if event == FenrirEventType.ignore:
|
if event == FenrirEventType.ignore:
|
||||||
return False
|
return False
|
||||||
# Use bounded queue - if full, this will block briefly or drop older
|
self._eventQueue.put({"Type": event, "data": data})
|
||||||
# events
|
|
||||||
try:
|
|
||||||
self._eventQueue.put({"Type": event, "data": data}, timeout=0.1)
|
|
||||||
except Exception as e:
|
|
||||||
# Queue full - drop oldest event and add new one for critical
|
|
||||||
# events
|
|
||||||
if event in [
|
|
||||||
FenrirEventType.screen_update,
|
|
||||||
FenrirEventType.keyboard_input,
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
self._eventQueue.get_nowait() # Remove oldest
|
|
||||||
self._eventQueue.put(
|
|
||||||
{"Type": event, "data": data}, timeout=0.1
|
|
||||||
)
|
|
||||||
except BaseException:
|
|
||||||
pass # If still can't add, drop the event
|
|
||||||
# For non-critical events, just drop them if queue is full
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ class FenrirManager:
|
|||||||
].get_input_event()
|
].get_input_event()
|
||||||
|
|
||||||
if event["data"]:
|
if event["data"]:
|
||||||
event["data"]["EventName"] = self.environment["runtime"][
|
event["data"]["event_name"] = self.environment["runtime"][
|
||||||
"InputManager"
|
"InputManager"
|
||||||
].convert_event_name(event["data"]["EventName"])
|
].convert_event_name(event["data"]["event_name"])
|
||||||
self.environment["runtime"]["InputManager"].handle_input_event(
|
self.environment["runtime"]["InputManager"].handle_input_event(
|
||||||
event["data"]
|
event["data"]
|
||||||
)
|
)
|
||||||
@@ -121,8 +121,8 @@ class FenrirManager:
|
|||||||
self.environment["runtime"]["InputManager"].write_event_buffer()
|
self.environment["runtime"]["InputManager"].write_event_buffer()
|
||||||
self.environment["runtime"]["InputManager"].handle_device_grab()
|
self.environment["runtime"]["InputManager"].handle_device_grab()
|
||||||
|
|
||||||
if self.environment["input"]["keyForeward"] > 0:
|
if self.environment["input"]["key_forward"] > 0:
|
||||||
self.environment["input"]["keyForeward"] -= 1
|
self.environment["input"]["key_forward"] -= 1
|
||||||
|
|
||||||
self.environment["runtime"]["CommandManager"].execute_default_trigger(
|
self.environment["runtime"]["CommandManager"].execute_default_trigger(
|
||||||
"onKeyInput"
|
"onKeyInput"
|
||||||
@@ -265,11 +265,11 @@ class FenrirManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def detect_shortcut_command(self):
|
def detect_shortcut_command(self):
|
||||||
if self.environment["input"]["keyForeward"] > 0:
|
if self.environment["input"]["key_forward"] > 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(self.environment["input"]["prevInput"]) > len(
|
if len(self.environment["input"]["prev_input"]) > len(
|
||||||
self.environment["input"]["currInput"]
|
self.environment["input"]["curr_input"]
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -283,7 +283,7 @@ class FenrirManager:
|
|||||||
].no_key_pressed():
|
].no_key_pressed():
|
||||||
if self.singleKeyCommand:
|
if self.singleKeyCommand:
|
||||||
self.singleKeyCommand = (
|
self.singleKeyCommand = (
|
||||||
len(self.environment["input"]["currInput"]) == 1
|
len(self.environment["input"]["curr_input"]) == 1
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
|
|||||||
@@ -9,28 +9,30 @@ import time
|
|||||||
from fenrirscreenreader.core import debug
|
from fenrirscreenreader.core import debug
|
||||||
|
|
||||||
input_data = {
|
input_data = {
|
||||||
"currInput": [],
|
"curr_input": [],
|
||||||
"prevDeepestInput": [],
|
"prev_input": [],
|
||||||
"eventBuffer": [],
|
"prev_deepest_input": [],
|
||||||
|
"event_buffer": [],
|
||||||
"shortcut_repeat": 0,
|
"shortcut_repeat": 0,
|
||||||
"fenrirKey": [],
|
"fenrir_key": [],
|
||||||
"scriptKey": [],
|
"script_key": [],
|
||||||
"keyForeward": 0,
|
"key_forward": 0,
|
||||||
"lastInputTime": time.time(),
|
"last_input_time": time.time(),
|
||||||
"oldNumLock": True,
|
"old_num_lock": True,
|
||||||
"newNumLock": True,
|
"new_num_lock": True,
|
||||||
"oldScrollLock": True,
|
"old_scroll_lock": True,
|
||||||
"newScrollLock": True,
|
"new_scroll_lock": True,
|
||||||
"oldCapsLock": False,
|
"old_caps_lock": False,
|
||||||
"newCapsLock": False,
|
"new_caps_lock": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
input_event = {
|
input_event = {
|
||||||
"EventName": "",
|
"event_name": "",
|
||||||
"EventValue": "",
|
"event_value": "",
|
||||||
"EventSec": 0,
|
"event_sec": 0,
|
||||||
"EventUsec": 0,
|
"event_usec": 0,
|
||||||
"EventState": 0,
|
"event_state": 0,
|
||||||
|
"event_type": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
key_names = [
|
key_names = [
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class InputDriver:
|
|||||||
def clear_event_buffer(self):
|
def clear_event_buffer(self):
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
return
|
return
|
||||||
del self.env["input"]["eventBuffer"][:]
|
del self.env["input"]["event_buffer"][:]
|
||||||
|
|
||||||
def update_input_devices(self, new_devices=None, init=False):
|
def update_input_devices(self, new_devices=None, init=False):
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
|
|||||||
@@ -43,18 +43,18 @@ class InputManager:
|
|||||||
self.update_input_devices()
|
self.update_input_devices()
|
||||||
|
|
||||||
# init LEDs with current state
|
# init LEDs with current state
|
||||||
self.env["input"]["newNumLock"] = self.env["runtime"][
|
self.env["input"]["new_num_lock"] = self.env["runtime"][
|
||||||
"InputDriver"
|
"InputDriver"
|
||||||
].get_led_state()
|
].get_led_state()
|
||||||
self.env["input"]["oldNumLock"] = self.env["input"]["newNumLock"]
|
self.env["input"]["old_num_lock"] = self.env["input"]["new_num_lock"]
|
||||||
self.env["input"]["newCapsLock"] = self.env["runtime"][
|
self.env["input"]["new_caps_lock"] = self.env["runtime"][
|
||||||
"InputDriver"
|
"InputDriver"
|
||||||
].get_led_state(1)
|
].get_led_state(1)
|
||||||
self.env["input"]["oldCapsLock"] = self.env["input"]["newCapsLock"]
|
self.env["input"]["old_caps_lock"] = self.env["input"]["new_caps_lock"]
|
||||||
self.env["input"]["newScrollLock"] = self.env["runtime"][
|
self.env["input"]["new_scroll_lock"] = self.env["runtime"][
|
||||||
"InputDriver"
|
"InputDriver"
|
||||||
].get_led_state(2)
|
].get_led_state(2)
|
||||||
self.env["input"]["oldScrollLock"] = self.env["input"]["newScrollLock"]
|
self.env["input"]["old_scroll_lock"] = self.env["input"]["new_scroll_lock"]
|
||||||
self.lastDeepestInput = []
|
self.lastDeepestInput = []
|
||||||
self.lastEvent = None
|
self.lastEvent = None
|
||||||
self.env["input"]["shortcut_repeat"] = 1
|
self.env["input"]["shortcut_repeat"] = 1
|
||||||
@@ -84,7 +84,7 @@ class InputManager:
|
|||||||
self.set_execute_device_grab()
|
self.set_execute_device_grab()
|
||||||
if not self.executeDeviceGrab:
|
if not self.executeDeviceGrab:
|
||||||
return
|
return
|
||||||
if self.env["input"]["eventBuffer"] != []:
|
if self.env["input"]["event_buffer"] != []:
|
||||||
return
|
return
|
||||||
if not self.no_key_pressed():
|
if not self.no_key_pressed():
|
||||||
return
|
return
|
||||||
@@ -176,36 +176,36 @@ class InputManager:
|
|||||||
return
|
return
|
||||||
self.lastEvent = event_data
|
self.lastEvent = event_data
|
||||||
# a hang apears.. try to fix
|
# a hang apears.. try to fix
|
||||||
if self.env["input"]["eventBuffer"] == []:
|
if self.env["input"]["event_buffer"] == []:
|
||||||
if self.env["input"]["currInput"] != []:
|
if self.env["input"]["curr_input"] != []:
|
||||||
self.env["input"]["currInput"] = []
|
self.env["input"]["curr_input"] = []
|
||||||
self.env["input"]["shortcut_repeat"] = 1
|
self.env["input"]["shortcut_repeat"] = 1
|
||||||
|
|
||||||
self.env["input"]["prevInput"] = self.env["input"]["currInput"].copy()
|
self.env["input"]["prev_input"] = self.env["input"]["curr_input"].copy()
|
||||||
if event_data["EventState"] == 0:
|
if event_data["event_state"] == 0:
|
||||||
if event_data["EventName"] in self.env["input"]["currInput"]:
|
if event_data["event_name"] in self.env["input"]["curr_input"]:
|
||||||
self.env["input"]["currInput"].remove(event_data["EventName"])
|
self.env["input"]["curr_input"].remove(event_data["event_name"])
|
||||||
if len(self.env["input"]["currInput"]) > 1:
|
if len(self.env["input"]["curr_input"]) > 1:
|
||||||
self.env["input"]["currInput"] = sorted(
|
self.env["input"]["curr_input"] = sorted(
|
||||||
self.env["input"]["currInput"]
|
self.env["input"]["curr_input"]
|
||||||
)
|
)
|
||||||
elif len(self.env["input"]["currInput"]) == 0:
|
elif len(self.env["input"]["curr_input"]) == 0:
|
||||||
self.env["input"]["shortcut_repeat"] = 1
|
self.env["input"]["shortcut_repeat"] = 1
|
||||||
self.lastInputTime = time.time()
|
self.lastInputTime = time.time()
|
||||||
elif event_data["EventState"] == 1:
|
elif event_data["event_state"] == 1:
|
||||||
if not event_data["EventName"] in self.env["input"]["currInput"]:
|
if not event_data["event_name"] in self.env["input"]["curr_input"]:
|
||||||
self.env["input"]["currInput"].append(event_data["EventName"])
|
self.env["input"]["curr_input"].append(event_data["event_name"])
|
||||||
if len(self.env["input"]["currInput"]) > 1:
|
if len(self.env["input"]["curr_input"]) > 1:
|
||||||
self.env["input"]["currInput"] = sorted(
|
self.env["input"]["curr_input"] = sorted(
|
||||||
self.env["input"]["currInput"]
|
self.env["input"]["curr_input"]
|
||||||
)
|
)
|
||||||
if len(self.lastDeepestInput) < len(
|
if len(self.lastDeepestInput) < len(
|
||||||
self.env["input"]["currInput"]
|
self.env["input"]["curr_input"]
|
||||||
):
|
):
|
||||||
self.set_last_deepest_input(
|
self.set_last_deepest_input(
|
||||||
self.env["input"]["currInput"].copy()
|
self.env["input"]["curr_input"].copy()
|
||||||
)
|
)
|
||||||
elif self.lastDeepestInput == self.env["input"]["currInput"]:
|
elif self.lastDeepestInput == self.env["input"]["curr_input"]:
|
||||||
if time.time() - self.lastInputTime <= self.env["runtime"][
|
if time.time() - self.lastInputTime <= self.env["runtime"][
|
||||||
"SettingsManager"
|
"SettingsManager"
|
||||||
].get_setting_as_float("keyboard", "double_tap_timeout"):
|
].get_setting_as_float("keyboard", "double_tap_timeout"):
|
||||||
@@ -214,37 +214,37 @@ class InputManager:
|
|||||||
self.env["input"]["shortcut_repeat"] = 1
|
self.env["input"]["shortcut_repeat"] = 1
|
||||||
self.handle_led_states(event_data)
|
self.handle_led_states(event_data)
|
||||||
self.lastInputTime = time.time()
|
self.lastInputTime = time.time()
|
||||||
elif event_data["EventState"] == 2:
|
elif event_data["event_state"] == 2:
|
||||||
self.lastInputTime = time.time()
|
self.lastInputTime = time.time()
|
||||||
|
|
||||||
self.env["input"]["oldNumLock"] = self.env["input"]["newNumLock"]
|
self.env["input"]["old_num_lock"] = self.env["input"]["new_num_lock"]
|
||||||
self.env["input"]["newNumLock"] = self.env["runtime"][
|
self.env["input"]["new_num_lock"] = self.env["runtime"][
|
||||||
"InputDriver"
|
"InputDriver"
|
||||||
].get_led_state()
|
].get_led_state()
|
||||||
self.env["input"]["oldCapsLock"] = self.env["input"]["newCapsLock"]
|
self.env["input"]["old_caps_lock"] = self.env["input"]["new_caps_lock"]
|
||||||
self.env["input"]["newCapsLock"] = self.env["runtime"][
|
self.env["input"]["new_caps_lock"] = self.env["runtime"][
|
||||||
"InputDriver"
|
"InputDriver"
|
||||||
].get_led_state(1)
|
].get_led_state(1)
|
||||||
self.env["input"]["oldScrollLock"] = self.env["input"]["newScrollLock"]
|
self.env["input"]["old_scroll_lock"] = self.env["input"]["new_scroll_lock"]
|
||||||
self.env["input"]["newScrollLock"] = self.env["runtime"][
|
self.env["input"]["new_scroll_lock"] = self.env["runtime"][
|
||||||
"InputDriver"
|
"InputDriver"
|
||||||
].get_led_state(2)
|
].get_led_state(2)
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
"currInput " + str(self.env["input"]["currInput"]),
|
"curr_input " + str(self.env["input"]["curr_input"]),
|
||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
)
|
)
|
||||||
if self.no_key_pressed():
|
if self.no_key_pressed():
|
||||||
self.env["input"]["prevInput"] = []
|
self.env["input"]["prev_input"] = []
|
||||||
|
|
||||||
def handle_led_states(self, m_event):
|
def handle_led_states(self, m_event):
|
||||||
if self.curr_key_is_modifier():
|
if self.curr_key_is_modifier():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
if m_event["EventName"] == "KEY_NUMLOCK":
|
if m_event["event_name"] == "KEY_NUMLOCK":
|
||||||
self.env["runtime"]["InputDriver"].toggle_led_state()
|
self.env["runtime"]["InputDriver"].toggle_led_state()
|
||||||
elif m_event["EventName"] == "KEY_CAPSLOCK":
|
elif m_event["event_name"] == "KEY_CAPSLOCK":
|
||||||
self.env["runtime"]["InputDriver"].toggle_led_state(1)
|
self.env["runtime"]["InputDriver"].toggle_led_state(1)
|
||||||
elif m_event["EventName"] == "KEY_SCROLLLOCK":
|
elif m_event["event_name"] == "KEY_SCROLLLOCK":
|
||||||
self.env["runtime"]["InputDriver"].toggle_led_state(2)
|
self.env["runtime"]["InputDriver"].toggle_led_state(2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
@@ -368,11 +368,11 @@ class InputManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def no_key_pressed(self):
|
def no_key_pressed(self):
|
||||||
return self.env["input"]["currInput"] == []
|
return self.env["input"]["curr_input"] == []
|
||||||
|
|
||||||
def is_key_press(self):
|
def is_key_press(self):
|
||||||
return (self.env["input"]["prevInput"] == []) and (
|
return (self.env["input"]["prev_input"] == []) and (
|
||||||
self.env["input"]["currInput"] != []
|
self.env["input"]["curr_input"] != []
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_prev_deepest_shortcut(self):
|
def get_prev_deepest_shortcut(self):
|
||||||
@@ -384,7 +384,7 @@ class InputManager:
|
|||||||
def get_prev_shortcut(self):
|
def get_prev_shortcut(self):
|
||||||
shortcut = []
|
shortcut = []
|
||||||
shortcut.append(self.env["input"]["shortcut_repeat"])
|
shortcut.append(self.env["input"]["shortcut_repeat"])
|
||||||
shortcut.append(self.env["input"]["prevInput"])
|
shortcut.append(self.env["input"]["prev_input"])
|
||||||
return str(shortcut)
|
return str(shortcut)
|
||||||
|
|
||||||
def get_curr_shortcut(self, inputSequence=None):
|
def get_curr_shortcut(self, inputSequence=None):
|
||||||
@@ -430,16 +430,16 @@ class InputManager:
|
|||||||
if not self.env["runtime"][
|
if not self.env["runtime"][
|
||||||
"CursorManager"
|
"CursorManager"
|
||||||
].should_process_numpad_commands():
|
].should_process_numpad_commands():
|
||||||
for key in self.env["input"]["currInput"]:
|
for key in self.env["input"]["curr_input"]:
|
||||||
if key in numpad_keys:
|
if key in numpad_keys:
|
||||||
# Return an empty/invalid shortcut that won't match any
|
# Return an empty/invalid shortcut that won't match any
|
||||||
# command
|
# command
|
||||||
return "[]"
|
return "[]"
|
||||||
|
|
||||||
shortcut.append(self.env["input"]["currInput"])
|
shortcut.append(self.env["input"]["curr_input"])
|
||||||
|
|
||||||
if len(self.env["input"]["prevInput"]) < len(
|
if len(self.env["input"]["prev_input"]) < len(
|
||||||
self.env["input"]["currInput"]
|
self.env["input"]["curr_input"]
|
||||||
):
|
):
|
||||||
if self.env["input"][
|
if self.env["input"][
|
||||||
"shortcut_repeat"
|
"shortcut_repeat"
|
||||||
@@ -447,7 +447,7 @@ class InputManager:
|
|||||||
shortcut = []
|
shortcut = []
|
||||||
self.env["input"]["shortcut_repeat"] = 1
|
self.env["input"]["shortcut_repeat"] = 1
|
||||||
shortcut.append(self.env["input"]["shortcut_repeat"])
|
shortcut.append(self.env["input"]["shortcut_repeat"])
|
||||||
shortcut.append(self.env["input"]["currInput"])
|
shortcut.append(self.env["input"]["curr_input"])
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
"curr_shortcut " + str(shortcut), debug.DebugLevel.INFO
|
"curr_shortcut " + str(shortcut), debug.DebugLevel.INFO
|
||||||
)
|
)
|
||||||
@@ -456,15 +456,15 @@ class InputManager:
|
|||||||
def curr_key_is_modifier(self):
|
def curr_key_is_modifier(self):
|
||||||
if len(self.get_last_deepest_input()) != 1:
|
if len(self.get_last_deepest_input()) != 1:
|
||||||
return False
|
return False
|
||||||
return (self.env["input"]["currInput"][0] == "KEY_FENRIR") or (
|
return (self.env["input"]["curr_input"][0] == "KEY_FENRIR") or (
|
||||||
self.env["input"]["currInput"][0] == "KEY_SCRIPT"
|
self.env["input"]["curr_input"][0] == "KEY_SCRIPT"
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_fenrir_key(self, event_name):
|
def is_fenrir_key(self, event_name):
|
||||||
return event_name in self.env["input"]["fenrirKey"]
|
return event_name in self.env["input"]["fenrir_key"]
|
||||||
|
|
||||||
def is_script_key(self, event_name):
|
def is_script_key(self, event_name):
|
||||||
return event_name in self.env["input"]["scriptKey"]
|
return event_name in self.env["input"]["script_key"]
|
||||||
|
|
||||||
def get_command_for_shortcut(self, shortcut):
|
def get_command_for_shortcut(self, shortcut):
|
||||||
if not self.shortcut_exists(shortcut):
|
if not self.shortcut_exists(shortcut):
|
||||||
@@ -477,8 +477,8 @@ class InputManager:
|
|||||||
if not event_data:
|
if not event_data:
|
||||||
return
|
return
|
||||||
key_name = ""
|
key_name = ""
|
||||||
if event_data["EventState"] == 1:
|
if event_data["event_state"] == 1:
|
||||||
key_name = event_data["EventName"].lower()
|
key_name = event_data["event_name"].lower()
|
||||||
if key_name.startswith("key_"):
|
if key_name.startswith("key_"):
|
||||||
key_name = key_name[4:]
|
key_name = key_name[4:]
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
|||||||
@@ -65,15 +65,44 @@ class OutputManager:
|
|||||||
return
|
return
|
||||||
if (len(text) > 1) and (text.strip(string.whitespace) == ""):
|
if (len(text) > 1) and (text.strip(string.whitespace) == ""):
|
||||||
return
|
return
|
||||||
to_announce_capital = announce_capital and text[0].isupper()
|
is_capital = self._should_announce_capital(text, announce_capital)
|
||||||
if to_announce_capital:
|
use_pitch_for_capital = False
|
||||||
if self.play_sound_icon("capital", False):
|
|
||||||
to_announce_capital = False
|
if is_capital:
|
||||||
|
indicator = self.env["runtime"]["SettingsManager"].get_setting(
|
||||||
|
"speech", "capital_indicator"
|
||||||
|
).lower()
|
||||||
|
|
||||||
|
if indicator == "none":
|
||||||
|
pass # No indication
|
||||||
|
elif indicator == "beep":
|
||||||
|
# Play beep with interrupt=True to fix stacking
|
||||||
|
self.play_sound_icon("capital", True)
|
||||||
|
elif indicator == "pitch":
|
||||||
|
use_pitch_for_capital = True
|
||||||
|
elif indicator == "both":
|
||||||
|
self.play_sound_icon("capital", True)
|
||||||
|
use_pitch_for_capital = True
|
||||||
|
else:
|
||||||
|
# Default to pitch for unknown values
|
||||||
|
use_pitch_for_capital = True
|
||||||
|
|
||||||
self.last_echo = text
|
self.last_echo = text
|
||||||
self.speak_text(
|
self.speak_text(
|
||||||
text, interrupt, ignore_punctuation, to_announce_capital, flush
|
text, interrupt, ignore_punctuation, use_pitch_for_capital, flush
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _should_announce_capital(self, text, announce_capital):
|
||||||
|
if not announce_capital or not text:
|
||||||
|
return False
|
||||||
|
if len(text) == 1:
|
||||||
|
return text.isupper()
|
||||||
|
if any(char.isspace() for char in text):
|
||||||
|
return False
|
||||||
|
if not any(char.isalpha() for char in text):
|
||||||
|
return False
|
||||||
|
return text.isupper()
|
||||||
|
|
||||||
def get_last_echo(self):
|
def get_last_echo(self):
|
||||||
return self.last_echo
|
return self.last_echo
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,18 @@ settings_data = {
|
|||||||
"rate": 0.75,
|
"rate": 0.75,
|
||||||
"pitch": 0.5,
|
"pitch": 0.5,
|
||||||
"capital_pitch": 0.8,
|
"capital_pitch": 0.8,
|
||||||
|
"capital_indicator": "pitch",
|
||||||
"volume": 1.0,
|
"volume": 1.0,
|
||||||
"module": "",
|
"module": "",
|
||||||
"voice": "en-us",
|
"voice": "en-us",
|
||||||
"language": "",
|
"language": "",
|
||||||
"auto_read_incoming": True,
|
"auto_read_incoming": True,
|
||||||
"read_numbers_as_digits": False,
|
"read_numbers_as_digits": False,
|
||||||
|
"rapid_update_threshold": 5,
|
||||||
|
"rapid_update_window": 0.3,
|
||||||
|
"batch_flush_interval": 0.5,
|
||||||
|
"max_batch_lines": 100,
|
||||||
|
"flood_line_threshold": 500,
|
||||||
"generic_speech_command": 'espeak -a fenrirVolume -s fenrirRate -p fenrirPitch -v fenrirVoice "fenrirText"',
|
"generic_speech_command": 'espeak -a fenrirVolume -s fenrirRate -p fenrirPitch -v fenrirVoice "fenrirText"',
|
||||||
"fenrir_min_volume": 0,
|
"fenrir_min_volume": 0,
|
||||||
"fenrir_max_volume": 200,
|
"fenrir_max_volume": 200,
|
||||||
@@ -97,11 +103,6 @@ settings_data = {
|
|||||||
"vmenu_path": "",
|
"vmenu_path": "",
|
||||||
"quick_menu": "speech#rate;speech#pitch;speech#volume",
|
"quick_menu": "speech#rate;speech#pitch;speech#volume",
|
||||||
},
|
},
|
||||||
"promote": {
|
|
||||||
"enabled": True,
|
|
||||||
"inactive_timeout_sec": 120,
|
|
||||||
"list": "",
|
|
||||||
},
|
|
||||||
"time": {
|
"time": {
|
||||||
"enabled": False,
|
"enabled": False,
|
||||||
"present_time": True,
|
"present_time": True,
|
||||||
|
|||||||
@@ -282,15 +282,15 @@ class SettingsManager:
|
|||||||
keys = keys.upper()
|
keys = keys.upper()
|
||||||
key_list = keys.split(",")
|
key_list = keys.split(",")
|
||||||
for key in key_list:
|
for key in key_list:
|
||||||
if key not in self.env["input"]["fenrirKey"]:
|
if key not in self.env["input"]["fenrir_key"]:
|
||||||
self.env["input"]["fenrirKey"].append(key)
|
self.env["input"]["fenrir_key"].append(key)
|
||||||
|
|
||||||
def set_script_keys(self, keys):
|
def set_script_keys(self, keys):
|
||||||
keys = keys.upper()
|
keys = keys.upper()
|
||||||
key_list = keys.split(",")
|
key_list = keys.split(",")
|
||||||
for key in key_list:
|
for key in key_list:
|
||||||
if key not in self.env["input"]["scriptKey"]:
|
if key not in self.env["input"]["script_key"]:
|
||||||
self.env["input"]["scriptKey"].append(key)
|
self.env["input"]["script_key"].append(key)
|
||||||
|
|
||||||
def reset_setting_arg_dict(self):
|
def reset_setting_arg_dict(self):
|
||||||
self.settingArgDict = {}
|
self.settingArgDict = {}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2025.12.14"
|
version = "2026.01.28"
|
||||||
code_name = "master"
|
code_name = "master"
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class driver(inputDriver):
|
|||||||
def clear_event_buffer(self):
|
def clear_event_buffer(self):
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
return
|
return
|
||||||
del self.env["input"]["eventBuffer"][:]
|
del self.env["input"]["event_buffer"][:]
|
||||||
print("Input Debug Driver: clear_event_buffer")
|
print("Input Debug Driver: clear_event_buffer")
|
||||||
|
|
||||||
def update_input_devices(self, new_devices=None, init=False):
|
def update_input_devices(self, new_devices=None, init=False):
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ class driver(inputDriver):
|
|||||||
"input_watchdog: EVENT:" + str(event),
|
"input_watchdog: EVENT:" + str(event),
|
||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
)
|
)
|
||||||
self.env["input"]["eventBuffer"].append(
|
self.env["input"]["event_buffer"].append(
|
||||||
[device, udevice, event]
|
[device, udevice, event]
|
||||||
)
|
)
|
||||||
if event.type == evdev.events.EV_KEY:
|
if event.type == evdev.events.EV_KEY:
|
||||||
@@ -271,11 +271,11 @@ class driver(inputDriver):
|
|||||||
event = device.read_one()
|
event = device.read_one()
|
||||||
continue
|
continue
|
||||||
if not isinstance(
|
if not isinstance(
|
||||||
curr_map_event["EventName"], str
|
curr_map_event["event_name"], str
|
||||||
):
|
):
|
||||||
event = device.read_one()
|
event = device.read_one()
|
||||||
continue
|
continue
|
||||||
if curr_map_event["EventState"] in [0, 1, 2]:
|
if curr_map_event["event_state"] in [0, 1, 2]:
|
||||||
event_queue.put(
|
event_queue.put(
|
||||||
{
|
{
|
||||||
"Type": FenrirEventType.keyboard_input,
|
"Type": FenrirEventType.keyboard_input,
|
||||||
@@ -301,7 +301,7 @@ class driver(inputDriver):
|
|||||||
def write_event_buffer(self):
|
def write_event_buffer(self):
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
return
|
return
|
||||||
for iDevice, uDevice, event in self.env["input"]["eventBuffer"]:
|
for iDevice, uDevice, event in self.env["input"]["event_buffer"]:
|
||||||
try:
|
try:
|
||||||
if uDevice:
|
if uDevice:
|
||||||
if self.gDevices[iDevice.fd]:
|
if self.gDevices[iDevice.fd]:
|
||||||
@@ -554,18 +554,18 @@ class driver(inputDriver):
|
|||||||
m_event = inputData.input_event
|
m_event = inputData.input_event
|
||||||
try:
|
try:
|
||||||
# mute is a list = ['KEY_MIN_INTERESTING', 'KEY_MUTE']
|
# mute is a list = ['KEY_MIN_INTERESTING', 'KEY_MUTE']
|
||||||
m_event["EventName"] = evdev.ecodes.keys[event.code]
|
m_event["event_name"] = evdev.ecodes.keys[event.code]
|
||||||
if isinstance(m_event["EventName"], list):
|
if isinstance(m_event["event_name"], list):
|
||||||
if len(m_event["EventName"]) > 0:
|
if len(m_event["event_name"]) > 0:
|
||||||
m_event["EventName"] = m_event["EventName"][0]
|
m_event["event_name"] = m_event["event_name"][0]
|
||||||
if isinstance(m_event["EventName"], list):
|
if isinstance(m_event["event_name"], list):
|
||||||
if len(m_event["EventName"]) > 0:
|
if len(m_event["event_name"]) > 0:
|
||||||
m_event["EventName"] = m_event["EventName"][0]
|
m_event["event_name"] = m_event["event_name"][0]
|
||||||
m_event["EventValue"] = event.code
|
m_event["event_value"] = event.code
|
||||||
m_event["EventSec"] = event.sec
|
m_event["event_sec"] = event.sec
|
||||||
m_event["EventUsec"] = event.usec
|
m_event["event_usec"] = event.usec
|
||||||
m_event["EventState"] = event.value
|
m_event["event_state"] = event.value
|
||||||
m_event["EventType"] = event.type
|
m_event["event_type"] = event.type
|
||||||
return m_event
|
return m_event
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user