134 Commits
Author SHA1 Message Date
Storm Dragon f462ca7990 Merge branch 'testing' minor settings file update. 2025-12-03 16:25:47 -05:00
Storm Dragon f0bbcb8a38 Updated settings file to document that capslock as fenrir key and echo mode 2 are incompatible. 2025-12-03 16:25:22 -05:00
Storm Dragon aed627ec2a Discovered through much pain that echo mode 2 and capslock as fenrir key are incompatible. Documented in settings file. 2025-12-03 16:18:02 -05:00
Storm Dragon e62b887e9c Some socket improvements for remote manager I thought should make it into this release. 2025-12-03 12:20:14 -05:00
Storm Dragon bf0d134187 Tests added, see the documentation in the tests directory for details. Improved the socket code. 2025-12-03 02:51:49 -05:00
Storm Dragon c66a9ba9c2 Problems with voice selection fixed.: 2025-12-02 18:38:06 -05:00
Storm Dragon 2092a3e257 Fixed voice selection. 2025-12-02 18:36:46 -05:00
Storm Dragon d46d8de3ee Updated sound driver to gstreamer by default. 2025-12-02 16:25:25 -05:00
Storm Dragon 75a8447759 One more feature addition before hopefully releasing the new version. 2025-12-02 16:13:15 -05:00
Storm Dragon 1650eec768 Add ability to switch speech-dispatcher module and voice to the speeach keys. 2025-12-02 16:11:47 -05:00
Storm Dragon 5bb786ef4c Bug fix in vmenu for keyboard layouts. 2025-11-27 22:44:51 -05:00
Storm Dragon 7f7faa17d3 keyboard layout fixed in vmenu. 2025-11-27 22:42:36 -05:00
Storm Dragon 2766f70c5d Some cleanup and minor fixes in docs. 2025-11-24 09:07:45 -05:00
Storm Dragon 8d781643bc Remove group and user comments from the Arch service file, not being used, so just extra stuff. 2025-11-24 08:49:31 -05:00
Storm Dragon c184cf023a Code cleanups, fixes to systemd files, url corrections. 2025-11-24 08:44:49 -05:00
Storm Dragon 841c221c7b Preparing for new tagged release. 2025-11-23 18:51:02 -05:00
Storm Dragon 87553bdc38 more fixes for the pickle error. 2025-11-23 18:37:21 -05:00
Storm Dragon 77a3aae5a4 Attempt to fix cannot pickle 'TextIOWrapper' instances error for some distros. 2025-11-23 18:29:38 -05:00
Storm Dragon aabc202d83 Latest version of configure_pipewire.sh tested and appears to work. 2025-10-17 22:19:58 -04:00
Storm Dragon 2f3a114790 Pipewire configuration tool updated. In a bout of pure insanity I tested this on my production system and it worked without a hitch, so should be good to go. 2025-10-17 22:19:30 -04:00
Storm Dragon c797974560 Pre tag release. If no problems reported this will become the new stable release. 2025-10-17 21:27:36 -04:00
Storm Dragon af4740d5ad Various minor fixes in preparation for new release. 2025-10-17 21:26:13 -04:00
Storm Dragon 5ef5faaebe Progressbar tweaks. 2025-10-17 21:03:12 -04:00
Storm Dragon 7041d2567a Progress bar updates. 2025-09-26 18:07:35 -04:00
Storm Dragon 2c38bcf5f4 Another attempt at fixing external numpad detection. I *think* some of them send numlock state with every event. If that's the case, this should fix it. 2025-09-13 14:11:27 -04:00
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
Storm Dragon e177c7f486 Final merge for version 2025.07.16. If no problems will tag later today. 2025-07-16 12:33:21 -04:00
Storm Dragon ae4c418323 A few minor progress bar beep tweaks. 2025-07-15 23:13:40 -04:00
Storm Dragon b9abf02b12 A few minor tweaks in preparation for release. 2025-07-13 15:31:57 -04:00
Storm Dragon 0c116adaf2 Progress bar detection updates. 2025-07-13 11:18:51 -04:00
Storm Dragon fe5e2c065e Merge branch 'testing' moving closer to tagged release. 2025-07-09 18:32:15 -04:00
Storm Dragon 5a14804d11 Updates to documentation. A few tidying up changes. 2025-07-09 18:31:58 -04:00
Storm Dragon ef3ebee10c Preparing for new tagged version. Please watch for bugs. 2025-07-09 09:33:19 -04:00
Storm Dragon 6876995d4c New punctuation setting added. 2025-07-09 09:30:45 -04:00
Storm Dragon 01f4b64390 Replace skipped punctuation with space. There's a setting in general, on by default, that will revert to old behavior if you replace it with false. 2025-07-09 01:17:36 -04:00
Storm Dragon 5e858cfde1 Expirmental improvements to highlight tracking. 2025-07-09 01:06:54 -04:00
Storm Dragon 2ad754a372 Some fixes to pep8 stuff. 2025-07-08 18:35:12 -04:00
Storm Dragon 271c4fc18f Table mode fixes and improvements to application detection. 2025-07-08 14:41:43 -04:00
Storm Dragon bb6dbc7186 Fixed character navigation in table mode. 2025-07-07 22:58:14 -04:00
Storm Dragon c7cc9d039b Improved application detection, now works inside screen and tmux. Fixed incosistancies in prev/next word navigation. 2025-07-07 11:17:12 -04:00
Storm Dragon d1a42835e4 Initial table mode added. Probably bugs. 2025-07-07 09:46:12 -04:00
Storm Dragon 3390c25dfe More pep8 fixes. A tiny bit of refactoring. 2025-07-07 00:42:23 -04:00
Storm Dragon ea56b90b48 Oops, getting used to this pep8 thing myself. Fixed codeName to code_name. 2025-07-06 18:51:43 -04:00
Storm Dragon 1268d989b7 Merge after mostly converting to pep8 compliance. 2025-07-06 18:34:28 -04:00
Storm Dragon d28c18faed Found and sniped a couple more pep8 migration errors. 2025-07-05 09:29:32 -04:00
Storm Dragon c90c726899 Fixed a bug in the remote manager. 2025-07-05 09:15:40 -04:00
Storm Dragon bab7c1a552 Removed double keybinding from laptop layout. 2025-07-03 16:11:13 -04:00
Storm Dragon 848844aae4 Oops, forgot to delete my traceback notes. 2025-07-03 13:24:23 -04:00
Storm Dragon 4c1bddbbd3 Most of the pep8 changes finished. Be careful, things may be horribly broken. 2025-07-03 13:23:25 -04:00
Storm Dragon 21bb9c6083 Most of the pep8 changes finished. Be careful, things may be horribly broken. 2025-07-03 13:22:00 -04:00
Storm Dragon 7408951152 To make Fenrir easier to approach for new developer, start code migration to be pep8 compliant. 2025-07-01 22:23:50 -04:00
Storm Dragon 23c3ad20a1 More code optmizations. Removed fenrir+pk_plus. The functionality of bringing back speech has been added to the temperary speech interruption key kp_enter. 2025-06-30 22:25:01 -04:00
Storm Dragon 4bcf82178e Updated configure_pipewire script. A bit more code refactor. Preparing to start moving everything over to pep8 compliance. 2025-06-28 22:52:21 -04:00
Storm Dragon beae1866bb Moved restore speech on prompt to existing temp interrupt speech command, Fenrir+kp_plus unbound now. 2025-06-28 01:49:20 -04:00
Storm Dragon 8af1cca879 Latest changes and bug fixes. 2025-06-27 21:18:27 -04:00
Storm Dragon 40b88efa34 Fix up a few errors that sneaked in while updating vmenu code and a couple other things. Logs should be much nicer now. 2025-06-25 10:56:08 -04:00
Storm Dragon b1edc53362 Added missing dependency to requirements.txt. 2025-06-20 12:43:49 -04:00
Storm Dragon dda84b9905 Final batch of code stability updates before anouther bout of extended testing. Still plenty to go, but making progress. 2025-06-20 03:34:50 -04:00
Storm Dragon 64e79f6945 Code cleanup, make sure race conditions can't happen, at least in theory. 2025-06-20 03:10:07 -04:00
Storm Dragon f4ed8da4c3 Forgot one in last commit. 2025-06-20 03:04:25 -04:00
Storm Dragon a5ca1d28e8 Make sure all except statements are no longer empty, should help a lot with debugging. 2025-06-20 03:03:43 -04:00
Storm Dragon a394ea0222 Code cleanup and bug fixes. 2025-06-20 02:19:57 -04:00
Storm Dragon 4e193f133f Fixed an error that crept into log permissions during code restructuring. 2025-06-20 02:19:07 -04:00
Storm Dragon 27dcff23bb Code optimization and bug fixes. Removed the broken, partial atspi driver implementation. 2025-06-18 15:08:36 -04:00
Storm Dragon efb308ac72 latest testing code merged. Nothing major reported from testing branch, so if we get no reports, this will become the next stable release. I'm waiting a bit to tag because major new features introduced. 2025-06-17 00:53:28 -04:00
Storm Dragon d81d563bb6 Add page up and page down to move through the voice browser by 10%. 2025-06-16 01:08:05 -04:00
Storm Dragon 43871cea3c Fixes to the voice driver. It should actually work completely now. 2025-06-15 19:52:18 -04:00
Storm Dragon 72bd334d65 Very experimental attempt to move the configure_fenrir script into fenrir itself using the vmenu system. Lots of testing please. 2025-06-15 14:04:14 -04:00
Storm Dragon f6be6c54fb Bug fixes mostlry, tested and seems to be working better. 2025-06-13 23:21:46 -04:00
Storm Dragon e76b914d6e Ignore screen 7 by default in case auto ignore screen doesn't work. 2025-06-12 18:43:12 -04:00
Storm Dragon 83cb330d34 Potential fixes to progress bar. Better handling of punctuation while reading. 2025-06-10 18:10:08 -04:00
Storm Dragon b0ac6e1409 improved documentation for the diff generation. 2025-06-09 16:53:05 -04:00
Storm Dragon 6998706934 Testing fixes for security improvement, thread safety, and memory management. 2025-06-09 14:41:33 -04:00
Storm Dragon 62e1001679 Add keybinding for progress bar monitoring for laptop mode. Fenrir+Shift+P. 2025-06-09 13:11:51 -04:00
Storm Dragon d935ef2e3c The fix for hopefully not reading all spaces broke review by character. Hopefully fix that. 2025-06-09 12:48:02 -04:00
Storm Dragon e2fb28d92f Attempt to fix the bug where fenrir sometimes reads all the spaces in a message. 2025-06-09 02:33:36 -04:00
Storm Dragon 8a223282df Some modifications to progress bar detection, can revert if needed. 2025-06-09 02:19:39 -04:00
Storm Dragon 91c97dd1dd Fixed error in settings file, Time and date need %% instead of just a single %. 2025-06-08 18:04:06 -04:00
Storm Dragon 5cc719a6f3 Commit 2 of 2, code refactor and cleanup. 2025-06-08 14:26:24 -04:00
Storm Dragon ddc1b43304 Commit 1 of 2, code refactor and cleanup. 2025-06-08 13:49:22 -04:00
Storm Dragon 6ad11effc6 Enhanced prompt detection 2025-06-08 13:31:37 -04:00
Storm Dragon f18c31df6c Merge branch 'testing' bug fix for remoteDriver 2025-06-07 18:24:44 -04:00
Storm Dragon ca0e3b5987 Reverted changes to remot driver because some settings, e.g. highlight mode, stopped working. 2025-06-07 18:22:31 -04:00
Storm Dragon 0009d90a68 Updated .gitignore 2025-06-07 13:35:10 -04:00
Storm Dragon 2c2efc56f0 Progress beeps should now rok with dd's progress flag. 2025-06-07 13:23:45 -04:00
Storm Dragon 3dca3e5b23 Merged for new release. 2025-06-07 12:23:53 -04:00
Storm Dragon 27c35939b1 A few minor tweaks to progressbar beeps. 2025-06-07 11:13:15 -04:00
Storm Dragon 7e87ebf04b Attempt to improve how Fenrir reads multiple characters in a row, e.g. [------]. 2025-06-07 10:51:02 -04:00
Storm Dragon ec6c135581 Keybinding for silence until prompt returns added to laptop.conf. Fenrir+Shift+Enter 2025-06-07 10:20:09 -04:00
Storm Dragon 998c63cc71 Fixed a few typos in settings file, changed progressbars to true by default. 2025-06-07 01:48:58 -04:00
Storm Dragon 26c6e32c59 Add the ability to add custom prompts to the settings file, generic prompts are covered with the existing detection code. 2025-06-07 01:26:21 -04:00
Storm Dragon 97e2da614b 2 new features, silence speech until prompt returns and progress bar beeps. 2025-06-07 00:52:13 -04:00
Storm Dragon 0930a86ce7 --ignore-screen (-i) flag added. 2025-06-06 22:58:08 -04:00
Storm Dragon 1b9a9a90b1 Fixed version conflict. 2025-06-06 20:35:07 -04:00
Storm Dragon a742c12cd8 Remote driver now responds so you can know for sure the command worked. 2025-06-06 20:32:46 -04:00
Storm Dragon 8d50003730 Updated documentation. Attempted to track down a bug that causes disable speech sound to play with no interaction.? 2025-06-06 17:56:02 -04:00
Storm Dragon 4c8c8d896d Fixed version conflict. 2025-06-05 16:05:11 -04:00
Storm Dragon bd151c7cec Removed the speakup keyboard layout. It was never finished, and Fenrir has more functionality anyway and isn't that hard to learn. 2025-06-04 22:22:51 -04:00
Storm Dragon 6f4784daed Pty layouts skipped in keyboard layout cycling. 2025-06-04 22:14:49 -04:00
Storm Dragon 8c471adfa4 Keyboard layout shortcut changed to a single key, f4, because it multikey shortcuts require the fenrir key. 2025-06-04 20:37:21 -04:00
Storm Dragon 77065c55b4 Attempt to add keyboard shortcut to switch keyboard layout. 2025-06-04 20:17:06 -04:00
Storm Dragon 7f75c231e1 Removed _ from some punctuation, added it to most. 2025-06-04 19:34:15 -04:00
573 changed files with 32184 additions and 15477 deletions
+1
View File
@@ -6,3 +6,4 @@ dist/
build/
*.kate-swp
.directory
CLAUDE.md
+20 -8
View File
@@ -1,18 +1,30 @@
# Fenrir screen reader
# Fenrir Screen Reader Credits
## Developers
## Current Maintainer
* Storm Dragon: Project leader
* Jeremiah: Coder.
* **Storm Dragon** - Project leader and maintainer
## Current Contributors
* **Jeremiah** - Developer
## Previous Developers
* Chrys: coder.
* **Chrys** - Original creator and main developer
## Special thanks to:
## Special Thanks
* F123 Consulting for suggestions, some funding, and endless testing.
* Stormux for continuation of the project.
* **F123 Consulting** - Suggestions, funding, and extensive testing
* **Stormux Community** - Continuation of the project and ongoing support
* **All contributors** - Bug reports, feature requests, and community support
## Community
* IRC: irc.stormux.org #stormux
* Email list: stormux+subscribe@groups.io
* Wiki: https://git.stormux.org/storm/fenrir/wiki
+681 -64
View File
@@ -1,69 +1,99 @@
# Fenrir
A modern, modular, flexible and fast console screenreader.
A modern, modular, flexible and fast console screen reader.
It should run on any operating system. If you want to help, or write drivers to make it work on other systems, just let me know.
This software is licensed under the LGPL v3.
**Current maintainer:** Storm Dragon
**Previous developer:** Chrys
## Key Features
- **Multiple Interface Support**: Works in Linux TTY, and terminal emulators
- **Flexible Driver System**: Modular architecture with multiple drivers for speech, sound, input, and screen
- **Review Mode**: Navigate and review screen content without moving the edit cursor
- **Table Navigation**: Advanced table mode with column headers, cell-by-cell navigation, and boundary feedback
- **Progress Bar Monitoring**: Automatic detection and audio feedback for progress indicators with ascending tones
- **Multiple Clipboard Support**: Manage multiple clipboard entries
- **Configurable Key Bindings**: Desktop and laptop keyboard layouts
- **Sound Icons**: Audio feedback for various events
- **Spell Checking**: Built-in spell checker with word management
- **Language Support**: Multiple speech synthesis languages and voices
- **Bookmark System**: Quick access to specific screen areas
- **Auto-announcement**: Automatic reading of incoming text and time announcements
- **Tutorial Mode**: Built-in help system for learning keyboard shortcuts
## OS Requirements
- Linux (ptyDriver, vcsaDriver, evdevDriver)
- macOS (ptyDriver)
- BSD (ptyDriver)
- Windows (ptyDriver)
- Linux (ptyDriver, vcsaDriver, evdevDriver) - Primary platform with full support
- macOS (ptyDriver) - Limited support
- BSD (ptyDriver) - Limited support
- Windows (ptyDriver) - Limited support
## Core Requirements
- python3 >= 3.3
- screen, input, speech, sound drivers dependencies see "Features, Drivers, Extras".
- Python 3 >= 3.9 (recommended 3.13+)
- Screen, input, speech, sound driver dependencies (see "Features, Drivers, Extras" section)
- For full functionality on Linux: evdev, speech-dispatcher, sox
## Features, Drivers, Extras, Dependencies
### Input Drivers:
1. "evdevDriver" input driver for linux evdev
- python-evdev >=0.6.3 (This is commonly referred to as python3-evdev by your distribution)
- python-pyudev
- loaded uinput kernel module
- ReadWrite permission
- /dev/input
- /dev/uinput
2. "ptyDriver" terminal emulation input driver
- python-pyte
1. **evdevDriver** - Linux evdev input driver (recommended for Linux)
- python-evdev >=0.6.3 (This is commonly referred to as python3-evdev by your distribution)
- python-pyudev
- loaded uinput kernel module
- ReadWrite permission:
- /dev/input
- /dev/uinput
2. **ptyDriver** - Terminal emulation input driver (cross-platform)
- python-pyte
3. **atspiDriver** - AT-SPI input driver for desktop environments
- python-pyatspi2
### Remote Drivers:
1. **unixDriver** - Unix socket remote control (default)
- socat (for command-line interaction)
2. **tcpDriver** - TCP socket remote control (localhost only)
- netcat or telnet (for command-line interaction)
### Screen Drivers:
1. "vcsaDriver" screen driver for linux VCSA devices
- python-dbus
- Read permission to the following files and services:
- /sys/devices/virtual/tty/tty0/active
- /dev/tty[1-64]
- /dev/vcsa[1-64]
- read logind DBUS
2. "ptyDriver" terminal emulation driver
- python-pyte
1. **vcsaDriver** - Linux VCSA devices driver (recommended for Linux TTY)
- python-dbus
- Read permission to the following files and services:
- /sys/devices/virtual/tty/tty0/active
- /dev/tty[1-64]
- /dev/vcsa[1-64]
- read logind DBUS
2. **ptyDriver** - Terminal emulation driver (cross-platform)
- python-pyte
### Speech Drivers:
1. "genericDriver" (default) speech driver for sound as subprocess:
- espeak or espeak-ng
2. "speechdDriver" speech driver for Speech-dispatcher:
- Speech-dispatcher
- python-speechd
3. "emacspeakDriver" speech driver for emacspeak
- emacspeak
1. **speechdDriver** - Speech-dispatcher driver (recommended)
- Speech-dispatcher
- python-speechd
2. **genericDriver** - Generic subprocess speech driver
- espeak or espeak-ng (or any TTS command)
3. **debugDriver** - Debug speech driver for testing
- No dependencies
### Sound Drivers:
1. "genericDriver" (default) sound driver for sound as subprocess:
- Sox
2. "gstreamerDriver" sound driver for gstreamer
- gstreamer >=1.0
- GLib
1. **genericDriver** (default) - Generic subprocess sound driver
- Sox with opus support (recommended)
2. **gstreamerDriver** - GStreamer sound driver
- gstreamer >=1.0
- GLib
3. **debugDriver** - Debug sound driver for testing
- No dependencies
## Extras:
@@ -91,48 +121,635 @@ If there is a package for your distrobution of choice, please let us know so we
- You can also just run it from Git without installing:
Requires root privileges
cd src/fenrir/
cd src/
sudo ./fenrir
Settings "settings.conf" is located in the "config" directory or after installation in /etc/fenrir/settings.
Take care to use drivers from the config matching your installed drivers.
By default it uses:
- sound driver: genericDriver (via sox, could configured in settings.conf)
- speech driver: genericDriver (via espeak or espeak-ng, could configured in settings.conf)
- input driver: evdevDriver
Settings are located in:
- **After installation**: `/etc/fenrir/settings/settings.conf`
- **Development**: `config/settings/settings.conf`
By default Fenrir uses:
- **Sound driver**: genericDriver (via sox)
- **Speech driver**: speechdDriver (via speech-dispatcher)
- **Input driver**: evdevDriver (Linux) or ptyDriver (other platforms)
- **Screen driver**: vcsaDriver (Linux TTY) or ptyDriver (terminal emulation)
## Configure pulseaudio
## Audio Configuration
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.
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.
just run the configuration script twice (once as user, once as root):
### Quick Setup - Direct Script Download
/usr/share/fenrirscreenreader/tools/configure_pulse.sh
sudo /usr/share/fenrirscreenreader/tools/configure_pulse.sh
For non-Fenrir users or quick setup, download and run these scripts directly:
The script is also located in the tools directory in git
#### 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
```
## Configure pipewire
#### 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
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.
# Run twice: once as user, once as root
./configure_pipewire.sh
sudo ./configure_pipewire.sh
```
just run the configuration script twice (once as user, once as root):
**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)
/usr/share/fenrirscreenreader/tools/configure_pipewire.sh
sudo /usr/share/fenrirscreenreader/tools/configure_pipewire.sh
### Using Installed Scripts
The script is also located in the tools directory in git
If you have Fenrir installed, the scripts are available at:
## localization
copy fenrir.mo translations file from fenrir/locale/your_language/LC_MESSAGES/fenrir.mo to /usr/share/locale/your_language/LC_MESSAGES/fenrir.mo
**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
1. **Start Fenrir**:
```bash
sudo systemctl start fenrir # If installed as service
# OR
sudo fenrir # Run directly
```
2. **Basic Navigation**:
- **Fenrir Key**: By default `Insert`, `Keypad Insert`, or `Meta/Super` key
- **Tutorial Mode**: `Fenrir + H` to learn all commands interactively
- **Quit Fenrir**: `Fenrir + Q`
3. **Essential Commands**:
- `Ctrl` - Stop speech (shut up)
- `Fenrir + Keypad 5` - Read current screen
- `Keypad 8` - Read current line
- `Keypad 5` - Read current word
- `Keypad 2` - Read current character
- `Fenrir + T` - Announce time
- `Fenrir + S` - Spell check current word
- `Fenrir + Keypad *` - Toggle table mode / highlight tracking
### Keyboard Layouts
Fenrir supports two main keyboard layouts:
- **Desktop Layout**: Uses numeric keypad for navigation (recommended for desktop users)
- **Laptop Layout**: Alternative bindings for keyboards without numeric keypad
Configure in `/etc/fenrir/settings/settings.conf`:
```ini
[keyboard]
keyboardLayout=desktop # or 'laptop'
```
### First Time Setup
1. **Enable Fenrir at boot**:
```bash
sudo systemctl enable fenrir
```
2. **Configure audio** (if needed):
- For PulseAudio: Run configure_pulse.sh script (see below)
- For PipeWire: Run configure_pipewire.sh script (see below)
3. **Test speech**:
```bash
# Test speech-dispatcher directly
sudo spd-say "Hello World"
```
## Remote Control
Fenrir includes a powerful remote control system that allows external applications and scripts to control Fenrir through Unix sockets or TCP connections. This is particularly useful for automation, integration with other applications, or providing alternative control methods.
### Configuration
Enable remote control in `/etc/fenrir/settings/settings.conf`:
```ini
[remote]
enable=True
driver=unixDriver # or tcpDriver
port=22447 # for TCP driver
socketFile= # custom socket path (optional)
enableSettingsRemote=True # allow settings changes
enableCommandRemote=True # allow command execution
```
### Remote Drivers
1. **unixDriver** (recommended): Uses Unix domain sockets
- Socket location: `/tmp/fenrirscreenreader-deamon.sock` (TTY mode) or `/tmp/fenrirscreenreader-<pid>.sock`
- More secure, local-only access
- Works with `socat`
2. **tcpDriver**: Uses TCP sockets on localhost
- Default port: 22447
- Works with `netcat`, `telnet`, or any TCP client
- Local connections only (127.0.0.1)
### Using socat with Unix Sockets
The `socat` command provides the easiest way to send commands to Fenrir:
#### Basic Speech Control
```bash
# Interrupt current speech
echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Speak custom text
echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Temporarily disable speech (until next keystroke)
echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Settings Control
```bash
# Enable highlight tracking mode
echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change speech parameters
echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set speech#pitch=0.6" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set speech#volume=0.9" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change punctuation level (none/some/most/all)
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set general#punctuationLevel=none" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Voice and TTS engine control
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set speech#module=espeak-ng" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Disable sound temporarily
echo "setting set sound#enabled=False" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set sound#volume=0.5" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Keyboard and input settings
echo "setting set keyboard#charEchoMode=1" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting set keyboard#wordEcho=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Screen control (ignore specific TTYs)
echo "setting set screen#ignoreScreen=1,2,3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Multiple settings at once
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuationLevel=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset all settings to defaults
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Save current settings
echo "setting save" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
echo "setting saveas /tmp/my-fenrir-settings.conf" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Clipboard Operations
```bash
# Place text into clipboard
echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Export clipboard to file
echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Window Management
```bash
# Define a window area (x1 y1 x2 y2)
echo "command window 0 0 80 24" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset window to full screen
echo "command resetwindow" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### VMenu Control
```bash
# Set virtual menu context
echo "command vmenu nano/file" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset virtual menu
echo "command resetvmenu" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
#### Application Control
```bash
# Quit Fenrir
echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
```
### Using TCP Driver
If using the TCP driver, replace socat commands with netcat:
```bash
# Using netcat
echo "command say Hello from TCP" | nc localhost 22447
# Using telnet
echo "command interrupt" | telnet localhost 22447
```
### Remote Command Reference
#### Command Format
```
command <action> [parameters]
setting <action> [parameters]
```
#### Available Commands
**Speech Commands:**
- `command say <text>` - Speak the specified text
- `command interrupt` - Stop current speech
- `command tempdisablespeech` - Disable speech until next key press
**Clipboard Commands:**
- `command clipboard <text>` - Add text to clipboard
- `command exportclipboard` - Export clipboard to file
**Window Commands:**
- `command window <x1> <y1> <x2> <y2>` - Define window area
- `command resetwindow` - Reset to full screen
**VMenu Commands:**
- `command vmenu <menu_path>` - Set vmenu context
- `command resetvmenu` - Reset vmenu
**Application Commands:**
- `command quitapplication` - Quit Fenrir
#### Available Settings
**Settings Commands:**
- `setting set <section>#<key>=<value>` - Set configuration value
- `setting reset` - Reset all settings to defaults
- `setting save [path]` - Save current settings
- `setting saveas <path>` - Save settings to specific file
**Common Settings:**
*Speech Settings:*
- `speech#enabled=True/False` - Enable/disable speech
- `speech#rate=0.1-1.0` - Speech rate (speed)
- `speech#pitch=0.1-1.0` - Speech pitch (tone)
- `speech#volume=0.1-1.0` - Speech volume
- `speech#voice=voice_name` - Voice selection (e.g., "en-us+f3")
- `speech#module=module_name` - TTS module (e.g., "espeak-ng")
- `speech#driver=driver_name` - Speech driver (speechdDriver/genericDriver)
- `speech#autoReadIncoming=True/False` - Auto-read new text
*Sound Settings:*
- `sound#enabled=True/False` - Enable/disable sound
- `sound#volume=0.1-1.0` - Sound volume
- `sound#driver=driver_name` - Sound driver (genericDriver/gstreamerDriver)
- `sound#theme=theme_name` - Sound theme
*General Settings:*
- `general#punctuationLevel=none/some/most/all` - Punctuation verbosity
- `general#debugLevel=0-3` - Debug level
- `general#emoticons=True/False` - Enable emoticon replacement
- `general#autoSpellCheck=True/False` - Automatic spell checking
*Focus Settings:*
- `focus#cursor=True/False` - Follow text cursor
- `focus#highlight=True/False` - Follow text highlighting
*Keyboard Settings:*
- `keyboard#charEchoMode=0-2` - Character echo (0=none, 1=always, 2=capslock only)
- `keyboard#wordEcho=True/False` - Echo complete words
- `keyboard#charDeleteEcho=True/False` - Echo deleted characters
- `keyboard#interruptOnKeyPress=True/False` - Interrupt speech on key press
*Screen Settings:*
- `screen#ignoreScreen=1,2,3` - TTY screens to ignore
- `screen#autodetectIgnoreScreen=True/False` - Auto-detect screens to ignore
- `screen#screenUpdateDelay=float` - Screen update delay
*Time Settings:*
- `time#enabled=True/False` - Enable time announcements
- `time#presentTime=True/False` - Announce time
- `time#presentDate=True/False` - Announce date changes
- `time#delaySec=seconds` - Announcement interval
- `time#onMinutes=00,30` - Specific minutes to announce
## Table Navigation
Fenrir includes advanced table navigation capabilities for working with tabular data in terminal applications, CSV files, and formatted text output.
### Entering Table Mode
Table mode is activated through the **toggle_highlight_tracking** command, which cycles through three focus modes:
1. **Highlight tracking mode** (default) - Follows text highlighting
2. **Cursor tracking mode** - Follows text cursor movement
3. **Table mode** - Enables table navigation
**Key bindings:**
- **Desktop layout**: `Fenrir + Keypad *` (asterisk)
- **Laptop layout**: `Fenrir + Y`
Press the key combination repeatedly to cycle through modes until you hear "table mode enabled".
### Table Navigation Commands
#### Column Navigation (Desktop Layout)
- **Next column**: `Keypad 6` - Move to next table column
- **Previous column**: `Keypad 4` - Move to previous table column
- **First column**: `Fenrir + Keypad 4` - Jump to first column of current row
- **Last column**: `Fenrir + Keypad 6` - Jump to last column of current row
#### Column Navigation (Laptop Layout)
- **Next column**: `Fenrir + L` - Move to next table column
- **Previous column**: `Fenrir + J` - Move to previous table column
- **First column**: `Fenrir + Shift + J` - Jump to first column of current row
- **Last column**: `Fenrir + Shift + L` - Jump to last column of current row
#### Cell Character Navigation
- **First character in cell**: `Fenrir + Keypad 1` (desktop) or `Fenrir + Ctrl + J` (laptop)
- **Last character in cell**: `Fenrir + Keypad 3` (desktop) or `Fenrir + Ctrl + L` (laptop)
### Setting Column Headers
For better navigation experience, you can set column headers:
1. **Navigate to header row**: Use normal navigation to reach the row containing column headers
2. **Set headers**: Press `Fenrir + X` to mark the current line as the header row
3. **Navigation feedback**: Column headers will be announced along with cell content
### Table Detection
Fenrir automatically detects table structures using multiple strategies:
- **Delimited text**: CSV, pipe-separated (`|`), semicolon-separated (`;`), tab-separated
- **Aligned columns**: Space-aligned columns (2+ spaces between columns)
- **Flexible parsing**: Handles various table formats commonly found in terminal applications
### Table Mode Features
- **Cell-by-cell navigation**: Navigate through table cells with precise positioning
- **Column header support**: Set and announce column headers for better context
- **Boundary feedback**: Audio cues when reaching start/end of rows
- **Empty cell handling**: Blank cells are announced as "blank"
- **Independent tracking**: Table position is maintained independently of cursor movement
### Speech Output in Table Mode
When navigating in table mode, Fenrir announces:
- **Cell content** followed by **column header/name**
- **Boundary notifications**: "end of line", "start of line"
- **Position indicators**: "first character in cell [column name]"
### Example Usage
```bash
# Working with CSV data
cat data.csv
Name,Age,City
Alice,30,New York
Bob,25,Los Angeles
# 1. Press Fenrir + Keypad * until "table mode enabled"
# 2. Navigate to "Name,Age,City" line
# 3. Press Fenrir + X to set headers
# 4. Use Keypad 4/6 to navigate between columns
# 5. Each cell will be announced with its column header
```
## Progress Bar Monitoring
Fenrir provides intelligent progress bar detection and audio feedback for various progress indicators commonly found in terminal applications.
### Enabling Progress Monitoring
**Command**: `progress_bar_monitor` (no default key binding - assign manually)
To enable progress monitoring:
1. Add a key binding in your keyboard layout file
2. Or use the remote control system: `echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
### Progress Detection Patterns
Fenrir automatically detects various progress indicator formats:
#### 1. Percentage Progress
```
Download: 45%
Processing: 67.5%
Installing: 100%
```
#### 2. Fraction Progress
```
Files: 15/100
Progress: 3 of 10
Step 7/15
```
#### 3. Progress Bars
```
[#### ] 40%
[====> ] 50%
[**********] 100%
```
#### 4. Activity Indicators
```
Loading...
Processing...
Working...
Installing...
Downloading...
Compiling...
Building...
```
### Audio Feedback
#### Progress Tones
- **Ascending tones**: 400Hz to 1200Hz frequency range
- **Percentage mapping**: 0% = 400Hz, 100% = 1200Hz
- **Smooth progression**: Frequency increases proportionally with progress
#### Activity Indicators
- **Steady beep**: 800Hz tone every 2 seconds for ongoing activity
- **Non-intrusive**: Beeps don't interrupt speech or other audio
### Progress Monitoring Features
- **Automatic detection**: No manual configuration required
- **Multiple format support**: Handles various progress indicator styles
- **Prompt awareness**: Automatically pauses when command prompts are detected
- **Non-blocking**: Progress tones don't interrupt speech or other functionality
- **Configurable**: Can be enabled/disabled as needed
### Usage Examples
```bash
# Enable progress monitoring
echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Common scenarios where progress monitoring is useful:
wget https://example.com/large-file.zip # Download progress
tar -xvf archive.tar.gz # Extraction progress
make -j4 # Compilation progress
pacman -S package # Package installation
rsync -av source/ destination/ # File synchronization
```
### Customization
Progress monitoring can be configured through settings:
- **Default enabled**: Set `progressMonitoring=True` in sound section
- **Sound integration**: Works with all sound drivers (sox, gstreamer)
- **Remote control**: Enable/disable through remote commands
### Scripting Examples
#### Bash Script for Speech Notifications
```bash
#!/bin/bash
# notify_fenrir.sh - Send notifications to Fenrir
SOCKET="/tmp/fenrirscreenreader-deamon.sock"
fenrir_say() {
echo "command say $1" | socat - UNIX-CLIENT:$SOCKET
}
fenrir_interrupt() {
echo "command interrupt" | socat - UNIX-CLIENT:$SOCKET
}
# Usage examples
fenrir_say "Build completed successfully"
fenrir_interrupt
```
#### Python Integration
```python
#!/usr/bin/env python3
import socket
import os
def send_fenrir_command(command):
"""Send command to Fenrir via Unix socket"""
socket_path = "/tmp/fenrirscreenreader-deamon.sock"
if os.path.exists(socket_path):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.connect(socket_path)
sock.send(command.encode('utf-8'))
finally:
sock.close()
# Examples
send_fenrir_command("command say Processing complete")
send_fenrir_command("setting set speech#rate=0.9")
```
### Security Considerations
- Unix sockets are accessible only to the user running Fenrir
- TCP driver binds only to localhost (127.0.0.1)
- Socket file permissions are set to write-only (0o222)
- Commands are processed with Fenrir's privileges
- Settings changes can be disabled via `enableSettingsRemote=False`
- Command execution can be disabled via `enableCommandRemote=False`
### Troubleshooting
**Socket not found:**
- Verify Fenrir is running: `ps aux | grep fenrir`
- Check socket location: `/tmp/fenrirscreenreader-*`
- Ensure remote driver is enabled in settings
**Commands not working:**
- Verify `enableCommandRemote=True` in settings
- Check Fenrir debug logs: `/var/log/fenrir.log`
- Test with simple command: `echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
## Command Line Options
Fenrir supports several command-line options for different use cases:
```
fenrir [OPTIONS]
```
### Options:
- `-h, --help` - Show help message and exit
- `-v, --version` - Show version information and exit
- `-f, --foreground` - Run in foreground (don't daemonize)
- `-s, --setting SETTING-FILE` - Path to custom settings file
- `-o, --options SECTION#SETTING=VALUE;..` - Override settings file options
- `-d, --debug` - Enable debug mode
- `-p, --print` - Print debug messages to screen
- `-e, --emulated-pty` - Use PTY emulation with escape sequences for input (enables desktop/X/Wayland usage)
- `-E, --emulated-evdev` - Use PTY emulation with evdev for input (single instance)
- `-F, --force-all-screens` - Force Fenrir to respond on all screens, ignoring ignoreScreen setting
- `-i, -I, --ignore-screen SCREEN` - Ignore specific screen(s). Can be used multiple times. Combines with existing ignore settings.
### Examples:
```bash
# Run in foreground with debug output
sudo fenrir -f -d
# Use PTY emulation for desktop use
sudo fenrir -e
# Override settings via command line
sudo fenrir -o "speech#rate=0.8;sound#volume=0.5"
# Force Fenrir to work on all screens (ignore ignoreScreen setting)
sudo fenrir -F
# Ignore specific screens
sudo fenrir --ignore-screen 1
sudo fenrir -i 1 -i 2 # Ignore screens 1 and 2
```
## Localization
Translation files are located in the `locale/` directory. To install translations:
```bash
# Copy translation file to system location
sudo cp locale/your_language/LC_MESSAGES/fenrir.mo /usr/share/locale/your_language/LC_MESSAGES/fenrir.mo
```
Available languages:
- German (de)
- Spanish (es)
- Polish (pl)
- Portuguese (pt)
- Russian (ru)
## Documentation and Support
- Email list: [stormux+subscribe@groups.io](mailto:stormux+subscribe@groups.io?subject=subscribe) with the subject subscribe.
- [Fenrir Wiki](https://git.stormux.org/storm/fenrir/wiki)
- IRC: irc.stormux.org #stormux
- **Email list**: [stormux+subscribe@groups.io](mailto:stormux+subscribe@groups.io?subject=subscribe) with the subject subscribe
- **Fenrir Wiki**: [https://git.stormux.org/storm/fenrir/wiki](https://git.stormux.org/storm/fenrir/wiki)
- **IRC**: irc.stormux.org #stormux
- **Issues**: Report bugs and feature requests on the project repository
+192
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.
-3
View File
@@ -1,3 +0,0 @@
V2.0
Cleanup folders and config files.
+1 -3
View File
@@ -4,12 +4,10 @@ Wants=systemd-udev-settle.service
After=systemd-udev-settle.service getty.target
[Service]
Type=forking
PIDFile=/var/run/fenrir.pid
PIDFile=/run/fenrir.pid
ExecStart=/usr/bin/fenrir
ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always
#Group=fenrirscreenreader
#User=fenrirscreenreader
[Install]
WantedBy=getty.target
+1 -1
View File
@@ -4,7 +4,7 @@ Wants=systemd-udev-settle.service
After=systemd-udev-settle.service sound.target
[Service]
Type=forking
PIDFile=/var/run/fenrir.pid
PIDFile=/run/fenrir.pid
ExecStart=/usr/local/bin/fenrir
ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always
+2 -2
View File
@@ -1,5 +1,5 @@
Please report Bugs and feature requests to:
https://github.com/chrys87/fenrir/issues
Please report bugs and feature requests to:
https://git.stormux.org/storm/fenrir/issues
For bugs, please provide a debug file that shows the issue.
How to create a debug file:
+9 -4
View File
@@ -50,7 +50,7 @@ def check_dependency(dep: Dependency) -> bool:
dependencyList = [
# Core dependencies
Dependency('FenrirCore', 'core', 'core',
pythonImports=['daemonize', 'enchant']),
pythonImports=['daemonize', 'enchant', 'pyperclip', 'setproctitle']),
# Screen drivers
Dependency('DummyScreen', 'screen', 'dummyDriver'),
@@ -58,7 +58,7 @@ dependencyList = [
pythonImports=['dbus'],
devicePaths=['/dev/vcsa']),
Dependency('PTY', 'screen', 'ptyDriver',
pythonImports=['pyte']),
pythonImports=['pyte', 'xdg']),
# Input drivers
Dependency('DummyInput', 'input', 'dummyDriver'),
@@ -82,7 +82,11 @@ dependencyList = [
Dependency('Speechd', 'speech', 'speechdDriver',
pythonImports=['speechd']),
Dependency('GenericSpeech', 'speech', 'genericDriver',
checkCommands=['espeak-ng'])
checkCommands=['espeak-ng']),
# Additional dependencies
Dependency('Pexpect', 'core', 'pexpectDriver',
pythonImports=['pexpect'])
]
defaultModules = {
@@ -90,7 +94,8 @@ defaultModules = {
'VCSA',
'Evdev',
'GenericSpeech',
'GenericSound'
'GenericSound',
'Pexpect'
}
def check_all_dependencies():
+42
View File
@@ -1,4 +1,46 @@
# Fenrir Keyboard Configuration
This directory contains keyboard layout files for Fenrir screen reader.
## Available Layouts
- **desktop.conf** - Desktop layout using numeric keypad (recommended)
- **laptop.conf** - Laptop layout for keyboards without numeric keypad
- **nvda-desktop.conf** - NVDA-compatible desktop layout
- **nvda-laptop.conf** - NVDA-compatible laptop layout
- **pty.conf** - PTY emulation layout for terminal use
- **pty2.conf** - Alternative PTY emulation layout
## Key Features
### Table Navigation
- **Toggle table mode**: `Fenrir + Keypad *` (desktop) or `Fenrir + Y` (laptop)
- **Column navigation**: `Keypad 4/6` (desktop) or `Fenrir + J/L` (laptop)
- **Row boundaries**: `Fenrir + Keypad 4/6` (desktop) or `Fenrir + Shift + J/L` (laptop)
- **Set headers**: `Fenrir + X` in table mode
### Progress Bar Monitoring
- **Monitor progress**: `progress_bar_monitor` command (assign key binding manually)
- **Auto-detection**: Percentage, fractions, progress bars, activity indicators
- **Audio feedback**: Ascending tones (400Hz-1200Hz) for progress
### Review Mode
- **Basic navigation**: `Keypad 7/8/9` (lines), `Keypad 4/5/6` (words), `Keypad 1/2/3` (characters)
- **Exit review**: `Fenrir + Keypad .`
- **Screen reading**: `Fenrir + Keypad 5` (current screen)
## Configuration
To change keyboard layout, edit `/etc/fenrir/settings/settings.conf`:
```ini
[keyboard]
keyboardLayout=desktop # or laptop, nvda-desktop, nvda-laptop, pty, pty2
```
## Available Key Constants
Keymap for Fenrir
KEY_RESERVED
KEY_ESC
+8 -3
View File
@@ -73,7 +73,8 @@ KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
KEY_KPPLUS=last_incoming
KEY_KPPLUS=progress_bar_monitor
#KEY_FENRIR,KEY_KPPLUS=silence_until_prompt
KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
@@ -113,6 +114,8 @@ KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SHIFT,KEY_F10=voice_browser_safe
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_F10=apply_tested_voice
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
@@ -123,6 +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_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout
+9 -4
View File
@@ -75,11 +75,12 @@ KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F4=toggle_speech
KEY_FENRIR,KEY_ENTER=temp_disable_speech
KEY_FENRIR,KEY_SHIFT,KEY_P=progress_bar_monitor
KEY_FENRIR,KEY_SHIFT,KEY_ENTER=toggle_auto_read
KEY_FENRIR,KEY_SHIFT,KEY_CTRL,KEY_P=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_ENTER=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_ENTER=toggle_output
KEY_FENRIR,KEY_SHIFT,KEY_E=toggle_emoticons
KEY_FENRIR,KEY_ENTER=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_Y=toggle_highlight_tracking
#=toggle_barrier
@@ -111,6 +112,8 @@ KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SHIFT,KEY_F10=voice_browser_safe
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_F10=apply_tested_voice
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
@@ -123,6 +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_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout
-128
View File
@@ -1,128 +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=last_incoming
#=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
-128
View File
@@ -1,128 +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
+2
View File
@@ -85,3 +85,5 @@ alt+f12 - quit fenrir
^[[1;3F=temp_disable_speech
# control+end - toggle auto read
^[[1;5F=toggle_auto_read
# F12 - cycle keyboard layout
^[[24~=cycle_keyboard_layout
-218
View File
@@ -1,218 +0,0 @@
# Fenrir comment: copy of speakup DefaultKeyAssignments converted to fenrir syntax
# Fenrir comment: https://android.googlesource.com/kernel/msm/+/android-7.1.0_r0.2/drivers/staging/speakup/DefaultKeyAssignments
# Fenrir comment: The insert or shift key named below is the fenrir key
# This file is intended to give you an overview of the default keys used
# by speakup for it's review functions. You may change them to be
# anything you want but that will take some familiarity with key
# mapping.
# We have remapped the insert or zero key on the keypad to act as a
# shift key. Well, actually as an altgr key. So in the following list
# InsKeyPad-period means hold down the insert key like a shift key and
# hit the keypad period.
# KeyPad-8 Say current Line
KEY_KP8=review_curr_line
# InsKeyPad-8 say from top of screen to reading cursor.
KEY_FENRIR,KEY_KP8=curr_screen_before_cursor
# KeyPad-7 Say Previous Line (UP one line)
KEY_KP7=review_prev_line
# KeyPad-9 Say Next Line (down one line)
KEY_KP9=review_next_line
# KeyPad-5 Say Current Word
KEY_KP5=review_curr_word
# InsKeyPad-5 Spell Current Word
KEY_FENRIR,KEY_KP5=review_curr_word_phonetic
# KeyPad-4 Say Previous Word (left one word)
KEY_KP4=review_prev_word
# InsKeyPad-4 say from left edge of line to reading cursor.
KEY_FENRIR,KEY_KP4=cursor_read_line_to_cursor
# KeyPad-6 Say Next Word (right one word)
KEY_KP6=review_next_word
# InsKeyPad-6 Say from reading cursor to right edge of line.
KEY_FENRIR,KEY_KP6=cursor_read_to_end_of_line
# KeyPad-2 Say Current Letter
KEY_KP2=review_curr_char
# InsKeyPad-2 say current letter phonetically
KEY_FENRIR,KEY_KP2=review_curr_char_phonetic
# KeyPad-1 Say Previous Character (left one letter)
KEY_KP1=review_prev_char
# KeyPad-3 Say Next Character (right one letter)
KEY_KP3=review_next_char
# KeyPad-plus Say Entire Screen
KEY_KPPLUS=curr_screen
# InsKeyPad-plus Say from reading cursor line to bottom of screen.
KEY_FENRIR,KEY_KPPLUS=curr_screen_after_cursor
# KeyPad-Minus Park reading cursor (toggle)
# TODO
# InsKeyPad-minus Say character hex and decimal value.
# TODO
# KeyPad-period Say Position (current line, position and console)
KEY_KPDOT=cursor_position
# InsKeyPad-period say colour attributes of current position.
KEY_FENRIR,KEY_KPDOT=attribute_cursor
# InsKeyPad-9 Move reading cursor to top of screen (insert pgup)
KEY_FENRIR,KEY_KP9=review_bottom
# InsKeyPad-3 Move reading cursor to bottom of screen (insert pgdn)
KEY_FENRIR,KEY_KP3=review_top
# InsKeyPad-7 Move reading cursor to left edge of screen (insert home)
KEY_FENRIR,KEY_KP7=review_screen_first_char
# InsKeyPad-1 Move reading cursor to right edge of screen (insert end)
KEY_FENRIR,KEY_KP1=review_screen_last_char
# ControlKeyPad-1 Move reading cursor to last character on current line.
KEY_CTRL,KEY_KP1=review_line_end
# KeyPad-Enter Shut Up (until another key is hit) and sync reading cursor
KEY_KPENTER=temp_disable_speech
# InsKeyPad-Enter Shut Up (until toggled back on).
KEY_FENRIR,KEY_KPENTER=toggle_speech
# InsKeyPad-star n<x|y> go to line (y) or column (x). Where 'n' is any
# allowed value for the row or column for your current screen.
# TODO
# KeyPad-/ Mark and Cut screen region.
KEY_KPSLASH=copy_marked_to_clipboard
# InsKeyPad-/ Paste screen region into any console.
KEY_FENRIR,KEY_KPSLASH=paste_clipboard
# Hitting any key while speakup is outputting speech will quiet the
# synth until it has caught up with what is being printed on the
# console.
# following by other fenrir commands
KEY_FENRIR,KEY_H=toggle_tutorial_mode
KEY_CTRL=shut_up
KEY_FENRIR,KEY_KP4=review_line_begin
#=review_line_end
#=review_line_first_char
#=review_line_last_char
KEY_FENRIR,KEY_ALT,KEY_1=present_first_line
KEY_FENRIR,KEY_ALT,KEY_2=present_last_line
KEY_FENRIR,KEY_SHIFT,KEY_KP4=review_prev_word_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP6=review_next_word_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP1=review_prev_char_phonetic
KEY_FENRIR,KEY_SHIFT,KEY_KP3=review_next_char_phonetic
KEY_FENRIR,KEY_CTRL,KEY_KP8=review_up
KEY_FENRIR,KEY_CTRL,KEY_KP2=review_down
#=exit_review
KEY_FENRIR,KEY_I=indent_curr_line
KEY_KPPLUS=curr_screen
#=cursor_column
#=cursor_lineno
#=braille_flush
#=braille_return_to_cursor
#=braille_pan_left
#=braille_pan_right
KEY_FENRIR,KEY_CTRL,KEY_1=clear_bookmark_1
KEY_FENRIR,KEY_SHIFT,KEY_1=set_bookmark_1
KEY_FENRIR,KEY_1=bookmark_1
KEY_FENRIR,KEY_CTRL,KEY_2=clear_bookmark_2
KEY_FENRIR,KEY_SHIFT,KEY_2=set_bookmark_2
KEY_FENRIR,KEY_2=bookmark_2
KEY_FENRIR,KEY_CTRL,KEY_3=clear_bookmark_3
KEY_FENRIR,KEY_SHIFT,KEY_3=set_bookmark_3
KEY_FENRIR,KEY_3=bookmark_3
KEY_FENRIR,KEY_CTRL,KEY_4=clear_bookmark_4
KEY_FENRIR,KEY_SHIFT,KEY_4=set_bookmark_4
KEY_FENRIR,KEY_4=bookmark_4
KEY_FENRIR,KEY_CTRL,KEY_5=clear_bookmark_5
KEY_FENRIR,KEY_SHIFT,KEY_5=set_bookmark_5
KEY_FENRIR,KEY_5=bookmark_5
KEY_FENRIR,KEY_CTRL,KEY_6=clear_bookmark_6
KEY_FENRIR,KEY_SHIFT,KEY_6=set_bookmark_6
KEY_FENRIR,KEY_6=bookmark_6
KEY_FENRIR,KEY_CTRL,KEY_7=clear_bookmark_7
KEY_FENRIR,KEY_SHIFT,KEY_7=set_bookmark_7
KEY_FENRIR,KEY_7=bookmark_7
KEY_FENRIR,KEY_CTRL,KEY_8=clear_bookmark_8
KEY_FENRIR,KEY_SHIFT,KEY_8=set_bookmark_8
KEY_FENRIR,KEY_8=bookmark_8
KEY_FENRIR,KEY_CTRL,KEY_9=clear_bookmark_9
KEY_FENRIR,KEY_SHIFT,KEY_9=set_bookmark_9
KEY_FENRIR,KEY_9=bookmark_9
KEY_FENRIR,KEY_CTRL,KEY_0=clear_bookmark_10
KEY_FENRIR,KEY_SHIFT,KEY_0=set_bookmark_10
KEY_FENRIR,KEY_0=bookmark_10
KEY_FENRIR,KEY_KPSLASH=set_window_application
2,KEY_FENRIR,KEY_KPSLASH=clear_window_application
#=last_incoming
KEY_FENRIR,KEY_F2=toggle_braille
KEY_FENRIR,KEY_F3=toggle_sound
KEY_FENRIR,KEY_F9=toggle_punctuation_level
KEY_FENRIR,KEY_RIGHTBRACE=toggle_auto_spell_check
KEY_FENRIR,KEY_BACKSLASH=toggle_output
KEY_FENRIR,KEY_CTRL,KEY_E=toggle_emoticons
key_FENRIR,KEY_KPENTER=toggle_auto_read
KEY_FENRIR,KEY_CTRL,KEY_T=toggle_auto_time
KEY_FENRIR,KEY_KPASTERISK=toggle_highlight_tracking
KEY_FENRIR,KEY_KPMINUS=toggle_barrier
KEY_FENRIR,KEY_Q=quit_fenrir
KEY_FENRIR,KEY_T=time
2,KEY_FENRIR,KEY_T=date
KEY_KPSLASH=toggle_auto_indent
#=toggle_has_attribute
KEY_FENRIR,KEY_S=spell_check
2,KEY_FENRIR,KEY_S=add_word_to_spell_check
KEY_FENRIR,KEY_SHIFT,KEY_S=remove_word_from_spell_check
KEY_FENRIR,KEY_BACKSPACE=forward_keypress
KEY_FENRIR,KEY_ALT,KEY_UP=inc_sound_volume
KEY_FENRIR,KEY_ALT,KEY_DOWN=dec_sound_volume
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_C=clear_clipboard
KEY_FENRIR,KEY_HOME=first_clipboard
KEY_FENRIR,KEY_END=last_clipboard
KEY_FENRIR,KEY_PAGEUP=prev_clipboard
KEY_FENRIR,KEY_PAGEDOWN=next_clipboard
KEY_FENRIR,KEY_SHIFT,KEY_C=curr_clipboard
KEY_FENRIR,KEY_CTRL,KEY_C=copy_last_echo_to_clipboard
KEY_FENRIR,KEY_F5=import_clipboard_from_file
KEY_FENRIR,KEY_F6=export_clipboard_to_file
KEY_FENRIR,KEY_CTRL,KEY_SHIFT,KEY_X=remove_marks
KEY_FENRIR,KEY_X=set_mark
KEY_FENRIR,KEY_SHIFT,KEY_X=marked_text
KEY_FENRIR,KEY_F10=toggle_vmenu_mode
KEY_FENRIR,KEY_SPACE=current_quick_menu_entry
KEY_FENRIR,KEY_CTRL,KEY_SPACE=current_quick_menu_value
KEY_FENRIR,KEY_RIGHT=next_quick_menu_entry
KEY_FENRIR,KEY_UP=next_quick_menu_value
KEY_FENRIR,KEY_LEFT=prev_quick_menu_entry
KEY_FENRIR,KEY_DOWN=prev_quick_menu_value
KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
+2 -2
View File
@@ -4,8 +4,8 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict]
none:===:
some:===:-$~+*-/\@#_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
some:===:-$~+*-/\@#
most:===:.,:-_$~+*-/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
[punctDict]
+2 -2
View File
@@ -4,8 +4,8 @@
# the entrys are seperated with :===: in words colon tripple equal colon ( to not collide with substitutions)
[levelDict]
none:===:
some:===:-$~+*-/\@_
most:===:.,:-$~+*-/\@!#%^&*()[]}{<>;
some:===:-$~+*-/\@
most:===:.,:-$~+*-_/\@!#%^&*()[]}{<>;
all:===:!"#$%& \'()*+,-./:;<=>?@[\\]^_`{|}~
[punctDict]
+49 -10
View File
@@ -3,9 +3,9 @@
enabled=True
# Select the driver used to play sounds, choices are genericDriver and gstreamerDriver.
# Sox is the default.
#driver=gstreamerDriver
driver=genericDriver
# Gstreamer is the default.
driver=gstreamerDriver
#driver=genericDriver
# Sound themes. These are the pack of sounds used for sound alerts.
# Sound packs may be located at /usr/share/sounds
@@ -28,6 +28,9 @@ genericPlayFileCommand=play -q -v fenrirVolume fenrirSoundFile
#the following command is used to generate a frequency beep
genericFrequencyCommand=play -q -v fenrirVolume -n -c1 synth fenrirDuration sine fenrirFrequence
# Enable progress bar monitoring with ascending tones by default
progressMonitoring=True
[speech]
# Turn speech on or off:
enabled=True
@@ -92,7 +95,7 @@ fenrirMaxRate=450
driver=vcsaDriver
encoding=auto
screenUpdateDelay=0.05
ignoreScreen=
ignoreScreen=7
autodetectIgnoreScreen=True
[keyboard]
@@ -107,7 +110,7 @@ keyboardLayout=desktop
# echo chars while typing.
# 0 = None
# 1 = always
# 2 = only while capslock
# 2 = only while capslock (not compatible with capslock as fenrir key)
charEchoMode=1
# echo deleted chars
charDeleteEcho=True
@@ -121,7 +124,9 @@ interruptOnKeyPressFilter=
doubleTapTimeout=0.2
[general]
debugLevel=0
# Debug levels: 0=DEACTIVE, 1=ERROR, 2=WARNING, 3=INFO (most verbose)
# For production use, WARNING (2) provides good balance of useful info without spam
debugLevel=2
# debugMode sets where the debug output should send to:
# debugMode=File writes to debugFile (Default:/tmp/fenrir-PID.log)
# debugMode=Print just prints on the screen
@@ -130,6 +135,9 @@ debugFile=
punctuationProfile=default
punctuationLevel=some
respectPunctuationPause=True
# Replace undefined punctuation with spaces instead of removing them
# This improves readability of text with punctuation like [X]mute, IP addresses, etc.
replaceUndefinedPunctuationWithSpace=True
newLinePause=True
numberOfClipboards=50
# used path for "export_clipboard_to_file"
@@ -140,8 +148,8 @@ emoticons=True
# define the current Fenrir key
fenrirKeys=KEY_KP0,KEY_META,KEY_INSERT
scriptKeys=KEY_COMPOSE
timeFormat=%H:%M:%P
dateFormat=%A, %B %d, %Y
timeFormat=%%I:%%M%%P
dateFormat=%%A, %%B %%d, %%Y
autoSpellCheck=True
spellCheckLanguage=en_US
# path for your scripts "scriptKeys" functionality
@@ -163,7 +171,7 @@ autoPresentIndent=False
# 1 = sound only
# 2 = speak only
autoPresentIndentMode=1
# play a sound when attributes are changeing
# play a sound when attributes change
hasAttributes=True
# shell for PTY emulatiun (empty = default shell)
shell=
@@ -209,10 +217,41 @@ list=
[menu]
vmenuPath=
# quickMenu: Semicolon-separated list of settings for quick adjustment
# Format: section#setting;section#setting;...
# Supported settings:
# - speech#rate, speech#pitch, speech#volume (0.0-1.0)
# - speech#module, speech#voice (speechdDriver only, auto-added)
# Note: speech#module and speech#voice are automatically added when
# speechdDriver is active. Do not add them manually.
quickMenu=speech#rate;speech#pitch;speech#volume
[prompt]
# Custom prompt patterns for silence until prompt feature
# You can add your own shell prompt patterns as regular expressions
# Each pattern should be on a separate line, format: customPatterns=pattern1,pattern2,pattern3
#
# Built-in patterns include:
# - Shell prompts: $, #, >, user@host$, [user@host]$, bash-5.1$
# - Package manager prompts: [Y/n], [y/N], [Yes/No], (Y/n), (y/N)
# - sudo prompts: [sudo] password for user:, Password:, user's password:
# - Confirmation prompts: Press any key, Are you sure?, Please confirm
#
# Custom pattern examples:
# For PS1='[\u@\h \W] \$ ' use: \[.*@.*\s.*\]\s*[$#>]\s*
# For "[user@hostname ~] $" use: \[.*@.*\s.*\]\s*[$#>]\s*
# For custom prompts ending with specific strings, use patterns like: .*your_prompt_ending$
# For custom package manager prompts: .*your_package_manager.*\[[YyNn]/[YyNn]\].*
customPatterns=
# Specific prompt strings to match exactly (useful for very specific custom prompts)
# Format: exactMatches=prompt1,prompt2,prompt3
# Examples:
# exactMatches=[storm@fenrir ~] $,[root@fenrir ~] #,Continue installation? [Y/n]
exactMatches=
[time]
# automatic time anouncement
# automatic time announcement
enabled=False
# present time
presentTime=True
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -5
View File
@@ -30,9 +30,6 @@ ContentChanged='ContentChanged.wav'
# Speech has turned On or Off
SpeechOn='SpeechOn.wav'
SpeechOff='SpeechOff.wav'
# Braille has turned On or Off
BrailleOn='BrailleOn.wav'
BrailleOff='BrailleOff.wav'
# SoundIcons has turned On or Off
SoundOn='SoundOn.wav'
SoundOff='SoundOff.wav'
@@ -44,9 +41,8 @@ PlaceEndMark='PlaceEndMark.wav'
CopyToClipboard='CopyToClipboard.wav'
# Pasted on the screen
PasteClipboardOnScreen='PasteClipboardOnScreen.wav'
# An error accoured while speech or braille output or reading the screen
# An error accoured while speech output or reading the screen
ErrorSpeech='ErrorSpeech.wav'
ErrorBraille='ErrorBraille.wav'
ErrorScreen='ErrorScreen.wav'
# If you cursor over an text that has attributs (like color)
HasAttributes='has_attribute.wav'
+388 -3
View File
@@ -1,4 +1,389 @@
1. Basic
2. Commands
3. Useful API
# Fenrir Development Guide
This document provides information for developers who want to contribute to Fenrir or understand its architecture.
## Project Structure
Fenrir follows a modular, driver-based architecture:
```
src/fenrirscreenreader/
├── core/ # Core system modules
│ ├── fenrirManager.py # Main application manager
│ ├── screenManager.py # Screen handling
│ ├── inputManager.py # Input handling
│ ├── outputManager.py # Speech/sound output
│ ├── commandManager.py # Command system
│ └── settingsManager.py # Configuration management
├── commands/ # Command implementations
│ ├── commands/ # User-invoked commands
│ ├── onCursorChange/ # Cursor movement hooks
│ ├── onScreenUpdate/ # Screen update hooks
│ ├── onKeyInput/ # Key input hooks
│ └── help/ # Tutorial system
├── drivers/ # Driver implementations
│ ├── inputDriver/ # Input drivers (evdev, pty, atspi)
│ ├── screenDriver/ # Screen drivers (vcsa, pty)
│ ├── speechDriver/ # Speech drivers (speechd, generic)
│ └── soundDriver/ # Sound drivers (generic, gstreamer)
└── utils/ # Utility modules
```
## Core Architecture
### Driver System
Fenrir uses a pluggable driver architecture:
1. **Input Drivers**: Capture keyboard input
- evdevDriver: Linux evdev (recommended)
- ptyDriver: Terminal emulation
- atspiDriver: AT-SPI for desktop
2. **Screen Drivers**: Read screen content
- vcsaDriver: Linux VCSA devices
- ptyDriver: Terminal emulation
3. **Speech Drivers**: Text-to-speech output
- speechdDriver: Speech-dispatcher
- genericDriver: Command-line TTS
4. **Sound Drivers**: Audio output
- genericDriver: Sox-based
- gstreamerDriver: GStreamer
5. **Remote Drivers**: Remote control interfaces
- unixDriver: Unix socket control
- tcpDriver: TCP socket control
### Command System
Commands are Python modules that implement specific functionality:
```python
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Command description')
def run(self):
# Command implementation
pass
```
### Event Hooks
Fenrir supports various event hooks:
- **onCursorChange**: Triggered when cursor moves
- **onScreenUpdate**: Triggered on screen content changes
- **onKeyInput**: Triggered on key presses
- **onByteInput**: Triggered on byte-level input
- **onScreenChanged**: Triggered when switching screens
## Development Setup
### Requirements
- Python 3.6+
- python3-evdev
- python3-pyudev
- speech-dispatcher
- sox
### Getting Started
```bash
# Clone repository
git clone https://git.stormux.org/storm/fenrir.git
cd fenrir
# Install dependencies
sudo pip3 install -r requirements.txt
# Run from source
cd src/
sudo ./fenrir -f -d
```
### Testing
```bash
# Run in debug mode
sudo ./fenrir -f -d -p
# Debug output goes to:
# - Console (with -p flag)
# - /var/log/fenrir.log
```
## Creating Commands
### Basic Command
Create a file in `src/fenrirscreenreader/commands/commands/`:
```python
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('My custom command')
def run(self):
# Get current text
text = self.env['screen']['newContentText']
# Speak something
self.env['runtime']['outputManager'].presentText('Hello World')
# Play sound
self.env['runtime']['outputManager'].playSoundIcon('Accept')
```
### Key Bindings
Add key bindings in keyboard layout files:
`config/keyboard/desktop.conf` or `config/keyboard/laptop.conf`
```ini
[KEY_CTRL]#[KEY_ALT]#[KEY_H]=my_command
```
### Event Hooks
Create event handlers in appropriate directories:
```python
# onCursorChange/my_hook.py
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('My cursor change handler')
def run(self):
if self.env['runtime']['cursorManager'].isCursorHorizontalMove():
# Handle horizontal cursor movement
pass
```
## Creating Drivers
### Driver Template
```python
class driver():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
# Driver-specific methods...
```
### Input Driver
Implement these methods:
- `getInputEvent()`: Return input events
- `writeEventBuffer()`: Handle output events
- `grabDevices()`: Take exclusive control
- `releaseDevices()`: Release control
### Screen Driver
Implement these methods:
- `getCurrScreen()`: Get current screen content
- `getSessionInformation()`: Get session info
### Speech Driver
Implement these methods:
- `speak()`: Speak text
- `cancel()`: Stop speech
- `setCallback()`: Set callback functions
### Remote Driver
Implement these methods:
- `initialize()`: Setup socket/connection
- `watchDog()`: Listen for incoming commands
- `shutdown()`: Clean up connections
#### Remote Driver Example
```python
class driver(remoteDriver):
def initialize(self, environment):
self.env = environment
# Start watchdog thread
self.env['runtime']['processManager'].addCustomEventThread(
self.watchDog, multiprocess=True
)
def watchDog(self, active, eventQueue):
# Listen for connections and process commands
while active.value:
# Accept connections
# Parse incoming data
# Send to event queue
eventQueue.put({
"Type": fenrirEventType.RemoteIncomming,
"Data": command_text
})
```
## Configuration
### Settings System
Settings are hierarchical:
1. Command-line options (`-o`)
2. Configuration file
3. Hard-coded defaults
### Adding Settings
1. Add default value to `core/settingsData.py`
2. Access via `self.env['runtime']['settingsManager'].getSetting(section, key)`
## Debugging
### Debug Levels
- 0: DEACTIVE
- 1: ERROR
- 2: WARNING
- 3: INFO
### Debug Output
```python
self.env['runtime']['debug'].writeDebugOut(
'Debug message',
debug.debugLevel.INFO
)
```
### Testing Commands
```bash
# Test specific functionality
sudo fenrir -f -d -o "general#debugLevel=3"
# Test with custom config
sudo fenrir -f -s /path/to/test.conf
```
## Contributing
### Code Style
- Follow PEP 8
- Use descriptive variable names
- Add docstrings for complex functions
- Handle exceptions gracefully
### Testing
- Test with different drivers
- Test keyboard layouts
- Test on different terminals
- Verify accessibility features
### Submitting Changes
1. Fork the repository
2. Create feature branch
3. Make changes with clear commit messages
4. Test thoroughly
5. Submit pull request
## API Reference
### Environment Structure
The `environment` dict contains all runtime data:
```python
environment = {
'runtime': {
'settingsManager': settingsManager,
'commandManager': commandManager,
'screenManager': screenManager,
'inputManager': inputManager,
'outputManager': outputManager,
'debug': debugManager,
# ... other managers
},
'screen': {
'newContentText': '',
'oldContentText': '',
'newCursor': {'x': 0, 'y': 0},
'oldCursor': {'x': 0, 'y': 0},
# ... screen data
},
'general': {
'prevCommand': '',
'currCommand': '',
# ... general data
}
}
```
### Common Operations
#### Speaking Text
```python
self.env['runtime']['outputManager'].presentText('Hello')
```
#### Playing Sounds
```python
self.env['runtime']['outputManager'].playSoundIcon('Accept')
```
#### Getting Settings
```python
rate = self.env['runtime']['settingsManager'].getSetting('speech', 'rate')
```
#### Cursor Information
```python
x = self.env['screen']['newCursor']['x']
y = self.env['screen']['newCursor']['y']
```
#### Screen Content
```python
text = self.env['screen']['newContentText']
lines = text.split('\n')
current_line = lines[self.env['screen']['newCursor']['y']]
```
## Maintenance
### Release Process
1. Update version in `fenrirVersion.py`
2. Update changelog
3. Test on multiple systems
4. Tag release
5. Update documentation
### Compatibility
- Maintain Python 3.6+ compatibility
- Test on multiple Linux distributions
- Ensure driver compatibility
- Check dependencies
## Resources
- **Repository**: https://git.stormux.org/storm/fenrir
- **Wiki**: https://git.stormux.org/storm/fenrir/wiki
- **Issues**: Use repository issue tracker
- **Community**: IRC irc.stormux.org #stormux
- **Email**: stormux+subscribe@groups.io
+509 -2754
View File
File diff suppressed because it is too large Load Diff
+192 -6
View File
@@ -7,14 +7,14 @@ configurable and easy to customize and extend.
=== Credit and intended audience
This document is just a customization for Slint of the genuine
https://github.com/chrys87/fenrir/blob/master/docu/user.txt[Fenrir User
https://git.stormux.org/storm/fenrir/src/branch/master/docs/user.txt[Fenrir User
Manual] motly written by Chrys, main developer of Fenrir.
It has been adapted to its intended audience: end users of Fenrir on
Slint where it is already installed, thus concentrates on its setting
and usage. You will find more information about its features,
installation and how customize and troubleshoot it and contribute to its
development on https://github.com/chrys87/fenrir[the Fenrir Git
development on https://git.stormux.org/storm/fenrir[the Fenrir Git
repository].
=== Getting started with Fenrir
@@ -1202,6 +1202,47 @@ link:#Settings[Settings]
=== Commandline Arguments
Fenrir supports several command-line options:
....
fenrir [OPTIONS]
....
==== Available Options
`+-h, --help+`::
Show help message and exit.
`+-v, --version+`::
Show version information and exit.
`+-f, --foreground+`::
Run Fenrir in the foreground instead of as a daemon.
`+-s, --setting SETTING-FILE+`::
Path to a custom settings file.
`+-o, --options SECTION#SETTING=VALUE;..+`::
Override settings file options (see below for details).
`+-d, --debug+`::
Enable debug mode. Debug information will be logged.
`+-p, --print+`::
Print debug messages to screen in addition to logging them.
`+-e, --emulated-pty+`::
Use PTY emulation with escape sequences for input. This enables usage in desktop/X11/Wayland environments and terminal emulators.
`+-E, --emulated-evdev+`::
Use PTY emulation with evdev for input (single instance mode).
`+-F, --force-all-screens+`::
Force Fenrir to respond on all screens, ignoring the ignoreScreen setting. This temporarily overrides screen filtering for the current session.
`+-i, -I, --ignore-screen <SCREEN>+`::
Ignore specific screen(s). Can be used multiple times to ignore multiple screens. This is equivalent to setting ignoreScreen in the configuration file and will be combined with any existing ignore settings.
==== Set settings options
You can specify options that overwrite the setting.conf. This is done
@@ -1224,9 +1265,154 @@ or change the debug level to verbose
fenrir -o "general#debugLevel=3"
....
Example using force all screens option:
....
fenrir -F
....
You can find the available sections and variables here #Settings See
Syntax link:#settings.conf syntax[#settings.conf syntax]
=== Remote Control
Fenrir includes a powerful remote control system that allows external applications and scripts to control Fenrir through Unix sockets or TCP connections.
==== Configuration
Enable remote control in settings.conf:
....
[remote]
enable=True
driver=unixDriver
enableSettingsRemote=True
enableCommandRemote=True
....
==== Using socat with Unix Sockets
The `+socat+` command provides the easiest way to send commands to Fenrir:
===== Basic Speech Control
....
# Interrupt current speech
echo "command interrupt" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Speak custom text
echo "command say Hello, this is a test message" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Temporarily disable speech (until next keystroke)
echo "command tempdisablespeech" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
===== Settings Control
....
# Enable highlight tracking mode
echo "setting set focus#highlight=True" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change speech rate
echo "setting set speech#rate=0.8" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Change punctuation level (none/some/most/all)
echo "setting set general#punctuationLevel=all" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Voice and TTS control
echo "setting set speech#voice=en-us+f3" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Multiple settings at once
echo "setting set speech#rate=0.8;sound#volume=0.7;general#punctuationLevel=most" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Reset all settings to defaults
echo "setting reset" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
===== Clipboard Operations
....
# Place text into clipboard
echo "command clipboard This text will be copied to clipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
# Export clipboard to file
echo "command exportclipboard" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
===== Application Control
....
# Quit Fenrir
echo "command quitapplication" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
....
==== Command Reference
===== Available Commands
*Speech Commands:*
* `+command say <text>+` - Speak the specified text
* `+command interrupt+` - Stop current speech
* `+command tempdisablespeech+` - Disable speech until next key press
*Clipboard Commands:*
* `+command clipboard <text>+` - Add text to clipboard
* `+command exportclipboard+` - Export clipboard to file
*Window Commands:*
* `+command window <x1> <y1> <x2> <y2>+` - Define window area
* `+command resetwindow+` - Reset to full screen
*VMenu Commands:*
* `+command vmenu <menu_path>+` - Set virtual menu context
* `+command resetvmenu+` - Reset virtual menu
*Application Commands:*
* `+command quitapplication+` - Quit Fenrir
===== Available Settings
*Settings Commands:*
* `+setting set <section>#<key>=<value>+` - Set configuration value
* `+setting reset+` - Reset all settings to defaults
* `+setting save [path]+` - Save current settings
*Key Settings You Can Control:*
*Speech Settings:*
* `+speech#enabled=True/False+` - Enable/disable speech
* `+speech#rate=0.1-1.0+` - Speech rate (speed)
* `+speech#pitch=0.1-1.0+` - Speech pitch (tone)
* `+speech#volume=0.1-1.0+` - Speech volume
* `+speech#voice=voice_name+` - Voice selection (e.g., "en-us+f3")
* `+speech#module=module_name+` - TTS module (e.g., "espeak-ng")
*General Settings:*
* `+general#punctuationLevel=none/some/most/all+` - Punctuation verbosity
* `+general#autoSpellCheck=True/False+` - Automatic spell checking
* `+general#emoticons=True/False+` - Enable emoticon replacement
*Sound Settings:*
* `+sound#enabled=True/False+` - Enable/disable sound
* `+sound#volume=0.1-1.0+` - Sound volume
*Focus Settings:*
* `+focus#cursor=True/False+` - Follow text cursor
* `+focus#highlight=True/False+` - Follow text highlighting
*Keyboard Settings:*
* `+keyboard#charEchoMode=0-2+` - Character echo (0=none, 1=always, 2=capslock only)
* `+keyboard#wordEcho=True/False+` - Echo complete words
*Screen Settings:*
* `+screen#ignoreScreen=1,2,3+` - TTY screens to ignore
==== settings.conf syntax
the syntax of the link:#Settings[settings.conf] is quite simple and
@@ -2007,9 +2193,9 @@ settings.conf). Commands are python files with a special scheme. You can
assign them to a shortcut using the filename without an extension or
place them in a hook trigger like OnInput or OnScreenChange. For further
information see developer guide. Good Examples:
https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/date.py["date.py"]
https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/date.py["date.py"]
(announce the Date),
https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/shut_up.py["shut_up.py"]
https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/shut_up.py["shut_up.py"]
(interrupt output) the basic scheme for a command is as follows:
....
@@ -2032,7 +2218,7 @@ class command():
pass
....
* https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/command_template.py[Template
* https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/command_template.py[Template
lives here]
* The class needs to have the name "command".
* "initialize" is running once whilst loading the command.
@@ -2090,7 +2276,7 @@ root.
=== Bugreports and feature requests
Please report Bugs and feature requests to:
https://github.com/chrys87/fenrir/issues
https://git.stormux.org/storm/fenrir/issues
for bugs please provide a link:#Howto create a debug file[debug] file
that shows the issue.
+365 -1572
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -160,7 +160,7 @@ For Arch there are PKGBUILDs in the AUR:
- Download the latest stable version from the [[https://linux-a11y.org/index.php?page=fenrir-screenreader|Fenrir-Project]] site.
- Unpack the archive
- Check the needed Dependencys by running [[https://github.com/chrys87/fenrir/blob/master/check-dependencies.py|check-dependencys.py]] script
- Check the needed Dependencys by running [[https://git.stormux.org/storm/fenrir/src/branch/master/check-dependencies.py|check-dependencys.py]] script
- install the missing dependencies an standard installation requires the following:
* python3 >= 3.3 (and all the following is needed for python3 )
* python3-speechd (screen)
@@ -171,7 +171,7 @@ For Arch there are PKGBUILDs in the AUR:
* python3-pyenchant (spellchecker)
* your language for aspell (aspell-<lang>) (spellchecker)
* sox (sound)
* For an individual installation see [[#Support and Requirements|Support and Requirements]] or consult the [[https://github.com/chrys87/fenrir/blob/master/README.md|Readme]])
* For an individual installation see [[#Support and Requirements|Support and Requirements]] or consult the [[https://git.stormux.org/storm/fenrir/src/branch/master/README.md|Readme]])
- run "install.sh" as root
this installs Fenrir as the following
@@ -185,7 +185,7 @@ to remove Fenrir just run uninstall.sh as root
if you want to get the latest code you can use git to get a development snapshot:
git clone https://github.com/chrys87/fenrir.git
git clone https://git.stormux.org/storm/fenrir.git
===== Auto Start =====
@@ -1270,7 +1270,7 @@ File: ''/usr/share/fenrirscreenreader/scripts/helloWorld__-__key_h.sh'':
===== Commands =====
You can place your own commands in "/usr/share/fenrirscreenreader/commands" (path is configurable in settings.conf).
Commands are python files with a special scheme. You can assign them to a shortcut using the filename without an extension or place them in a hook trigger like OnInput or OnScreenChange. For further information see developer guide.
Good Examples: [[https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/date.py|"date.py"]] (announce the Date), [[https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/commands/shut_up.py|"shut_up.py"]] (interrupt output)
Good Examples: [[https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/date.py|"date.py"]] (announce the Date), [[https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/commands/shut_up.py|"shut_up.py"]] (interrupt output)
the basic scheme for a command is as follows:
from core import debug
@@ -1289,7 +1289,7 @@ the basic scheme for a command is as follows:
def setCallback(self, callback):
pass
* [[https://github.com/chrys87/fenrir/blob/master/src/fenrir/commands/command_template.py|Template lives here]]
* [[https://git.stormux.org/storm/fenrir/src/branch/master/src/fenrirscreenreader/commands/command_template.py|Template lives here]]
* The class needs to have the name "command".
* "initialize" is running once whilst loading the command.
* "shutdown" is running on unload like the command (quit fenrir)
@@ -1319,7 +1319,7 @@ the basic scheme for a command is as follows:
- You can test if speech-dispatcher works by invoking it as root\\ ''sudo spd-say "hello world"''
===== Bugreports and feature requests =====
Please report Bugs and feature requests to:
[[https://github.com/chrys87/fenrir/issues|https://github.com/chrys87/fenrir/issues]]
[[https://git.stormux.org/storm/fenrir/issues|https://git.stormux.org/storm/fenrir/issues]]
for bugs please provide a [[#Howto create a debug file|debug]] file that shows the issue.
==== How-to create a debug file ====
+1 -1
View File
@@ -56,7 +56,7 @@ To test Fenrir:
sudo fenrir
To have Fenrir start on system boot using systemd:
download service file: https://raw.githubusercontent.com/chrys87/fenrir/master/autostart/systemd/Arch/fenrir.service
download service file: https://git.stormux.org/storm/fenrir/raw/branch/master/autostart/systemd/Arch/fenrir.service
move the service file to: /etc/systemd/system/fenrir.service
sudo systemctl enable fenrir
+69
View File
@@ -0,0 +1,69 @@
[pytest]
# Pytest configuration for Fenrir screen reader
# Test discovery patterns
python_files = test_*.py *_test.py
python_classes = Test*
python_functions = test_*
# Test paths
testpaths = tests
# Minimum Python version
minversion = 3.7
# Output options
addopts =
# Verbose output with test names
-v
# Show extra test summary info
-ra
# Enable strict markers (only registered markers allowed)
--strict-markers
# Show local variables in tracebacks
--showlocals
# Warnings configuration
-W ignore::DeprecationWarning
# Optional plugins (uncomment if installed):
# --timeout=30 # Requires pytest-timeout
# --cov-report=term-missing # Requires pytest-cov
# -x # Stop on first failure
# Register custom markers
markers =
unit: Unit tests (fast, no mocking)
integration: Integration tests (require mocking)
driver: Driver tests (require root access)
slow: Tests that take more than 1 second
remote: Tests for remote control functionality
settings: Tests for settings and configuration
commands: Tests for command system
vmenu: Tests for VMenu system
# Coverage configuration
[coverage:run]
source = src/fenrirscreenreader
omit =
*/tests/*
*/vmenu-profiles/*
*/__pycache__/*
*/site-packages/*
[coverage:report]
# Fail if coverage falls below this percentage
# fail_under = 70
exclude_lines =
# Standard pragma
pragma: no cover
# Don't complain about missing debug code
def __repr__
# Don't complain if tests don't hit defensive assertion code
raise AssertionError
raise NotImplementedError
# Don't complain about abstract methods
@abstractmethod
# Don't complain about initialization
if __name__ == .__main__.:
[coverage:html]
directory = htmlcov
+1
View File
@@ -1,4 +1,5 @@
daemonize
dbus-python
evdev
pexpect
pyenchant
+1 -1
View File
@@ -37,7 +37,7 @@ for directory in directories:
if not forceSettingsFlag:
try:
files = [f for f in files if not f.endswith('settings.conf')]
except:
except Exception as e:
pass
elif 'config/scripts' in directory:
destDir = '/usr/share/fenrirscreenreader/scripts'
+51 -13
View File
@@ -4,19 +4,22 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import argparse
import inspect
import os
import sys
import inspect
import argparse
from daemonize import Daemonize
from fenrirscreenreader import fenrirVersion
from fenrirscreenreader.core import fenrirManager
# Get the fenrir installation path
fenrirPath = os.path.dirname(os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe()))))
if not fenrirPath in sys.path:
fenrirPath = os.path.dirname(os.path.realpath(
os.path.abspath(inspect.getfile(inspect.currentframe()))))
if fenrirPath not in sys.path:
sys.path.append(fenrirPath)
from fenrirscreenreader.core import fenrirManager
from fenrirscreenreader import fenrirVersion
def create_argument_parser():
"""Create and return the argument parser for Fenrir"""
@@ -27,7 +30,7 @@ def create_argument_parser():
argumentParser.add_argument(
'-v', '--version',
action='version',
version=f'Fenrir screen reader version {fenrirVersion.version}-{fenrirVersion.codeName}',
version=f'Fenrir screen reader version {fenrirVersion.version}-{fenrirVersion.code_name}',
help='Show version information and exit'
)
argumentParser.add_argument(
@@ -67,31 +70,65 @@ def create_argument_parser():
action='store_true',
help='Use PTY emulation with evdev for input (single instance)'
)
argumentParser.add_argument(
'-F',
'--force-all-screens',
action='store_true',
help='Force Fenrir to respond on all screens, ignoring ignoreScreen setting')
argumentParser.add_argument(
'-i', '-I', '--ignore-screen',
metavar='SCREEN',
action='append',
help='Ignore specific screen(s). Can be used multiple times. Same as ignoreScreen setting.'
)
return argumentParser
def validate_arguments(cliArgs):
"""Validate command line arguments"""
if cliArgs.options:
for option in cliArgs.options.split(';'):
if option and ('#' not in option or '=' not in option):
return False, f"Invalid option format: {option}\nExpected format: SECTION#SETTING=VALUE"
if cliArgs.emulated_pty and cliArgs.emulated_evdev:
return False, "Cannot use both --emulated-pty and --emulated-evdev simultaneously"
return True, None
def run_fenrir():
"""Main function that runs Fenrir"""
fenrirApp = fenrirManager.fenrirManager(cliArgs)
fenrirApp.proceed()
del fenrirApp
fenrirApp = None
try:
fenrirApp = fenrirManager.FenrirManager(cliArgs)
fenrirApp.proceed()
except Exception as e:
print(f"Error starting Fenrir: {e}", file=sys.stderr)
if fenrirApp and hasattr(fenrirApp, 'cleanup_on_error'):
try:
fenrirApp.cleanup_on_error()
except Exception as cleanup_error:
print(
f"Error during cleanup: {cleanup_error}", file=sys.stderr)
sys.exit(1)
finally:
if fenrirApp:
del fenrirApp
# Clean up PID file if it exists
pidFile = "/run/fenrir.pid"
if os.path.exists(pidFile):
try:
os.remove(pidFile)
except Exception:
pass
def main():
global cliArgs
argumentParser = create_argument_parser()
cliArgs = argumentParser.parse_args()
# Validate arguments
isValid, errorMsg = validate_arguments(cliArgs)
if not isValid:
@@ -112,5 +149,6 @@ def main():
)
daemonProcess.start()
if __name__ == "__main__":
main()
@@ -2,20 +2,27 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return 'No description found'
def get_description(self):
return "No description found"
def run(self):
pass
def setCallback(self, callback):
def set_callback(self, callback):
pass
@@ -2,25 +2,38 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
# this command is just to initialize stuff.
# like init index lists in memoryManager
# like init index lists in MemoryManager
# it is not useful to execute it
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
# clipboard
self.env['runtime']['memoryManager'].addIndexList('clipboardHistory', self.env['runtime']['settingsManager'].getSettingAsInt('general', 'numberOfClipboards'))
self.env["runtime"]["MemoryManager"].add_index_list(
"clipboardHistory",
self.env["runtime"]["SettingsManager"].get_setting_as_int(
"general", "numberOfClipboards"
),
)
def shutdown(self):
pass
def getDescription(self):
return 'No description found'
def get_description(self):
return "No description found"
def run(self):
pass
def setCallback(self, callback):
def set_callback(self, callback):
pass
@@ -2,53 +2,92 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import word_utils
import string
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import word_utils
initialized = False
try:
import enchant
initialized = True
except:
except Exception as e:
pass
class command():
class command:
def __init__(self):
self.language = ''
self.language = ""
self.spellChecker = None
def initialize(self, environment):
self.env = environment
self.updateSpellLanguage()
self.update_spell_language()
def shutdown(self):
pass
def getDescription(self):
return _('adds the current word to the exceptions dictionary')
def updateSpellLanguage(self):
self.spellChecker = enchant.Dict(self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage'))
self.language = self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage')
pass
def get_description(self):
return _("adds the current word to the exceptions dictionary")
def update_spell_language(self):
self.spellChecker = enchant.Dict(
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
)
)
self.language = self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
)
def run(self):
if not initialized:
return
if self.env['runtime']['settingsManager'].getSetting('general', 'spellCheckLanguage') != self.language:
return
if (
self.env["runtime"]["SettingsManager"].get_setting(
"general", "spellCheckLanguage"
)
!= self.language
):
try:
self.updateSpellLanguage()
self.update_spell_language()
except Exception as e:
return
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
return
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
# get the word
newContent = self.env['screen']['newContentText'].split('\n')[cursorPos['y']]
x, y, currWord, endOfScreen, lineBreak = word_utils.getCurrentWord(cursorPos['x'], 0, newContent)
currWord = currWord.strip(string.whitespace + r'!"#$%&\()*+,-./:;<=Â?@[\\]^_{|}~')
new_content = self.env["screen"]["new_content_text"].split("\n")[
cursor_pos["y"]
]
x, y, curr_word, end_of_screen, line_break = (
word_utils.get_current_word(cursor_pos["x"], 0, new_content)
)
curr_word = curr_word.strip(
string.whitespace + r'!"#$%&\()*+,-./:;<=Â?@[\\]^_{|}~'
)
if currWord != '':
if self.spellChecker.is_added(currWord):
self.env['runtime']['outputManager'].presentText(_('{0} is already in dictionary').format(currWord,), soundIcon='Cancel', interrupt=True)
if curr_word != "":
if self.spellChecker.is_added(curr_word):
self.env["runtime"]["OutputManager"].present_text(
_("{0} is already in dictionary").format(
curr_word,
),
sound_icon="Cancel",
interrupt=True,
)
else:
self.spellChecker.add(currWord)
self.env['runtime']['outputManager'].presentText(_('{0} added to dictionary').format(currWord,), soundIcon='Accept', interrupt=True)
self.spellChecker.add(curr_word)
self.env["runtime"]["OutputManager"].present_text(
_("{0} added to dictionary").format(
curr_word,
),
sound_icon="Accept",
interrupt=True,
)
def setCallback(self, callback):
def set_callback(self, callback):
pass
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import math
from fenrirscreenreader.core.i18n import _
class adjustment_command:
"""Base class for speech and sound adjustment commands"""
def __init__(self, section, setting, direction, step=0.1):
self.section = section # 'speech' or 'sound'
self.setting = setting # 'rate', 'pitch', 'volume'
self.direction = direction # 'inc' or 'dec'
self.step = step
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
action = "Increase" if self.direction == "inc" else "Decrease"
if self.section == "speech":
return _(f"{action} the speech {self.setting}")
else:
return _(f"{action} the {self.section} {self.setting}")
def run(self):
if self.section == "sound" and self.setting == "volume":
# Sound volume uses different method
self._adjust_sound_volume()
else:
# Speech rate, pitch, volume use standard method
self._adjust_speech_setting()
def _adjust_speech_setting(self):
"""Adjust speech settings (rate, pitch, volume)"""
value = self.env["runtime"]["SettingsManager"].get_setting_as_float(
self.section, self.setting
)
# Apply adjustment with rounding
if self.direction == "inc":
value = round((math.ceil(10 * value) / 10) + self.step, 2)
if value > 1.0:
value = 1.0
else: # dec
value = round((math.ceil(10 * value) / 10) - self.step, 2)
if value < 0.0:
value = 0.0
# Set the new value
self.env["runtime"]["SettingsManager"].set_setting(
self.section, self.setting, str(value)
)
# Present feedback
percentage = int(value * 100)
if self.section == "speech":
feedback = _("{0} percent speech {1}").format(
percentage, self.setting
)
else:
feedback = _("{0} percent {1} {2}").format(
percentage, self.section, self.setting
)
self.env["runtime"]["OutputManager"].present_text(
feedback, sound_icon="", interrupt=True
)
def _adjust_sound_volume(self):
"""Adjust sound volume using same logic as speech"""
value = self.env["runtime"]["SettingsManager"].get_setting_as_float(
self.section, self.setting
)
# Sound volume uses same math as speech settings
if self.direction == "inc":
value = round((math.ceil(10 * value) / 10) + self.step, 2)
if value > 1.0:
value = 1.0
else: # dec
value = round((math.ceil(10 * value) / 10) - self.step, 2)
if value < 0.0:
value = 0.0
# Set the new value
self.env["runtime"]["SettingsManager"].set_setting(
self.section, self.setting, str(value)
)
# Present feedback with appropriate sound icon
percentage = int(value * 100)
sound_icon = "SoundOn" if self.direction == "inc" else "SoundOff"
feedback = _("{0} percent sound volume").format(percentage)
self.env["runtime"]["OutputManager"].present_text(
feedback, sound_icon=sound_icon, interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,26 +2,36 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader import fenrirVersion
from fenrirscreenreader.core.i18n import _
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Present the version of Fenrir currrrently in use.')
def get_description(self):
return _("Present the version of Fenrir currently in use.")
def run(self):
try:
self.env['runtime']['outputManager'].presentText(f'Fenrir screen reader version {fenrirVersion.version}-{fenrirVersion.codeName}', interrupt=True)
except exception as e:
self.env['runtime']['outputManager'].presentText(_('Version information is unavailable.'), interrupt=True)
def setCallback(self, callback):
self.env["runtime"]["OutputManager"].present_text(
f"Fenrir screen reader version "
f"{fenrirVersion.version}-{fenrirVersion.code_name}",
interrupt=True,
)
except Exception as e:
self.env["runtime"]["OutputManager"].present_text(
_("Version information is unavailable."), interrupt=True
)
def set_callback(self, callback):
pass
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
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 "Apply the last tested voice from safe voice browser"
def run(self):
try:
# Check if we have a tested voice
if (
"commandBuffer" not in self.env
or "lastTestedModule" not in self.env["commandBuffer"]
or "lastTestedVoice" not in self.env["commandBuffer"]
):
self.env["runtime"]["OutputManager"].present_text(
"No voice has been tested yet", interrupt=True
)
self.env["runtime"]["OutputManager"].present_text(
"Use voice browser first", interrupt=True
)
return
module = self.env["commandBuffer"]["lastTestedModule"]
voice = self.env["commandBuffer"]["lastTestedVoice"]
self.env["runtime"]["OutputManager"].present_text(
f"Applying {voice} from {module}", interrupt=True
)
# Apply to runtime settings only (temporary until saved)
SettingsManager = self.env["runtime"]["SettingsManager"]
# Store old values for safety
old_driver = SettingsManager.get_setting("speech", "driver")
old_module = SettingsManager.get_setting("speech", "module")
old_voice = SettingsManager.get_setting("speech", "voice")
try:
# Apply new settings to runtime only (use set_setting to update
# settingArgDict)
SettingsManager.set_setting(
"speech", "driver", "speechdDriver"
)
SettingsManager.set_setting("speech", "module", module)
SettingsManager.set_setting("speech", "voice", voice)
# Apply to speech driver instance directly
if "SpeechDriver" in self.env["runtime"]:
SpeechDriver = self.env["runtime"]["SpeechDriver"]
# Set the module and voice on the driver instance
SpeechDriver.set_module(module)
SpeechDriver.set_voice(voice)
self.env["runtime"]["OutputManager"].present_text(
"Voice applied successfully!", interrupt=True
)
self.env["runtime"]["OutputManager"].present_text(
"Use save settings to make permanent", interrupt=True
)
self.env["runtime"]["OutputManager"].play_sound("Accept")
except Exception as e:
# Revert on failure
SettingsManager.set_setting("speech", "driver", old_driver)
SettingsManager.set_setting("speech", "module", old_module)
SettingsManager.set_setting("speech", "voice", old_voice)
self.env["runtime"]["OutputManager"].present_text(
f"Failed to apply voice, reverted: {str(e)}",
interrupt=True,
)
self.env["runtime"]["OutputManager"].play_sound("Error")
except Exception as e:
self.env["runtime"]["OutputManager"].present_text(
f"Apply voice error: {str(e)}", interrupt=True
)
self.env["runtime"]["OutputManager"].play_sound("Error")
def set_callback(self, callback):
pass
@@ -2,29 +2,45 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import screen_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Reads attributes of current cursor position')
def get_description(self):
return _("Reads attributes of current cursor position")
def run(self):
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
try:
attributes = self.env['runtime']['attributeManager'].getAttributeByXY( cursorPos['x'], cursorPos['y'])
attributes = self.env["runtime"][
"AttributeManager"
].get_attribute_by_xy(cursor_pos["x"], cursor_pos["y"])
except Exception as e:
print(e)
attributeFormatString = self.env['runtime']['settingsManager'].getSetting('general', 'attributeFormatString')
attributeFormatString = self.env['runtime']['attributeManager'].formatAttributes(attributes, attributeFormatString)
self.env['runtime']['outputManager'].presentText(attributeFormatString, soundIcon='', interrupt=True)
def setCallback(self, callback):
print(e)
attribute_format_string = self.env["runtime"][
"SettingsManager"
].get_setting("general", "attribute_format_string")
attribute_format_string = self.env["runtime"][
"AttributeManager"
].format_attributes(attributes, attribute_format_string)
self.env["runtime"]["OutputManager"].present_text(
attribute_format_string, sound_icon="", interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '1'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(1, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '10'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(10, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '2'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(2, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '3'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(3, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '4'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(4, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '5'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(5, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '6'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(6, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '7'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(7, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '8'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(8, "read")
@@ -2,47 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.utils import mark_utils
from fenrirscreenreader.utils import line_utils
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '9'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
if not self.env['commandBuffer']['bookMarks'][self.ID]:
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} not set').format(self.ID,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
if not self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1']:
self.env['runtime']['outputManager'].presentText(_('Bookmark for application {0} not set').format(currApp,), interrupt=True)
return
# set marks
marked = ''
startMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['1'].copy()
if self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2']:
endMark = self.env['commandBuffer']['bookMarks'][self.ID][currApp]['2'].copy()
marked = mark_utils.getTextBetweenMarks(startMark, endMark, self.env['screen']['newContentText'])
else:
x, y, marked = \
line_utils.getCurrentLine(startMark['x'], startMark['y'], self.env['screen']['newContentText'])
if marked.isspace():
self.env['runtime']['outputManager'].presentText(_('blank'), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(marked, interrupt=True)
def setCallback(self, callback):
pass
super().__init__(9, "read")
@@ -0,0 +1,211 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import line_utils
from fenrirscreenreader.utils import mark_utils
class bookmark_command:
"""Base class for bookmark operations - read, set, clear"""
def __init__(self, bookmark_id, action="read"):
self.ID = str(bookmark_id)
self.action = action
def initialize(self, environment):
self.env = environment
# Always initialize bookmark structure - all commands need this
if self.ID not in self.env["commandBuffer"]["bookMarks"]:
self.env["commandBuffer"]["bookMarks"][self.ID] = {}
def shutdown(self):
pass
def get_description(self):
if self.action == "read":
return _("read Bookmark {0}").format(self.ID)
elif self.action == "set":
return _("set Bookmark {0}").format(self.ID)
elif self.action == "clear":
return _("remove Bookmark {0}").format(self.ID)
return f"{self.action} Bookmark {self.ID}"
def run(self):
if self.action == "read":
self._read_bookmark()
elif self.action == "set":
self._set_bookmark()
elif self.action == "clear":
self._clear_bookmark()
def _read_bookmark(self):
curr_app = self.env["runtime"][
"ApplicationManager"
].get_current_application()
if not self.env["commandBuffer"]["bookMarks"][self.ID]:
self.env["runtime"]["OutputManager"].present_text(
"Bookmark {0} not set".format(self.ID), interrupt=True
)
return
if curr_app not in self.env["commandBuffer"]["bookMarks"][self.ID]:
self.env["runtime"]["OutputManager"].present_text(
"Bookmark for application {0} not set".format(curr_app),
interrupt=True,
)
return
if not self.env["commandBuffer"]["bookMarks"][self.ID][curr_app].get(
"1"
):
self.env["runtime"]["OutputManager"].present_text(
"Bookmark for application {0} not set".format(curr_app),
interrupt=True,
)
return
# Get bookmarked text
marked = ""
start_mark = self.env["commandBuffer"]["bookMarks"][self.ID][curr_app][
"1"
].copy()
if self.env["commandBuffer"]["bookMarks"][self.ID][curr_app]["2"]:
end_mark = self.env["commandBuffer"]["bookMarks"][self.ID][
curr_app
]["2"].copy()
marked = mark_utils.get_text_between_marks(
start_mark, end_mark, self.env["screen"]["new_content_text"]
)
else:
x, y, marked = line_utils.get_current_line(
start_mark["x"],
start_mark["y"],
self.env["screen"]["new_content_text"],
)
if marked.isspace():
self.env["runtime"]["OutputManager"].present_text(
_("blank"), sound_icon="EmptyLine", interrupt=True
)
else:
self.env["runtime"]["OutputManager"].present_text(
marked, interrupt=True
)
def _set_bookmark(self):
if not self.env["commandBuffer"]["Marks"]["1"]:
self.env["runtime"]["OutputManager"].present_text(
_("No mark found"), interrupt=True
)
return
curr_app = self.env["runtime"][
"ApplicationManager"
].get_current_application()
self.env["commandBuffer"]["bookMarks"][self.ID][curr_app] = {}
bookmarks = self.env["commandBuffer"]["bookMarks"][self.ID][curr_app]
bookmarks["1"] = self.env["commandBuffer"]["Marks"]["1"].copy()
if self.env["commandBuffer"]["Marks"]["2"]:
bookmarks["2"] = self.env["commandBuffer"]["Marks"]["2"].copy()
else:
bookmarks["2"] = None
self.env["runtime"]["OutputManager"].present_text(
_("Bookmark {0} set for application {1}").format(
self.ID, curr_app
),
interrupt=True,
)
# Clear marks after setting bookmark
self.env["commandBuffer"]["Marks"]["1"] = None
self.env["commandBuffer"]["Marks"]["2"] = None
def _clear_bookmark(self):
curr_app = self.env["runtime"][
"ApplicationManager"
].get_current_application()
bookmarks = self.env["commandBuffer"]["bookMarks"]
if self.ID in bookmarks and curr_app in bookmarks[self.ID]:
del self.env["commandBuffer"]["bookMarks"][self.ID][curr_app]
self.env["runtime"]["OutputManager"].present_text(
_("Bookmark {0} removed for application {1}").format(
self.ID, curr_app
),
interrupt=True,
)
else:
self.env["runtime"]["OutputManager"].present_text(
_("Bookmark {0} not set for application {1}").format(
self.ID, curr_app
),
interrupt=True,
)
def set_callback(self, callback):
pass
# Factory function to create bookmark command instances
def create_bookmark_commands():
"""Create all bookmark command instances"""
commands = {}
# Create read bookmark commands (bookmark_1 through bookmark_10)
for i in range(1, 11):
commands[f"bookmark_{i}"] = lambda i=i: bookmark_command(i, "read")
# Create set bookmark commands (set_bookmark_1 through set_bookmark_10)
for i in range(1, 11):
commands[f"set_bookmark_{i}"] = lambda i=i: bookmark_command(i, "set")
# Create clear bookmark commands (clear_bookmark_1 through
# clear_bookmark_10)
for i in range(1, 11):
commands[f"clear_bookmark_{i}"] = lambda i=i: bookmark_command(
i, "clear"
)
return commands
# For backwards compatibility, provide individual command classes
# This allows the existing command loading system to work unchanged
def _make_command_class(bookmark_id, action):
"""Create a command class for a specific bookmark and action"""
class command(bookmark_command):
def __init__(self):
super().__init__(bookmark_id, action)
return command
# Generate individual command classes for each bookmark operation
# These will be used by the existing command loading system
# Read bookmarks (bookmark_1.py style)
for i in range(1, 11):
globals()[f"bookmark_{i}_command"] = _make_command_class(i, "read")
# Set bookmarks (set_bookmark_1.py style)
for i in range(1, 11):
globals()[f"set_bookmark_{i}_command"] = _make_command_class(i, "set")
# Clear bookmarks (clear_bookmark_1.py style)
for i in range(1, 11):
globals()[f"clear_bookmark_{i}_command"] = _make_command_class(i, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '1'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(1, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '10'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(10, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '2'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(2, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '3'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(3, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '4'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(4, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '5'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(5, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '6'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(6, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '7'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(7, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '8'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(8, "clear")
@@ -2,26 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import importlib.util
import os
class command():
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "bookmark_base.py")
_spec = importlib.util.spec_from_file_location("bookmark_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
bookmark_command = _module.bookmark_command
class command(bookmark_command):
def __init__(self):
self.ID = '9'
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('remove Bookmark {0}').format(self.ID,)
def run(self):
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
del self.env['commandBuffer']['bookMarks'][self.ID][currApp]
self.env['runtime']['outputManager'].presentText(_('Bookmark {0} removed for application {1}').format(self.ID, currApp), interrupt=True)
def setCallback(self, callback):
pass
super().__init__(9, "clear")
@@ -2,23 +2,33 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('clears the currently selected clipboard')
pass
def get_description(self):
return _("clears the currently selected clipboard")
def run(self):
self.env['runtime']['memoryManager'].clearCurrentIndexList('clipboardHistory')
self.env['runtime']['outputManager'].presentText(_('clipboard cleared'), interrupt=True)
return
def setCallback(self, callback):
self.env["runtime"]["MemoryManager"].clear_current_index_list(
"clipboardHistory"
)
self.env["runtime"]["OutputManager"].present_text(
_("clipboard cleared"), interrupt=True
)
return
def set_callback(self, callback):
pass
@@ -2,26 +2,40 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Turn off window mode for application')
pass
def get_description(self):
return _("Turn off window mode for application")
def run(self):
if self.env['runtime']['cursorManager'].clearWindowForApplication():
currApp = self.env['runtime']['applicationManager'].getCurrentApplication()
self.env['runtime']['outputManager'].presentText(_('Window Mode off for application {0}').format(currApp,), interrupt=True)
if self.env["runtime"]["CursorManager"].clear_window_for_application():
curr_app = self.env["runtime"][
"ApplicationManager"
].get_current_application()
self.env["runtime"]["OutputManager"].present_text(
_("Window Mode off for application {0}").format(
curr_app,
),
interrupt=True,
)
else:
self.env['runtime']['outputManager'].presentText(_("Not in window Mode"), interrupt=True)
def setCallback(self, callback):
self.env["runtime"]["OutputManager"].present_text(
_("Not in window Mode"), interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,27 +2,35 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import mark_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('copies last presented text to the clipboard')
def run(self):
lastEcho = self.env['runtime']['outputManager'].getLastEcho()
if lastEcho.rstrip() != "":
lastEcho = lastEcho.rstrip()
self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', lastEcho)
self.env['runtime']['outputManager'].presentText(lastEcho, soundIcon='CopyToClipboard', interrupt=True)
def setCallback(self, callback):
def shutdown(self):
pass
def get_description(self):
return _("copies last presented text to the clipboard")
def run(self):
last_echo = self.env["runtime"]["OutputManager"].get_last_echo()
if last_echo.rstrip() != "":
last_echo = last_echo.rstrip()
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", last_echo
)
self.env["runtime"]["OutputManager"].present_text(
last_echo, sound_icon="CopyToClipboard", interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,73 +2,81 @@
# -*- coding: utf-8 -*-
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import mark_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('copies marked text to the currently selected clipboard')
def getTextFromScreen(self, startMark, endMark):
screenContent = self.env['screen']['newContentText']
screenLines = screenContent.split('\n')
startY = min(startMark['y'], len(screenLines) - 1)
endY = min(endMark['y'], len(screenLines) - 1)
pass
def get_description(self):
return _("copies marked text to the currently selected clipboard")
def get_text_from_screen(self, start_mark, end_mark):
screen_content = self.env["screen"]["new_content_text"]
screen_lines = screen_content.split("\n")
start_y = min(start_mark["y"], len(screen_lines) - 1)
end_y = min(end_mark["y"], len(screen_lines) - 1)
# If marks are on the same line
if startY == endY:
line = screenLines[startY]
startX = min(startMark['x'], len(line))
endX = min(endMark['x'], len(line)) + 1
return line[startX:endX]
if start_y == end_y:
line = screen_lines[start_y]
start_x = min(start_mark["x"], len(line))
end_x = min(end_mark["x"], len(line)) + 1
return line[start_x:end_x]
# Handle multi-line selection
result = []
# First line (from start mark to end of line)
firstLine = screenLines[startY]
startX = min(startMark['x'], len(firstLine))
result.append(firstLine[startX:].rstrip())
first_line = screen_lines[start_y]
start_x = min(start_mark["x"], len(first_line))
result.append(first_line[start_x:].rstrip())
# Middle lines (complete lines)
for lineNum in range(startY + 1, endY):
result.append(screenLines[lineNum].rstrip())
for lineNum in range(start_y + 1, end_y):
result.append(screen_lines[lineNum].rstrip())
# Last line (from start to end mark)
if endY > startY:
lastLine = screenLines[endY]
endX = min(endMark['x'], len(lastLine)) + 1
result.append(lastLine[:endX].rstrip())
return '\n'.join(result)
if end_y > start_y:
last_line = screen_lines[end_y]
end_x = min(end_mark["x"], len(last_line)) + 1
result.append(last_line[:end_x].rstrip())
return "\n".join(result)
def run(self):
if not self.env['commandBuffer']['Marks']['1']:
self.env['runtime']['outputManager'].presentText(_("One or two marks are needed"), interrupt=True)
if not self.env["commandBuffer"]["Marks"]["1"]:
self.env["runtime"]["OutputManager"].present_text(
_("One or two marks are needed"), interrupt=True
)
return
if not self.env['commandBuffer']['Marks']['2']:
self.env['runtime']['cursorManager'].setMark()
if not self.env["commandBuffer"]["Marks"]["2"]:
self.env["runtime"]["CursorManager"].set_mark()
# use the last first and the last setted mark as range
startMark = self.env['commandBuffer']['Marks']['1'].copy()
endMark = self.env['commandBuffer']['Marks']['2'].copy()
# Replace mark_utils.getTextBetweenMarks with our new method
marked = self.getTextFromScreen(startMark, endMark)
self.env['runtime']['memoryManager'].addValueToFirstIndex('clipboardHistory', marked)
start_mark = self.env["commandBuffer"]["Marks"]["1"].copy()
end_mark = self.env["commandBuffer"]["Marks"]["2"].copy()
# Replace mark_utils.get_text_between_marks with our new method
marked = self.get_text_from_screen(start_mark, end_mark)
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
"clipboardHistory", marked
)
# reset marks
self.env['runtime']['cursorManager'].clearMarks()
self.env['runtime']['outputManager'].presentText(marked, soundIcon='CopyToClipboard', interrupt=True)
def setCallback(self, callback):
self.env["runtime"]["CursorManager"].clear_marks()
self.env["runtime"]["OutputManager"].present_text(
marked, sound_icon="CopyToClipboard", interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,26 +2,39 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('speaks the contents of the currently selected clipboard')
pass
def get_description(self):
return _("speaks the contents of the currently selected clipboard")
def run(self):
if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'):
self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True)
return
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
self.env['runtime']['outputManager'].presentText(clipboard , interrupt=True)
def setCallback(self, callback):
if self.env["runtime"]["MemoryManager"].is_index_list_empty(
"clipboardHistory"
):
self.env["runtime"]["OutputManager"].present_text(
_("clipboard empty"), interrupt=True
)
return
clipboard = self.env["runtime"][
"MemoryManager"
].get_index_list_element("clipboardHistory")
self.env["runtime"]["OutputManager"].present_text(
clipboard, interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,25 +2,34 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('reads the contents of the current screen')
pass
def get_description(self):
return _("reads the contents of the current screen")
def run(self):
if self.env['screen']['newContentText'].isspace():
self.env['runtime']['outputManager'].presentText(_("screen is empty"), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(self.env['screen']['newContentText'],interrupt=True)
def setCallback(self, callback):
if self.env["screen"]["new_content_text"].isspace():
self.env["runtime"]["OutputManager"].present_text(
_("screen is empty"), sound_icon="EmptyLine", interrupt=True
)
else:
self.env["runtime"]["OutputManager"].present_text(
self.env["screen"]["new_content_text"], interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,31 +2,43 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import mark_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('reads from the cursor to the bottom of the screen')
pass
def get_description(self):
return _("reads from the cursor to the bottom of the screen")
def run(self):
# Prefer review cursor over text cursor
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
textAfterCursor = mark_utils.getTextAfterMark(cursorPos, self.env['screen']['newContentText'])
text_after_cursor = mark_utils.get_text_after_mark(
cursor_pos, self.env["screen"]["new_content_text"]
)
if textAfterCursor.isspace():
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True)
if text_after_cursor.isspace():
self.env["runtime"]["OutputManager"].present_text(
_("blank"), sound_icon="EmptyLine", interrupt=True
)
else:
self.env['runtime']['outputManager'].presentText(textAfterCursor, interrupt=True)
self.env["runtime"]["OutputManager"].present_text(
text_after_cursor, interrupt=True
)
def setCallback(self, callback):
def set_callback(self, callback):
pass
@@ -2,35 +2,44 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import mark_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Reads from the top of the screen to the cursor position')
pass
def get_description(self):
return _("Reads from the top of the screen to the cursor position")
def run(self):
# Prefer review cursor over text cursor
if self.env['screen']['newCursorReview']:
cursorPos = self.env['screen']['newCursorReview'].copy()
if self.env["screen"]["newCursorReview"]:
cursor_pos = self.env["screen"]["newCursorReview"].copy()
else:
cursorPos = self.env['screen']['newCursor'].copy()
cursor_pos = self.env["screen"]["new_cursor"].copy()
textBeforeCursor = mark_utils.getTextBeforeMark(cursorPos, self.env['screen']['newContentText'])
text_before_cursor = mark_utils.get_text_before_mark(
cursor_pos, self.env["screen"]["new_content_text"]
)
if textBeforeCursor.isspace():
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True)
if text_before_cursor.isspace():
self.env["runtime"]["OutputManager"].present_text(
_("blank"), sound_icon="EmptyLine", interrupt=True
)
else:
self.env['runtime']['outputManager'].presentText(textBeforeCursor, interrupt=True)
def setCallback(self, callback):
self.env["runtime"]["OutputManager"].present_text(
text_before_cursor, interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,25 +2,34 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('get current quick menu entry')
pass
def get_description(self):
return _("get current quick menu entry")
def run(self):
menu = ''
value = ''
menu = self.env['runtime']['quickMenuManager'].getCurrentEntry()
if menu != '':
value = self.env['runtime']['quickMenuManager'].getCurrentValue()
self.env['runtime']['outputManager'].presentText(menu + ' ' + value, interrupt=True)
def setCallback(self, callback):
menu = ""
value = ""
menu = self.env["runtime"]["QuickMenuManager"].get_current_entry()
if menu != "":
value = self.env["runtime"]["QuickMenuManager"].get_current_value()
self.env["runtime"]["OutputManager"].present_text(
menu + " " + value, interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,21 +2,30 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('get current quick menu value')
pass
def get_description(self):
return _("get current quick menu value")
def run(self):
value = self.env['runtime']['quickMenuManager'].getCurrentValue()
self.env['runtime']['outputManager'].presentText(value, interrupt=True)
def setCallback(self, callback):
value = self.env["runtime"]["QuickMenuManager"].get_current_value()
self.env["runtime"]["OutputManager"].present_text(
value, interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,24 +2,36 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Column number for cursor')
def get_description(self):
return _("Column number for cursor")
def run(self):
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
self.env['runtime']['outputManager'].presentText(str(cursorPos['x'] + 1) , interrupt=True)
self.env['runtime']['outputManager'].announceActiveCursor()
self.env['runtime']['outputManager'].presentText(' column number' , interrupt=False)
def setCallback(self, callback):
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
self.env["runtime"]["OutputManager"].present_text(
str(cursor_pos["x"] + 1), interrupt=True
)
self.env["runtime"]["OutputManager"].announce_active_cursor()
self.env["runtime"]["OutputManager"].present_text(
" column number", interrupt=False
)
def set_callback(self, callback):
pass
@@ -2,24 +2,36 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Line number for cursor')
def get_description(self):
return _("Line number for cursor")
def run(self):
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
self.env['runtime']['outputManager'].presentText(str(cursorPos['y'] + 1), interrupt=True)
self.env['runtime']['outputManager'].announceActiveCursor()
self.env['runtime']['outputManager'].presentText(' line number' , interrupt=False)
def setCallback(self, callback):
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
self.env["runtime"]["OutputManager"].present_text(
str(cursor_pos["y"] + 1), interrupt=True
)
self.env["runtime"]["OutputManager"].announce_active_cursor()
self.env["runtime"]["OutputManager"].present_text(
" line number", interrupt=False
)
def set_callback(self, callback):
pass
@@ -2,25 +2,39 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('displays the position of the review cursor')
pass
def get_description(self):
return _("displays the position of the review cursor")
def run(self):
# Prefer review cursor over text cursor
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
self.env['runtime']['outputManager'].presentText(_("line {0}, column {1}, Terminal {2}").format(cursorPos['y']+1, cursorPos['x']+1, self.env['screen']['newTTY']), interrupt=True)
def setCallback(self, callback):
self.env["runtime"]["OutputManager"].present_text(
_("line {0}, column {1}, Terminal {2}").format(
cursor_pos["y"] + 1,
cursor_pos["x"] + 1,
self.env["screen"]["newTTY"],
),
interrupt=True,
)
def set_callback(self, callback):
pass
@@ -2,32 +2,48 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import line_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read line to cursor pos, use review cursor if you are in review mode, otherwhise use text cursor')
def run(self):
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
x, y, currLine = \
line_utils.getCurrentLine(cursorPos['x'], cursorPos['y'], self.env['screen']['newContentText'])
if currLine.isspace():
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(currLine[:cursorPos['x']], interrupt=True)
self.env['runtime']['outputManager'].announceActiveCursor()
def setCallback(self, callback):
def shutdown(self):
pass
def get_description(self):
return _(
"read line to cursor pos, use review cursor if you are in review mode, "
"otherwhise use text cursor"
)
def run(self):
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
x, y, curr_line = line_utils.get_current_line(
cursor_pos["x"],
cursor_pos["y"],
self.env["screen"]["new_content_text"],
)
if curr_line.isspace():
self.env["runtime"]["OutputManager"].present_text(
_("blank"), sound_icon="EmptyLine", interrupt=True
)
else:
self.env["runtime"]["OutputManager"].present_text(
curr_line[: cursor_pos["x"]], interrupt=True
)
self.env["runtime"]["OutputManager"].announce_active_cursor()
def set_callback(self, callback):
pass
@@ -2,32 +2,48 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.utils import line_utils
class command():
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('read to end of line, use review cursor if you are in review mode, otherwhise use text cursor')
def run(self):
cursorPos = self.env['runtime']['cursorManager'].getReviewOrTextCursor()
x, y, currLine = \
line_utils.getCurrentLine(cursorPos['x'], cursorPos['y'], self.env['screen']['newContentText'])
if currLine.isspace():
self.env['runtime']['outputManager'].presentText(_("blank"), soundIcon='EmptyLine', interrupt=True)
else:
self.env['runtime']['outputManager'].presentText(currLine[cursorPos['x']:], interrupt=True)
self.env['runtime']['outputManager'].announceActiveCursor()
def setCallback(self, callback):
def shutdown(self):
pass
def get_description(self):
return _(
"read to end of line, use review cursor if you are in review mode, "
"otherwhise use text cursor"
)
def run(self):
cursor_pos = self.env["runtime"][
"CursorManager"
].get_review_or_text_cursor()
x, y, curr_line = line_utils.get_current_line(
cursor_pos["x"],
cursor_pos["y"],
self.env["screen"]["new_content_text"],
)
if curr_line.isspace():
self.env["runtime"]["OutputManager"].present_text(
_("blank"), sound_icon="EmptyLine", interrupt=True
)
else:
self.env["runtime"]["OutputManager"].present_text(
curr_line[cursor_pos["x"] :], interrupt=True
)
self.env["runtime"]["OutputManager"].announce_active_cursor()
def set_callback(self, callback):
pass
@@ -0,0 +1,105 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import os
from fenrirscreenreader.core import debug
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 _("cycles between available keyboard layouts")
def get_available_layouts(self):
"""Get list of available keyboard layout files"""
layouts = []
# Check standard locations for keyboard layouts
settings_root = "/etc/fenrirscreenreader/"
if not os.path.exists(settings_root):
# Fallback to source directory
import fenrirscreenreader
fenrir_path = os.path.dirname(fenrirscreenreader.__file__)
settings_root = fenrir_path + "/../../config/"
keyboard_path = settings_root + "keyboard/"
if os.path.exists(keyboard_path):
for file in os.listdir(keyboard_path):
if (
file.endswith(".conf")
and not file.startswith("__")
and not file.lower().startswith("pty")
):
layout_name = file.replace(".conf", "")
if layout_name not in layouts:
layouts.append(layout_name)
# Ensure we have at least basic layouts
if not layouts:
layouts = ["desktop", "laptop"]
else:
layouts.sort()
return layouts
def run(self):
current_layout = self.env["runtime"]["SettingsManager"].get_setting(
"keyboard", "keyboardLayout"
)
# Extract layout name from full path if needed
if "/" in current_layout:
current_layout = os.path.basename(current_layout).replace(
".conf", ""
)
# Get available layouts
available_layouts = self.get_available_layouts()
# Find next layout in cycle
try:
current_index = available_layouts.index(current_layout)
next_index = (current_index + 1) % len(available_layouts)
except ValueError:
# If current layout not found, start from beginning
next_index = 0
next_layout = available_layouts[next_index]
# Update setting and reload shortcuts
self.env["runtime"]["SettingsManager"].set_setting(
"keyboard", "keyboardLayout", next_layout
)
# Reload shortcuts with new layout
try:
self.env["runtime"]["InputManager"].reload_shortcuts()
self.env["runtime"]["OutputManager"].present_text(
_("Switched to {} keyboard layout").format(next_layout),
interrupt=True,
)
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
"Error reloading shortcuts: " + str(e), debug.DebugLevel.ERROR
)
self.env["runtime"]["OutputManager"].present_text(
_("Error switching keyboard layout"), interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,29 +2,41 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import datetime
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('presents the date')
pass
def get_description(self):
return _("presents the date")
def run(self):
dateFormat = self.env['runtime']['settingsManager'].getSetting('general', 'dateFormat')
date_format = self.env["runtime"]["SettingsManager"].get_setting(
"general", "date_format"
)
# get the time formatted
dateString = datetime.datetime.strftime(datetime.datetime.now(), dateFormat)
date_string = datetime.datetime.strftime(
datetime.datetime.now(), date_format
)
# present the time via speak and braile, there is no soundicon, interrupt the current speech
self.env['runtime']['outputManager'].presentText(dateString , soundIcon='', interrupt=True)
# present the time via speak and braile, there is no soundicon,
# interrupt the current speech
self.env["runtime"]["OutputManager"].present_text(
date_string, sound_icon="", interrupt=True
)
def setCallback(self, callback):
def set_callback(self, callback):
pass
@@ -1,39 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
initialized = False
try:
import alsaaudio
initialized = True
except:
pass
from fenrirscreenreader.core import debug
class command():
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _("Decrease system volume")
def run(self):
if not initialized:
self.env['runtime']['outputManager'].presentText(_('alsaaudio is not installed'), interrupt=True)
return
mixer = alsaaudio.Mixer()
value = mixer.getvolume()[0]
value = value - 5
if value < 5:
value = 5
mixer.setvolume(value)
self.env['runtime']['outputManager'].presentText(_("{0} percent system volume").format(value), interrupt=True)
def setCallback(self, callback):
pass
@@ -2,32 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import math
import importlib.util
import os
class command():
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):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('decrease sound volume')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('sound', 'volume')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.1:
value = 0.1
self.env['runtime']['settingsManager'].setSetting('sound', 'volume', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent sound volume").format(int(value * 100)), soundIcon='SoundOff', interrupt=True)
def setCallback(self, callback):
pass
super().__init__("sound", "volume", "dec")
@@ -2,28 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import math
import importlib.util
import os
class command():
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):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Decreases the pitch of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'pitch')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.0:
value = 0.0
self.env['runtime']['settingsManager'].setSetting('speech', 'pitch', str(value))
self.env['runtime']['outputManager'].presentText(_('{0} percent speech pitch').format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__("speech", "pitch", "dec")
@@ -2,29 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import math
import importlib.util
import os
class command():
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):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Decreases the rate of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'rate')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.0:
value = 0.0
self.env['runtime']['settingsManager'].setSetting('speech', 'rate', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech rate").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__("speech", "rate", "dec")
@@ -2,30 +2,20 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import math
import importlib.util
import os
class command():
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):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('Decreases the volume of the speech')
def run(self):
value = self.env['runtime']['settingsManager'].getSettingAsFloat('speech', 'volume')
value = round((math.ceil(10 * value) / 10) - 0.1, 2)
if value < 0.1:
value = 0.1
self.env['runtime']['settingsManager'].setSetting('speech', 'volume', str(value))
self.env['runtime']['outputManager'].presentText(_("{0} percent speech volume").format(int(value * 100)), soundIcon='', interrupt=True)
def setCallback(self, callback):
pass
super().__init__("speech", "volume", "dec")
@@ -2,27 +2,36 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('exits review mode')
def run(self):
if not self.env['runtime']['cursorManager'].isReviewMode():
self.env['runtime']['outputManager'].presentText(_("Not in Review Mode"), interrupt=True)
return
self.env['runtime']['cursorManager'].clearReviewCursor()
self.env['runtime']['outputManager'].presentText(_("Exiting Review Mode"), interrupt=True)
def setCallback(self, callback):
def shutdown(self):
pass
def get_description(self):
return _("exits review mode")
def run(self):
if not self.env["runtime"]["CursorManager"].is_review_mode():
self.env["runtime"]["OutputManager"].present_text(
_("Not in Review Mode"), interrupt=True
)
return
self.env["runtime"]["CursorManager"].clear_review_cursor()
self.env["runtime"]["OutputManager"].present_text(
_("Exiting Review Mode"), interrupt=True
)
def set_callback(self, callback):
pass
@@ -2,38 +2,66 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import os
class command():
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment, scriptPath=''):
def initialize(self, environment, script_path=""):
self.env = environment
def shutdown(self):
pass
def getDescription(self):
return _('export the current fenrir clipboard to a file')
def run(self):
clipboardFilePath = self.env['runtime']['settingsManager'].getSetting('general', 'clipboardExportPath')
clipboardFilePath = clipboardFilePath.replace('$user',self.env['general']['currUser'])
clipboardFilePath = clipboardFilePath.replace('$USER',self.env['general']['currUser'])
clipboardFilePath = clipboardFilePath.replace('$User',self.env['general']['currUser'])
clipboardFile = open(clipboardFilePath,'w')
def get_description(self):
return _("export the current fenrir clipboard to a file")
def run(self):
clipboard_file_path = self.env["runtime"][
"SettingsManager"
].get_setting("general", "clipboardExportPath")
clipboard_file_path = clipboard_file_path.replace(
"$user", self.env["general"]["curr_user"]
)
clipboard_file_path = clipboard_file_path.replace(
"$USER", self.env["general"]["curr_user"]
)
clipboard_file_path = clipboard_file_path.replace(
"$User", self.env["general"]["curr_user"]
)
clipboard_file = open(clipboard_file_path, "w")
try:
if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'):
self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True)
return
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
clipboardFile.write(clipboard)
clipboardFile.close()
os.chmod(clipboardFilePath, 0o666)
self.env['runtime']['outputManager'].presentText(_('clipboard exported to file'), interrupt=True)
if self.env["runtime"]["MemoryManager"].is_index_list_empty(
"clipboardHistory"
):
self.env["runtime"]["OutputManager"].present_text(
_("clipboard empty"), interrupt=True
)
return
clipboard = self.env["runtime"][
"MemoryManager"
].get_index_list_element("clipboardHistory")
clipboard_file.write(clipboard)
clipboard_file.close()
os.chmod(clipboard_file_path, 0o644)
self.env["runtime"]["OutputManager"].present_text(
_("clipboard exported to file"), interrupt=True
)
except Exception as e:
self.env['runtime']['debug'].writeDebugOut('export_clipboard_to_file:run: Filepath:'+ clipboardFile +' trace:' + str(e),debug.debugLevel.ERROR)
def setCallback(self, callback):
self.env["runtime"]["DebugManager"].write_debug_out(
"export_clipboard_to_file:run: Filepath:"
+ clipboard_file
+ " trace:"
+ str(e),
debug.DebugLevel.ERROR,
)
def set_callback(self, callback):
pass
@@ -2,70 +2,96 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core import debug
import os
import importlib
import _thread
import importlib
import os
import pyperclip
class command():
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment, scriptPath=''):
def initialize(self, environment, script_path=""):
self.env = environment
self.scriptPath = scriptPath
self.script_path = script_path
def shutdown(self):
pass
def getDescription(self):
return _('Export current fenrir clipboard to X or GUI clipboard')
def run(self):
_thread.start_new_thread(self._threadRun , ())
def _threadRun(self):
def get_description(self):
return _("Export current fenrir clipboard to X or GUI clipboard")
def run(self):
_thread.start_new_thread(self._thread_run, ())
def _thread_run(self):
try:
# Check if clipboard is empty
if self.env['runtime']['memoryManager'].isIndexListEmpty('clipboardHistory'):
self.env['runtime']['outputManager'].presentText(_('clipboard empty'), interrupt=True)
if self.env["runtime"]["MemoryManager"].is_index_list_empty(
"clipboardHistory"
):
self.env["runtime"]["OutputManager"].present_text(
_("clipboard empty"), interrupt=True
)
return
# Get current clipboard content
clipboard = self.env['runtime']['memoryManager'].getIndexListElement('clipboardHistory')
clipboard = self.env["runtime"][
"MemoryManager"
].get_index_list_element("clipboardHistory")
# Remember original display environment variable if it exists
originalDisplay = os.environ.get('DISPLAY', '')
original_display = os.environ.get("DISPLAY", "")
success = False
# Try different display options
for i in range(10):
display = f":{i}"
try:
# Set display environment variable
os.environ['DISPLAY'] = display
os.environ["DISPLAY"] = display
# Attempt to set clipboard content
importlib.reload(pyperclip) # Weird workaround for some distros
# Weird workaround for some distros
importlib.reload(pyperclip)