33 Commits

Author SHA1 Message Date
Storm Dragon
96cdda99c4 Separate menu entries for some settings, improves usability. Fixed device detection for devices that do not contain what they do in their name, e.g. numpads that are not listed as numpads. This hopefully fixes a bug with some external numpads. 2025-09-12 12:09:00 -04:00
Storm Dragon
0658d37ae8 I don't wanna say this too loud, but I think tab completion is much more reliable now. No more not reading when you press tab and something appears. 2025-08-31 20:45:32 -04:00
Storm Dragon
a6bb3e1301 A bit of code cleanup. Nothing should be changed at all functionally. 2025-08-31 20:30:06 -04:00
Storm Dragon
5ff653bd00 Progress bar and commit validator updates. 2025-08-31 14:54:07 -04:00
Storm Dragon
356f4b01c1 Got the version file wrong again. Need to be more careful. 2025-08-31 14:44:46 -04:00
Storm Dragon
c7ad4d9200 merged to master. 2025-08-31 14:39:26 -04:00
Storm Dragon
d274fe78f3 Rebind the keyboard layout switcher to fenrir+control+f4. 2025-08-31 14:37:48 -04:00
Storm Dragon
f5344a7227 removing keyboard layouts that are no longer used 2025-08-23 18:30:35 -04:00
Storm Dragon
90ffc2fc08 removing keyboard layouts that are no longer used 2025-08-23 18:29:42 -04:00
Storm Dragon
b635f7538b Latest changes merged. Minor fixes to progress bar detection. Fixed my old habbit of camel case variable names. :) 2025-08-23 12:20:05 -04:00
Storm Dragon
7f473b72a7 Minor updates to progress bar detection. 2025-08-23 12:16:10 -04:00
Storm Dragon
e255651c28 Merged testing. 2025-08-22 00:29:02 -04:00
Storm Dragon
b3d73102fc Fenrir will now read dots in the middle of or at the beginning of words regardless of punctuation settings. 2025-08-21 23:29:45 -04:00
Storm Dragon
78c1cbbb6b Improved progress bar detection for curl. Experimental flood protection for updates. 2025-08-21 19:59:40 -04:00
Storm Dragon
8c26d93001 Progress bar monitoring updates. Hopefully fixed some false positives, and updated claude-code progress monitoring. 2025-08-20 14:33:22 -04:00
Storm Dragon
98b9c56af7 Read all code added. It's definiately a work in progress and does not function currently. 2025-08-04 14:41:14 -04:00
Storm Dragon
8bada48a09 Fixed errors in README. Moved the audio configuration script stuff nearer the top. 2025-08-04 14:36:43 -04:00
Storm Dragon
0a2c8472c0 Fixed errors in README. Moved the audio configuration script stuff nearer the top. 2025-08-04 14:36:06 -04:00
Storm Dragon
e9a0101fe7 Merged README.md 2025-08-04 14:26:55 -04:00
Storm Dragon
69eade3327 Updated documentation for stand-alone pipewire or pulse configuration scripts. 2025-08-04 14:24:41 -04:00
Storm Dragon
73f67c2a04 Multiple fixes to indentation beep code, including volume for lower ranged beeps using the gstreamer driver. 2025-08-04 12:38:14 -04:00
Storm Dragon
9ef9d762f4 A few emoji updates. 2025-07-31 03:16:02 -04:00
Storm Dragon
84293db6dc Another shot at fixing multiple numpads. 2025-07-25 14:17:17 -04:00
Storm Dragon
914535d12b A few bug fixes, better checking in place to make sure syntax and other errors do not make it to commits. 2025-07-24 18:34:12 -04:00
Storm Dragon
94a1acbaca A few improvements to validation. 2025-07-24 14:05:51 -04:00
Storm Dragon
8c233e0385 Some syntax errors fixed. Syntax checking added. Release checklist created. 2025-07-24 13:52:10 -04:00
Storm Dragon
b6a9e1a692 Hopefully fixed problems with connection external numpads. 2025-07-23 00:01:46 -04:00
Storm Dragon
d41ea8388f Fixed url exclusion detection for progress bars. 2025-07-21 06:58:49 -04:00
Storm Dragon
2dd732dc9d Emojis added, improvements to pyt mode. 2025-07-19 16:50:53 -04:00
Storm Dragon
b68a28e857 Added more emojis. 2025-07-17 11:11:35 -04:00
Storm Dragon
8c668cc0cc Final batch of pty driver fixes... For now. 2025-07-16 19:48:48 -04:00
Storm Dragon
579bf0f0f0 More work on the pty driver. 2025-07-16 19:43:07 -04:00
Storm Dragon
d1bad818cd initial improvements to pty driver. Improved clipboard handling of multibyte characters. Added emoji menu to vmenu. It places the emoji to the clipboard to be used wherever. 2025-07-16 19:35:26 -04:00
190 changed files with 7010 additions and 836 deletions

View File

@@ -134,6 +134,58 @@ By default Fenrir uses:
- **Input driver**: evdevDriver (Linux) or ptyDriver (other platforms)
- **Screen driver**: vcsaDriver (Linux TTY) or ptyDriver (terminal emulation)
## Audio Configuration
Both PulseAudio and PipeWire require special configuration to allow console applications running as root (like Fenrir) to route audio to your regular user session. This is normal audio system behavior, not a Fenrir issue.
### Quick Setup - Direct Script Download
For non-Fenrir users or quick setup, download and run these scripts directly:
#### PulseAudio Configuration
```bash
# Download the script
wget https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pulse.sh
chmod +x configure_pulse.sh
# Run twice: once as user, once as root
./configure_pulse.sh
sudo ./configure_pulse.sh
```
#### PipeWire Configuration
```bash
# Download the script
wget https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pipewire.sh
chmod +x configure_pipewire.sh
# Run twice: once as user, once as root
./configure_pipewire.sh
sudo ./configure_pipewire.sh
```
**Direct links:**
- [configure_pulse.sh](https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pulse.sh)
- [configure_pipewire.sh](https://git.stormux.org/storm/fenrir/raw/branch/master/tools/configure_pipewire.sh)
### Using Installed Scripts
If you have Fenrir installed, the scripts are available at:
**PulseAudio:**
```bash
/usr/share/fenrirscreenreader/tools/configure_pulse.sh
sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh
```
**PipeWire:**
```bash
/usr/share/fenrirscreenreader/tools/configure_pipewire.sh
sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh
```
**Note:** These scripts work for any console application that needs root audio access, not just Fenrir.
## Getting Started
### Basic Usage
@@ -640,31 +692,6 @@ send_fenrir_command("setting set speech#rate=0.9")
- Check Fenrir debug logs: `/var/log/fenrir.log`
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
## Configure pulseaudio
Pulseaudio by default only plays sound for the user its currently running for. As fenrir is running as root, your local user does not hear the sound and speech produced by fenrir.
for this fenrir provides a script to configure pulseaudio to stream the sound played as root to your local user. This is not a issue of fenrir but this is how pulseaudio works.
just run the configuration script twice (once as user, once as root):
/usr/share/fenrirscreenreader/tools/configure_pulse.sh
sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh
The script is also located in the tools directory in git
## Configure pipewire
Pipewire by default only plays sound for the user its currently running for. As fenrir is running as root, your local user does not hear the sound and speech produced by fenrir.
for this fenrir provides a script to configure pipewire to stream the sound played as root to your local user. This is not a issue of fenrir but this is how pipewire works.
just run the configuration script twice (once as user, once as root):
/usr/share/fenrirscreenreader/tools/configure_pipewire.sh
sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh
The script is also located in the tools directory in git
## Command Line Options
Fenrir supports several command-line options for different use cases:

192
RELEASE_CHECKLIST.md Normal file
View File

@@ -0,0 +1,192 @@
# Fenrir Release Validation Checklist
This checklist ensures thorough validation before releasing Fenrir packages.
## 🔧 Setup Tools (One-time setup)
### Install Pre-commit Hook
```bash
# Safely install composite hook (preserves existing version management)
./tools/install_validation_hook.sh
# Test the hook
./.git/hooks/pre-commit
```
### Validation Scripts
- `tools/validate_syntax.py` - Python syntax validation
- `tools/validate_pep8.py` - PEP8 compliance checking with safe auto-fix
- `tools/validate_release.py` - Comprehensive release validation
- `tools/cleanup_cache.py` - Remove Python cache files and directories
- `tools/pre-commit-hook` - Git pre-commit validation
## 📋 Pre-Release Checklist
### 1. Code Quality Validation ✅
```bash
# Comprehensive release validation (includes syntax, imports, structure)
python3 tools/validate_release.py
# If issues found, try auto-fix
python3 tools/validate_release.py --fix
# Quick validation (skips slow dependency checks)
python3 tools/validate_release.py --quick
```
**Expected Result**: All tests pass, no syntax errors
### 2. Dependency Validation ✅
```bash
# Validate all dependencies are available
python3 check-dependencies.py
```
**Expected Result**: All required dependencies reported as available
### 3. Core Functionality Test ✅
```bash
# Test core imports (safe to run without sudo)
cd src
python3 -c "
import fenrirscreenreader.core.fenrirManager
import fenrirscreenreader.core.commandManager
import fenrirscreenreader.core.eventManager
print('Core imports successful')
"
cd ..
```
**Expected Result**: No import errors
### 4. Installation Script Validation ✅
```bash
# Validate setup.py syntax
python3 -m py_compile setup.py
# Check setup.py can be parsed
python3 setup.py --help-commands >/dev/null
```
**Expected Result**: No syntax errors, setup.py functional
### 5. Configuration Validation ✅
```bash
# Verify config files exist and are parseable
ls -la config/settings/settings.conf
ls -la config/keyboard/desktop.conf
ls -la config/punctuation/default.conf
```
**Expected Result**: All core config files present
### 6. Manual Testing (User/Package Maintainer) ⚠️
**Important**: These require user interaction as they need sudo access or specific hardware.
```bash
# Test basic functionality (ask user to run)
sudo ./src/fenrir --help
# Test in emulation mode (safer for desktop environments)
sudo ./src/fenrir -e --version
# Quick functionality test (3-5 seconds)
sudo timeout 5 ./src/fenrir -e -f || echo "Timeout reached (expected)"
```
**Expected Result**: No immediate crashes, basic help/version output works
### 7. Package-Specific Validation ✅
```bash
# Test the same compilation process used by package managers
python3 -m compileall src/fenrirscreenreader/ -q
# Verify no __pycache__ permission issues
find src/ -name "*.pyc" -delete
find src/ -name "__pycache__" -delete
```
**Expected Result**: Clean compilation, no permission errors
## 🚨 Known Issue Categories
### Critical Issues (Block Release)
- **Python syntax errors** (SyntaxError, unterminated strings)
- **Missing core dependencies** (dbus-python, evdev, etc.)
- **Import failures in core modules** (fenrirManager, commandManager)
- **Missing critical config files** (settings.conf, desktop.conf)
### Warning Issues (Address if Possible)
- **PEP8 violations** (cosmetic, don't block release)
- **Missing optional dependencies** (for specific features)
- **Command structure issues** (missing methods in command files)
- **Very long lines** (>120 characters)
## 🔍 Root Cause Analysis
### Why These Errors Weren't Caught Previously
1. **No automated syntax validation** - The codebase relied on manual testing
2. **No pre-commit hooks** - Syntax errors could be committed
3. **No CI/CD pipeline** - Package compilation happens only during release
4. **Manual PEP8 cleanup** - F-string refactoring introduced syntax errors during batch cleanup
## 📖 Usage Instructions
### For Developers
```bash
# Before committing changes
git add .
git commit # Pre-commit hook will run automatically
# Before creating tags/releases
python3 tools/validate_release.py
```
### For Package Maintainers
```bash
# Before packaging
python3 tools/validate_release.py
# If validation fails
python3 tools/validate_release.py --fix
# Quick check (if dependencies are known good)
python3 tools/validate_release.py --quick
```
### For Release Managers
```bash
# Complete validation before tagging
python3 tools/validate_release.py
# Manual verification (requires sudo)
sudo ./src/fenrir --version
# Tag release only after all validations pass
git tag -a v2.x.x -m "Release v2.x.x"
```
## 🎯 Future Improvements
### Recommended Additions
1. **GitHub Actions CI/CD** - Automated validation on every push
2. **Automated testing** - Unit tests for core functionality
3. **Integration testing** - Test driver interactions
4. **Package testing** - Validate actual package installation
### Modern Python Packaging
- Consider migrating to `pyproject.toml` (PEP 621)
- Use `build` instead of `setup.py` directly
- Add `tox.ini` for multi-environment testing
## 📞 Support
If validation fails and auto-fix doesn't resolve issues:
1. **Check the specific error messages** in validation output
2. **Review recent commits** that might have introduced issues
3. **Run individual validation steps** to isolate problems
Remember: **Working code is better than perfect code** - especially for accessibility software where reliability is critical.

View File

@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
# Read-all functionality
KEY_FENRIR,KEY_CTRL,KEY_DOWN=read_all_by_line
KEY_FENRIR,KEY_CTRL,KEY_PAGEDOWN=read_all_by_page
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout
KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout

View File

@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
# Read-all functionality
KEY_FENRIR,KEY_CTRL,KEY_DOWN=read_all_by_line
KEY_FENRIR,KEY_CTRL,KEY_PAGEDOWN=read_all_by_page
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
KEY_F4=cycle_keyboard_layout
KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout

View File

@@ -1,130 +0,0 @@
KEY_FENRIR,KEY_F1=toggle_tutorial_mode
KEY_FENRIR,KEY_H=toggle_tutorial_mode
KEY_CTRL=shut_up
KEY_SHIFT,KEY_KP9=review_bottom
KEY_SHIFT,KEY_KP7=review_top
KEY_KP8=review_curr_line
KEY_KP7=review_prev_line
KEY_KP9=review_next_line
KEY_SHIFT,KEY_KP1=review_line_begin
KEY_SHIFT,KEY_KP3=review_line_end
KEY_FENRIR,KEY_KP1=review_line_first_char
KEY_FENRIR,KEY_KP3=review_line_last_char
KEY_FENRIR,KEY_ALT,KEY_1=present_first_line
KEY_FENRIR,KEY_ALT,KEY_2=present_last_line
KEY_KP5=review_curr_word
KEY_KP4=review_prev_word
KEY_KP6=review_next_word
2,KEY_KP5=review_curr_word_phonetic
2,KEY_KP4=review_prev_word_phonetic
2,KEY_KP6=review_next_word_phonetic
KEY_KP2=review_curr_char
KEY_KP1=review_prev_char
KEY_KP3=review_next_char
2,KEY_KP2=review_curr_char_phonetic
2,KEY_KP1=review_prev_char_phonetic
2,KEY_KP3=review_next_char_phonetic
KEY_FENRIR,KEY_CTRL,KEY_KP8=review_up
KEY_FENRIR,KEY_CTRL,KEY_KP2=review_down
KEY_FENRIR,KEY_KPDOT=exit_review
KEY_KPDOT=cursor_position
KEY_FENRIR,KEY_I=indent_curr_line
KEY_FENRIR,KEY_B=curr_screen
KEY_FENRIR,KEY_KP8=curr_screen_before_cursor
KEY_FENRIR,KEY_KP2=curr_screen_after_cursor
KEY_FENRIR,KEY_SHIFT,KEY_PAGEDOWN=cursor_read_to_end_of_line
#=cursor_column
#=cursor_lineno
#=braille_flush
KEY_FENRIR,KEY_CTRL,KEY_T=braille_return_to_cursor
#=braille_pan_left
#=braille_pan_right
KEY_FENRIR,KEY_CTRL,KEY_1=clear_bookmark_1
KEY_FENRIR,KEY_SHIFT,KEY_1=set_bookmark_1
KEY_FENRIR,KEY_1=bookmark_1
KEY_FENRIR,KEY_K=bookmark_1
KEY_FENRIR,KEY_CTRL,KEY_2=clear_bookmark_2
KEY_FENRIR,KEY_SHIFT,KEY_2=set_bookmark_2
KEY_FENRIR,KEY_2=bookmark_2
KEY_FENRIR,KEY_CTRL,KEY_3=clear_bookmark_3
KEY_FENRIR,KEY_SHIFT,KEY_3=set_bookmark_3
KEY_FENRIR,KEY_3=bookmark_3
KEY_FENRIR,KEY_CTRL,KEY_4=clear_bookmark_4
KEY_FENRIR,KEY_SHIFT,KEY_4=set_bookmark_4
KEY_FENRIR,KEY_4=bookmark_4
KEY_FENRIR,KEY_CTRL,KEY_5=clear_bookmark_5
KEY_FENRIR,KEY_SHIFT,KEY_5=set_bookmark_5
KEY_FENRIR,KEY_5=bookmark_5
KEY_FENRIR,KEY_CTRL,KEY_6=clear_bookmark_6
KEY_FENRIR,KEY_SHIFT,KEY_6=set_bookmark_6
KEY_FENRIR,KEY_6=bookmark_6
KEY_FENRIR,KEY_CTRL,KEY_7=clear_bookmark_7
KEY_FENRIR,KEY_SHIFT,KEY_7=set_bookmark_7
KEY_FENRIR,KEY_7=bookmark_7
KEY_FENRIR,KEY_CTRL,KEY_8=clear_bookmark_8
KEY_FENRIR,KEY_SHIFT,KEY_8=set_bookmark_8
KEY_FENRIR,KEY_8=bookmark_8
KEY_FENRIR,KEY_CTRL,KEY_9=clear_bookmark_9
KEY_FENRIR,KEY_SHIFT,KEY_9=set_bookmark_9
KEY_FENRIR,KEY_9=bookmark_9
KEY_FENRIR,KEY_CTRL,KEY_0=clear_bookmark_10
KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
KEY_KPPLUS=progress_bar_monitor
KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
#=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
KEY_KPENTER=temp_disable_speech
KEY_FENRIR,KEY_P=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_S=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
key_FENRIR,KEY_5=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
KEY_FENRIR,KEY_KPMINUS=toggle_barrier
KEY_FENRIR,KEY_Q=quit_fenrir
KEY_FENRIR,KEY_T=time
KEY_FENRIR,KEY_F12=time
2,KEY_FENRIR,KEY_T=date
2,KEY_FENRIR,KEY_F12=date
KEY_KPSLASH=toggle_auto_indent
KEY_FENRIR,KEY_F=attribute_cursor
#=toggle_has_attribute
KEY_FENRIR,KEY_F7=spell_check
2,KEY_FENRIR,KEY_S=add_word_to_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_S=remove_word_from_spell_check
KEY_FENRIR,KEY_F2=forward_keypress
KEY_FENRIR,KEY_ALT,KEY_UP=inc_sound_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_sound_volume
#=clear_clipboard
KEY_FENRIR,KEY_HOME=first_clipboard
KEY_FENRIR,KEY_END=last_clipboard
KEY_FENRIR,KEY_PAGEUP=prev_clipboard
KEY_FENRIR,KEY_PAGEDOWN=next_clipboard
KEY_FENRIR,KEY_SHIFT,KEY_C=curr_clipboard
KEY_FENRIR,KEY_C=copy_marked_to_clipboard
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_C=copy_last_echo_to_clipboard
KEY_FENRIR,KEY_V=paste_clipboard
KEY_FENRIR,KEY_F5=import_clipboard_from_file
KEY_FENRIR,KEY_F6=export_clipboard_to_file
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_F9=set_mark
KEY_FENRIR,KEY_F10=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_RIGHT=next_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_UP=next_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_LEFT=prev_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_DOWN=prev_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_C=save_settings
# linux specific
#=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume
KEY_F4=cycle_keyboard_layout

View File

@@ -1,129 +0,0 @@
KEY_FENRIR,KEY_F1=toggle_tutorial_mode
KEY_FENRIR,KEY_H=toggle_tutorial_mode
KEY_CTRL=shut_up
KEY_FENRIR,KEY_CTRL,KEY_END=review_bottom
KEY_FENRIR,KEY_CTRL,KEY_HOME=review_top
KEY_FENRIR,KEY_SHIFT,KEY_DOT=review_curr_line
KEY_FENRIR,KEY_U=review_prev_line
KEY_FENRIR,KEY_O=review_next_line
KEY_FENRIR,KEY_HOME=review_line_begin
KEY_FENRIR,KEY_END=review_line_end
KEY_FENRIR,KEY_CTRL,KEY_J=review_line_first_char
KEY_FENRIR,KEY_CTRL,KEY_L=review_line_last_char
KEY_FENRIR,KEY_ALT,KEY_1=present_first_line
KEY_FENRIR,KEY_ALT,KEY_2=present_last_line
KEY_FENRIR,KEY_CTRL,KEY_DOT=review_curr_word
KEY_FENRIR,KEY_J=review_prev_word
KEY_FENRIR,KEY_L=review_next_word
2,KEY_FENRIR,KEY_CTRL,KEY_DOT=review_curr_word_phonetic
2,KEY_FENRIR,KEY_J=review_prev_word_phonetic
2,KEY_FENRIR,KEY_L=review_next_word_phonetic
KEY_FENRIR,KEY_COMMA=review_curr_char
KEY_FENRIR,KEY_M=review_prev_char
KEY_FENRIR,KEY_DOT=review_next_char
2,KEY_FENRIR,KEY_COMMA=curr_char_phonetic
2,KEY_FENRIR,KEY_M=prev_char_phonetic
2,KEY_FENRIR,KEY_DOT=next_char_phonetic
KEY_FENRIR,KEY_CTRL,KEY_I=review_up
KEY_FENRIR,KEY_CTRL,KEY_COMMA=review_down
KEY_FENRIR,KEY_SLASH=exit_review
KEY_FENRIR,KEY_SHIFT,KEY_DOT=cursor_position
2,KEY_FENRIR,KEY_I=indent_curr_line
KEY_FENRIR,KEY_B=curr_screen
KEY_FENRIR,KEY_SHIFT,KEY_I=curr_screen_before_cursor
KEY_FENRIR,KEY_SHIFT,KEY_COMMA=curr_screen_after_cursor
KEY_FENRIR,KEY_SHIFT,KEY_PAGEDOWN=cursor_read_to_end_of_line
#=cursor_column
#=cursor_lineno
#=braille_flush
KEY_FENRIR,KEY_CTRL,KEY_T=braille_return_to_cursor
#=braille_pan_left
#=braille_pan_right
KEY_FENRIR,KEY_CTRL,KEY_1=clear_bookmark_1
KEY_FENRIR,KEY_SHIFT,KEY_1=set_bookmark_1
KEY_FENRIR,KEY_1=bookmark_1
KEY_FENRIR,KEY_K=bookmark_1
KEY_FENRIR,KEY_CTRL,KEY_2=clear_bookmark_2
KEY_FENRIR,KEY_SHIFT,KEY_2=set_bookmark_2
KEY_FENRIR,KEY_2=bookmark_2
KEY_FENRIR,KEY_CTRL,KEY_3=clear_bookmark_3
KEY_FENRIR,KEY_SHIFT,KEY_3=set_bookmark_3
KEY_FENRIR,KEY_3=bookmark_3
KEY_FENRIR,KEY_CTRL,KEY_4=clear_bookmark_4
KEY_FENRIR,KEY_SHIFT,KEY_4=set_bookmark_4
KEY_FENRIR,KEY_4=bookmark_4
KEY_FENRIR,KEY_CTRL,KEY_5=clear_bookmark_5
KEY_FENRIR,KEY_SHIFT,KEY_5=set_bookmark_5
KEY_FENRIR,KEY_5=bookmark_5
KEY_FENRIR,KEY_CTRL,KEY_6=clear_bookmark_6
KEY_FENRIR,KEY_SHIFT,KEY_6=set_bookmark_6
KEY_FENRIR,KEY_6=bookmark_6
KEY_FENRIR,KEY_CTRL,KEY_7=clear_bookmark_7
KEY_FENRIR,KEY_SHIFT,KEY_7=set_bookmark_7
KEY_FENRIR,KEY_7=bookmark_7
KEY_FENRIR,KEY_CTRL,KEY_8=clear_bookmark_8
KEY_FENRIR,KEY_SHIFT,KEY_8=set_bookmark_8
KEY_FENRIR,KEY_8=bookmark_8
KEY_FENRIR,KEY_CTRL,KEY_9=clear_bookmark_9
KEY_FENRIR,KEY_SHIFT,KEY_9=set_bookmark_9
KEY_FENRIR,KEY_9=bookmark_9
KEY_FENRIR,KEY_CTRL,KEY_0=clear_bookmark_10
KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_CTRL,KEY_8=set_window_application
2,KEY_FENRIR,KEY_CTRL,KEY_8=clear_window_application
KEY_FENRIR,KEY_SEMICOLON=last_incoming
#=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
KEY_FENRIR,KEY_ENTER=temp_disable_speech
KEY_FENRIR,KEY_P=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_S=toggle_output
KEY_FENRIR,KEY_SHIFT,KEY_E=toggle_emoticons
KEY_FENRIR,KEY_5=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_Y=toggle_highlight_tracking
#=toggle_barrier
KEY_FENRIR,KEY_Q=quit_fenrir
KEY_FENRIR,KEY_T=time
KEY_FENRIR,KEY_F12=time
2,KEY_FENRIR,KEY_T=date
2,KEY_FENRIR,KEY_F12=date
KEY_FENRIR,KEY_BACKSLASH=toggle_auto_indent
KEY_FENRIR,KEY_F=attribute_cursor
#=toggle_has_attribute
KEY_FENRIR,KEY_F7=spell_check
2,KEY_FENRIR,KEY_S=add_word_to_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_S=remove_word_from_spell_check
KEY_FENRIR,KEY_F2=forward_keypress
KEY_FENRIR,KEY_ALT,KEY_UP=inc_sound_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_sound_volume
#=clear_clipboard
#=first_clipboard
#=last_clipboard
KEY_FENRIR,KEY_PAGEUP=prev_clipboard
KEY_FENRIR,KEY_PAGEDOWN=next_clipboard
KEY_FENRIR,KEY_SHIFT,KEY_C=curr_clipboard
KEY_FENRIR,KEY_C=copy_marked_to_clipboard
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_C=copy_last_echo_to_clipboard
KEY_FENRIR,KEY_V=paste_clipboard
KEY_FENRIR,KEY_F5=import_clipboard_from_file
KEY_FENRIR,KEY_F6=export_clipboard_to_file
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_F9=set_mark
KEY_FENRIR,KEY_F10=marked_text
KEY_FENRIR,KEY_SHIFT,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_RIGHT=next_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_UP=next_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_LEFT=prev_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_DOWN=prev_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_C=save_settings
# linux specific
#=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_ALT,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_alsa_volume
KEY_F4=cycle_keyboard_layout

View File

@@ -24,9 +24,8 @@ class command:
def run(self):
try:
self.env["runtime"]["OutputManager"].present_text(
f"Fenrir screen reader version {
fenrirVersion.version}-{
fenrirVersion.code_name}",
f"Fenrir screen reader version "
f"{fenrirVersion.version}-{fenrirVersion.code_name}",
interrupt=True,
)
except Exception as e:

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import importlib.util
import os
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "adjustment_base.py")
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
adjustment_command = _module.adjustment_command
class command(adjustment_command):
def __init__(self):
super().__init__("alsa", "volume", "dec")

View File

@@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import importlib.util
import os
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "adjustment_base.py")
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
adjustment_command = _module.adjustment_command
class command(adjustment_command):
def __init__(self):
super().__init__("alsa", "volume", "inc")

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Read all text line by line from current position")
def run(self):
self.env["runtime"]["OutputManager"].present_text(
"Read-all functionality temporarily disabled",
interrupt=True
)
return
# Check if speechd is available for callbacks
try:
speech_driver = self.env['runtime']['SpeechDriver']
if not (hasattr(speech_driver, '_sd') and speech_driver._sd):
self.env["runtime"]["OutputManager"].present_text(
_("Read all requires speech-dispatcher - not available with current speech driver"),
interrupt=True
)
return
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllByLine run: Error checking speech driver: {e}",
debug.DebugLevel.ERROR
)
return
# Check if ReadAllManager is available
if "ReadAllManager" not in self.env["runtime"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager not in runtime"),
interrupt=True
)
return
elif not self.env["runtime"]["ReadAllManager"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager is None"),
interrupt=True
)
return
# Start continuous line-by-line reading
if self.env["runtime"]["ReadAllManager"].is_active():
# Stop if already active
self.env["runtime"]["ReadAllManager"].stop_read_all()
else:
# Start line-by-line reading
self.env["runtime"]["ReadAllManager"].start_read_all('line')
def set_callback(self, callback):
pass

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Read all text page by page from current position")
def run(self):
# Check if speechd is available for callbacks
try:
speech_driver = self.env['runtime']['SpeechDriver']
if not (hasattr(speech_driver, '_sd') and speech_driver._sd):
self.env["runtime"]["OutputManager"].present_text(
_("Read all requires speech-dispatcher - not available with current speech driver"),
interrupt=True
)
return
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllByPage run: Error checking speech driver: {e}",
debug.DebugLevel.ERROR
)
return
# Check if ReadAllManager is available
if "ReadAllManager" not in self.env["runtime"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager not in runtime"),
interrupt=True
)
return
elif not self.env["runtime"]["ReadAllManager"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager is None"),
interrupt=True
)
return
# Start continuous page-by-page reading
if self.env["runtime"]["ReadAllManager"].is_active():
# Stop if already active
self.env["runtime"]["ReadAllManager"].stop_read_all()
else:
# Start page-by-page reading
self.env["runtime"]["ReadAllManager"].start_read_all('page')
def set_callback(self, callback):
pass

View File

@@ -4,7 +4,7 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import time
from fenrirscreenreader.core.i18n import _
@@ -14,52 +14,147 @@ class command:
def initialize(self, environment):
self.env = environment
# Initialize tab completion state tracking
if "tabCompletion" not in self.env["commandBuffer"]:
self.env["commandBuffer"]["tabCompletion"] = {
"lastTabTime": 0,
"pendingCompletion": None,
"retryCount": 0
}
def shutdown(self):
pass
def get_description(self):
return "No Description found"
return _("Announces tab completions when detected")
def _is_recent_tab_input(self):
"""Check if TAB was pressed recently (within 200ms window)"""
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"]
if time_since_tab <= 0.2: # 200ms window
tab_detected = True
return tab_detected
def _is_flexible_completion_match(self, x_move, delta_text):
"""Use flexible matching instead of strict equality"""
if not delta_text:
return False
delta_len = len(delta_text)
# Exact match (preserve original behavior)
if x_move == delta_len:
return True
# Flexible range: allow ±2 characters difference
# Handles spacing adjustments and unicode width variations
if abs(x_move - delta_len) <= 2 and delta_len > 0:
return True
# For longer completions, allow proportional variance
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
return True
return False
def _detect_completion_patterns(self, delta_text):
"""Detect common tab completion patterns for improved accuracy"""
if not delta_text:
return False
delta_stripped = delta_text.strip()
# File extension completion
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
return True
# Path completion (contains / or \)
if '/' in delta_stripped or '\\' in delta_stripped:
return True
# Command parameter completion (starts with -)
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
return True
# Word boundary completion (alphanumeric content)
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
return True
return False
def run(self):
# try to detect the tab completion by cursor change
"""Enhanced tab completion detection with improved reliability"""
# Basic cursor movement check (preserve original logic)
x_move = (
self.env["screen"]["new_cursor"]["x"]
- self.env["screen"]["old_cursor"]["x"]
)
if x_move <= 0:
return
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
if not (
self.env["runtime"]["InputManager"].get_last_deepest_input()
in [["KEY_TAB"]]
):
# Enhanced tab input detection with persistence
tab_detected = self._is_recent_tab_input()
# Fallback for non-tab movements (preserve original thresholds)
if not tab_detected:
if x_move < 5:
return
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
"BYTE"
]:
found = False
for currByte in self.env["runtime"][
"ByteManager"
].get_last_byte_key():
if currByte == 9:
found = True
if not found:
if x_move < 5:
return
# is there any change?
# Screen delta availability check
if not self.env["runtime"]["ScreenManager"].is_delta():
# If tab was detected but no delta yet, store for potential retry
if tab_detected and self.env["commandBuffer"]["tabCompletion"]["retryCount"] < 2:
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = {
"x_move": x_move,
"timestamp": time.time()
}
self.env["commandBuffer"]["tabCompletion"]["retryCount"] += 1
return
if not x_move == len(self.env["screen"]["new_delta"]):
delta_text = self.env["screen"]["new_delta"]
# Enhanced correlation checking with flexible matching
if not self._is_flexible_completion_match(x_move, delta_text):
# Additional pattern-based validation for edge cases
if not (tab_detected and self._detect_completion_patterns(delta_text)):
return
# filter unneded space on word begin
curr_delta = self.env["screen"]["new_delta"]
if (
len(curr_delta.strip()) != len(curr_delta)
and curr_delta.strip() != ""
):
# Reset retry counter on successful detection
self.env["commandBuffer"]["tabCompletion"]["retryCount"] = 0
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = None
# Mark that we've handled this delta to prevent duplicate announcements
# This prevents the incoming text handler from also announcing the same content
self.env["commandBuffer"]["tabCompletion"]["lastProcessedDelta"] = delta_text
self.env["commandBuffer"]["tabCompletion"]["lastProcessedTime"] = time.time()
# Text filtering and announcement (preserve original behavior)
curr_delta = delta_text
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
curr_delta = curr_delta.strip()
# 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
)

View File

@@ -60,6 +60,7 @@ class command:
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "autoPresentIndentMode"
) in [0, 1]:
if self.lastIdent != curr_ident:
self.env["runtime"]["OutputManager"].play_frequence(
curr_ident * 50, 0.1, interrupt=do_interrupt
)

View File

@@ -31,10 +31,9 @@ class command:
self.lastIdent = 0
return
# is a vertical change?
if not self.env["runtime"][
"CursorManager"
].is_cursor_horizontal_move():
# Skip if no cursor movement at all
if (not self.env["runtime"]["CursorManager"].is_cursor_horizontal_move() and
not self.env["runtime"]["CursorManager"].is_cursor_vertical_move()):
return
x, y, curr_line = line_utils.get_current_line(
self.env["screen"]["new_cursor"]["x"],
@@ -43,8 +42,11 @@ class command:
)
curr_ident = self.env["screen"]["new_cursor"]["x"]
if not curr_line.isspace():
# ident
if curr_line.isspace():
# Don't beep for lines with only spaces - no meaningful indentation
return
# Lines with actual content - calculate proper indentation
lastIdent, lastY, last_line = line_utils.get_current_line(
self.env["screen"]["new_cursor"]["x"],
self.env["screen"]["new_cursor"]["y"],
@@ -57,13 +59,17 @@ class command:
curr_ident = len(curr_line) - len(curr_line.lstrip())
if self.lastIdent == -1:
self.lastIdent = curr_ident
if curr_ident <= 0:
return
# Initialize lastIdent if needed
if self.lastIdent == -1:
self.lastIdent = curr_ident
# Only beep/announce if indentation level has changed
if self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"general", "autoPresentIndent"
):
) and self.lastIdent != curr_ident:
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "autoPresentIndentMode"
) in [0, 1]:
@@ -71,14 +77,15 @@ class command:
curr_ident * 50, 0.1, interrupt=False
)
if self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "autoPresentIndentMode"
"general", "autePresentIndentMode"
) in [0, 2]:
if self.lastIdent != curr_ident:
self.env["runtime"]["OutputManager"].present_text(
_("indented ") + str(curr_ident) + " ",
interrupt=False,
flush=False,
)
# Always update lastIdent for next comparison
self.lastIdent = curr_ident
def set_callback(self, callback):

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import time
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 _("Handles delayed retry for tab completion detection")
def run(self):
"""Check for and process pending tab completions with slight delay"""
# Only process if we have tab completion state
if "tabCompletion" not in self.env["commandBuffer"]:
return
tab_state = self.env["commandBuffer"]["tabCompletion"]
pending = tab_state.get("pendingCompletion")
if not pending:
return
current_time = time.time()
# Process pending completion after 50ms delay
if current_time - pending["timestamp"] < 0.05:
return
# Check if screen delta is now available
if not self.env["runtime"]["ScreenManager"].is_delta():
# Give up after 200ms total
if current_time - pending["timestamp"] > 0.2:
tab_state["pendingCompletion"] = None
tab_state["retryCount"] = 0
return
# Process the delayed completion
delta_text = self.env["screen"]["new_delta"]
x_move = pending["x_move"]
# Use the same flexible matching logic as main tab completion
match_found = self._is_flexible_completion_match(x_move, delta_text)
if not match_found:
# Try pattern-based detection as final fallback
match_found = self._detect_completion_patterns(delta_text)
if match_found and delta_text:
# Mark that we've handled this delta to prevent duplicate announcements
tab_state["lastProcessedDelta"] = delta_text
tab_state["lastProcessedTime"] = current_time
# Filter and announce the completion
curr_delta = delta_text
if (len(curr_delta.strip()) != len(curr_delta) and
curr_delta.strip() != ""):
curr_delta = curr_delta.strip()
if curr_delta:
self.env["runtime"]["OutputManager"].present_text(
curr_delta, interrupt=True, announce_capital=True, flush=False
)
# Clear pending completion
tab_state["pendingCompletion"] = None
tab_state["retryCount"] = 0
def _is_flexible_completion_match(self, x_move, delta_text):
"""Use flexible matching (duplicated from main command for heartbeat use)"""
if not delta_text:
return False
delta_len = len(delta_text)
# Exact match
if x_move == delta_len:
return True
# Flexible range: allow ±2 characters difference
if abs(x_move - delta_len) <= 2 and delta_len > 0:
return True
# For longer completions, allow proportional variance
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
return True
return False
def _detect_completion_patterns(self, delta_text):
"""Detect common tab completion patterns (duplicated from main command)"""
if not delta_text:
return False
delta_stripped = delta_text.strip()
# File extension completion
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
return True
# Path completion
if '/' in delta_stripped or '\\' in delta_stripped:
return True
# Command parameter completion
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
return True
# Word boundary completion
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
return True
return False
def set_callback(self, callback):
pass

View File

@@ -24,6 +24,11 @@ class command:
def run(self):
if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]:
return
# Only announce numlock changes if an actual numlock key was pressed
# This prevents spurious announcements from external numpad automatic state changes
current_input = self.env["input"]["currInput"]
if current_input and "KEY_NUMLOCK" in current_input:
if self.env["input"]["newNumLock"]:
self.env["runtime"]["OutputManager"].present_text(
_("Numlock on"), interrupt=True

View File

@@ -144,13 +144,43 @@ class command:
] = current_time
return
# Pattern 1a2: Curl classic progress format (percentage without % symbol)
# Extract percentage from curl's classic format
curl_classic_match = re.search(
r"^\s*(\d+)\s+\d+[kMGT]?\s+(\d+)\s+\d+[kMGT]?\s+\d+\s+\d+\s+\d+[kMGT]?\s+\d+\s+\d+:\d+:\d+\s+\d+:\d+:\d+\s+\d+:\d+:\d+\s+\d+[kMGT]?\s*$", text
)
if curl_classic_match:
# Use the first percentage (total progress)
percentage = float(curl_classic_match.group(1))
if 0 <= percentage <= 100:
self.env["runtime"]["DebugManager"].write_debug_out(
"found curl classic percentage: " + str(percentage),
debug.DebugLevel.INFO,
)
if (
percentage
!= self.env["commandBuffer"]["lastProgressValue"]
):
self.env["runtime"]["DebugManager"].write_debug_out(
"Playing tone for curl: " + str(percentage),
debug.DebugLevel.INFO,
)
self.play_progress_tone(percentage)
self.env["commandBuffer"][
"lastProgressValue"
] = percentage
self.env["commandBuffer"][
"lastProgressTime"
] = current_time
return
# Pattern 1b: Time/token activity (not percentage-based, so use single
# beep)
time_match = re.search(r"(\d+)s\s", text)
token_match = re.search(r"(\d+)\s+tokens", text)
time_match = re.search(r"(?:(?:remaining|elapsed|left|ETA|eta)[:;\s]*(\d+)s|(\d+)s\s+(?:remaining|elapsed|left))", text, re.IGNORECASE)
token_match = re.search(r"(?:processing|generating|used|consumed)\s+(\d+)\s+tokens", text, re.IGNORECASE)
# Pattern 1c: dd command output (bytes copied with transfer rate)
dd_match = re.search(r"\d+\s+bytes.*copied.*\d+\s+s.*[kMGT]?B/s", text)
# Pattern 1d: Curl-style transfer data (bytes, speed indicators)
# Pattern 1d: Curl-style transfer data (bytes, speed indicators - legacy)
curl_match = re.search(
r"(\d+\s+\d+\s+\d+\s+\d+.*?(?:k|M|G)?.*?--:--:--|Speed)", text
)
@@ -183,7 +213,10 @@ class command:
if fraction_match:
current = int(fraction_match.group(1))
total = int(fraction_match.group(2))
if total > 0:
# Filter out dates, page numbers, and other non-progress fractions
if (total > 0 and total <= 1000 and current <= total and
not re.search(r"\b(?:page|chapter|section|line|row|column|year|month|day)\b", text, re.IGNORECASE) and
not re.search(r"\d{1,2}/\d{1,2}/\d{2,4}", text)): # Date pattern
percentage = (current / total) * 100
if (
percentage
@@ -235,6 +268,38 @@ class command:
):
self.play_activity_beep()
self.env["commandBuffer"]["lastProgressTime"] = current_time
return
# Pattern 5: Braille progress indicators
braille_match = re.search(r'[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⡿⣟⣯⣷⣾⣽⣻⢿]', text)
if braille_match:
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
self.play_activity_beep()
self.env["commandBuffer"]["lastProgressTime"] = current_time
return
# Pattern 6: Claude Code progress indicators
claude_progress_match = re.search(r'^[·✶✢✻*]\s+[^(]+[…\.]*\s*\(esc to interrupt[^)]*\)\s*$', text)
if claude_progress_match:
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
self.play_activity_beep()
self.env["commandBuffer"]["lastProgressTime"] = current_time
return
# Pattern 7: Moon phase progress indicators
moon_match = re.search(r'[🌑🌒🌓🌔🌕🌖🌗🌘]', text)
if moon_match:
moon_phases = {
'🌑': 0, '🌒': 12.5, '🌓': 25, '🌔': 37.5,
'🌕': 50, '🌖': 62.5, '🌗': 75, '🌘': 87.5
}
moon_char = moon_match.group(0)
if moon_char in moon_phases:
percentage = moon_phases[moon_char]
if percentage != self.env["commandBuffer"]["lastProgressValue"]:
self.play_progress_tone(percentage)
self.env["commandBuffer"]["lastProgressValue"] = percentage
return
def play_progress_tone(self, percentage):
# Map 0-100% to 400-1200Hz frequency range
@@ -250,9 +315,21 @@ class command:
self.play_quiet_tone(800, 0.08)
def play_quiet_tone(self, frequency, duration):
"""Play a quiet tone using Sox directly"""
"""Play a quiet tone using Sox directly with flood protection"""
import shlex
import subprocess
import time
# Flood protection: prevent beeps closer than 0.1 seconds apart
current_time = time.time()
if not hasattr(self, '_last_beep_time'):
self._last_beep_time = 0
if current_time - self._last_beep_time < 0.1:
# Skip this beep to prevent audio crackling on low-resource systems
return
self._last_beep_time = current_time
# Build the Sox command: play -qn synth <duration> tri <frequency> gain
# -8
@@ -369,12 +446,10 @@ class command:
"""Check if text contains URLs that might cause false progress detection"""
import re
# Common URL patterns that might contain progress-like patterns
# Specific URL patterns - only match actual URLs, not filenames
url_patterns = [
r"https?://[^\s]+", # http:// or https:// URLs
r"ftp://[^\s]+", # ftp:// URLs
r"www\.[^\s]+", # www. domains
r"[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[/\w.-]*", # domain.com/path patterns
r"\S+://\S+\.\S{2,}", # Any protocol:// with domain.ext
r"www\.[^\s]+\.[a-zA-Z]{2,}", # www.domain.ext patterns
]
for pattern in url_patterns:

View File

@@ -4,7 +4,7 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import time
from fenrirscreenreader.core.i18n import _
@@ -19,7 +19,25 @@ class command:
pass
def get_description(self):
return "No Description found"
return _("Announces incoming text changes")
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"]:
return False
tab_state = self.env["commandBuffer"]["tabCompletion"]
# Check if this exact delta was processed recently by tab completion
if (tab_state.get("lastProcessedDelta") == delta_text and
tab_state.get("lastProcessedTime")):
# Only suppress if processed within the last 100ms to avoid stale suppression
time_since_processed = time.time() - tab_state["lastProcessedTime"]
if time_since_processed <= 0.1:
return True
return False
def run(self):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
@@ -30,6 +48,12 @@ class command:
if not self.env["runtime"]["ScreenManager"].is_delta(ignoreSpace=True):
return
delta_text = self.env["screen"]["new_delta"]
# Skip if tab completion already handled this delta
if self._was_handled_by_tab_completion(delta_text):
return
# this must be a keyecho or something
# if len(self.env['screen']['new_delta'].strip(' \n\t')) <= 1:
x_move = abs(
@@ -41,14 +65,14 @@ class command:
- self.env["screen"]["old_cursor"]["y"]
)
if (x_move >= 1) and x_move == len(self.env["screen"]["new_delta"]):
if (x_move >= 1) and x_move == len(delta_text):
# if len(self.env['screen']['new_delta'].strip(' \n\t0123456789'))
# <= 2:
if "\n" not in self.env["screen"]["new_delta"]:
if "\n" not in delta_text:
return
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
self.env["runtime"]["OutputManager"].present_text(
self.env["screen"]["new_delta"], interrupt=False, flush=False
delta_text, interrupt=False, flush=False
)
def set_callback(self, callback):

View File

@@ -0,0 +1 @@
# Emoji VMenu category

View File

@@ -0,0 +1 @@
# Flags emoji subcategory

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇦🇷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Argentina flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Argentina flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇦🇺"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Australia flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Australia flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇧🇪"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Belgium flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Belgium flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇧🇷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Brazil flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Brazil flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇨🇦"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Canada flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Canada flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇨🇳"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add China flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added China flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇩🇰"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Denmark flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Denmark flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇫🇮"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Finland flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Finland flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇫🇷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add France flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added France flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇩🇪"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Germany flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Germany flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇬🇷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Greece flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Greece flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇮🇳"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add India flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added India flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇮🇪"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Ireland flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Ireland flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇮🇱"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Israel flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Israel flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇮🇹"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Italy flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Italy flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇯🇵"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Japan flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Japan flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇲🇽"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Mexico flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Mexico flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇳🇱"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Netherlands flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Netherlands flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇳🇴"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Norway flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Norway flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇵🇱"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Poland flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Poland flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇵🇹"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Portugal flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Portugal flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇷🇺"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Russia flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Russia flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇿🇦"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add South Africa flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added South Africa flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇰🇷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add South Korea flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added South Korea flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇪🇸"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Spain flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Spain flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇸🇪"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Sweden flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Sweden flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇨🇭"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Switzerland flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Switzerland flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇹🇷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Turkey flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Turkey flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇬🇧"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add UK flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added UK flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇺🇦"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Ukraine flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Ukraine flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🇺🇸"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add USA flag emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added USA flag to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1 @@
# Food emoji subcategory

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🍎"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Red apple emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added red apple to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🥑"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Avocado emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added avocado to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🍺"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Beer emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added beer to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🎂"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Birthday cake emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added birthday cake to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = ""
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add coffee emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added coffee to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🍩"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Donut emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added donut to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🍔"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add hamburger emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added hamburger to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🍕"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add pizza emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added pizza to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🍓"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Strawberry emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added strawberry to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌮"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Taco emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added taco to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1 @@
# Holidays emoji subcategory

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "👾"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Alien monster emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added alien monster to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🦇"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add bat emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added bat to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🐈‍⬛"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Black cat emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added black cat to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🐰"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add bunny emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added bunny to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🧙"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Mage emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added mage to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🎄"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Christmas tree emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Christmas tree to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "⚰️"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Coffin emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added coffin to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🥚"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Easter egg emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Easter egg to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🎆"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add fireworks emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added fireworks to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "👻"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add ghost emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added ghost to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🎁"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add gift emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added gift to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🎃"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add jack o'lantern emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added jack o'lantern to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🧟"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Mummy emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added mummy to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🎅"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add Santa emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added Santa to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "☘️"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add shamrock emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added shamrock to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "💀"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add skull emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added skull to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = ""
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add snowman emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added snowman to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🕷"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add spider emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added spider to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🦃"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add turkey emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added turkey to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🧛"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Vampire emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added vampire to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🕸️"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Spider web emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added spider web to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🧙‍♀️"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Witch emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added witch to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🧟"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Zombie emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added zombie to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1 @@
# Nature emoji subcategory

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🦋"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Butterfly emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added butterfly to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🐱"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Cat emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added cat to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌸"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Cherry blossom emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added cherry blossom to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🐶"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Dog emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added dog to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌙"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add moon emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added moon to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌈"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Rainbow emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added rainbow to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌹"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Rose emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added rose to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "☀️"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add sun emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added sun to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌻"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Sunflower emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added sunflower to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🌳"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Add tree emoji to clipboard"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added tree to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "🐺"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Wolf emoji - The mighty Fenrir!"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added the mighty wolf Fenrir to clipboard",
interrupt=False, flush=False
)

View File

@@ -0,0 +1 @@
# People emoji subcategory

View File

@@ -0,0 +1,22 @@
class command():
def initialize(self, environment):
self.env = environment
self.emoji = "😀"
def shutdown(self):
pass
def setCallback(self, callback):
pass
def getDescription(self):
return "Grinning face emoji"
def run(self):
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", self.emoji
)
self.env["runtime"]["OutputManager"].present_text(
"Added grinning face to clipboard",
interrupt=False, flush=False
)

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