61 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 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 e255651c28 Merged testing. 2025-08-22 00:29:02 -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 e9a0101fe7 Merged README.md 2025-08-04 14:26:55 -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 2dd732dc9d Emojis added, improvements to pyt mode. 2025-07-19 16:50:53 -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 b9abf02b12 A few minor tweaks in preparation for release. 2025-07-13 15:31:57 -04:00
Storm Dragon fe5e2c065e Merge branch 'testing' moving closer to tagged release. 2025-07-09 18:32:15 -04:00
Storm Dragon ef3ebee10c Preparing for new tagged version. Please watch for bugs. 2025-07-09 09:33:19 -04:00
Storm Dragon 271c4fc18f Table mode fixes and improvements to application detection. 2025-07-08 14:41:43 -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 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 8af1cca879 Latest changes and bug fixes. 2025-06-27 21:18:27 -04:00
Storm Dragon a394ea0222 Code cleanup and bug fixes. 2025-06-20 02:19:57 -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 f6be6c54fb Bug fixes mostlry, tested and seems to be working better. 2025-06-13 23:21:46 -04:00
Storm Dragon f18c31df6c Merge branch 'testing' bug fix for remoteDriver 2025-06-07 18:24:44 -04:00
Storm Dragon 3dca3e5b23 Merged for new release. 2025-06-07 12:23:53 -04:00
Storm Dragon 1b9a9a90b1 Fixed version conflict. 2025-06-06 20:35:07 -04:00
Storm Dragon 4c8c8d896d Fixed version conflict. 2025-06-05 16:05:11 -04:00
Storm Dragon 4672592dba Latest merge from testing. 2025-04-28 15:41:14 -04:00
Storm Dragon 7a12992b88 latest release. 2025-04-17 00:36:26 -04:00
Storm Dragon 7a87fb51bb Fixed version for master branch. 2025-04-14 20:04:14 -04:00
Storm Dragon 2cc2fda28c Actually fix the version file this time. 2025-03-02 17:59:20 -05:00
Storm Dragon c99d0f6ee1 Fixed version.py. 2025-03-02 17:44:32 -05:00
Storm Dragon 5b642cd9e2 Fixed error in settings file. 2025-02-26 17:41:01 -05:00
172 changed files with 3956 additions and 1015 deletions
-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 After=systemd-udev-settle.service getty.target
[Service] [Service]
Type=forking Type=forking
PIDFile=/var/run/fenrir.pid PIDFile=/run/fenrir.pid
ExecStart=/usr/bin/fenrir ExecStart=/usr/bin/fenrir
ExecReload=/usr/bin/kill -HUP $MAINPID ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always Restart=always
#Group=fenrirscreenreader
#User=fenrirscreenreader
[Install] [Install]
WantedBy=getty.target WantedBy=getty.target
+1 -1
View File
@@ -4,7 +4,7 @@ Wants=systemd-udev-settle.service
After=systemd-udev-settle.service sound.target After=systemd-udev-settle.service sound.target
[Service] [Service]
Type=forking Type=forking
PIDFile=/var/run/fenrir.pid PIDFile=/run/fenrir.pid
ExecStart=/usr/local/bin/fenrir ExecStart=/usr/local/bin/fenrir
ExecReload=/usr/bin/kill -HUP $MAINPID ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always Restart=always
+2 -2
View File
@@ -1,5 +1,5 @@
Please report Bugs and feature requests to: 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 debug file that shows the issue. For bugs, please provide a debug file that shows the issue.
How to create a debug file: How to create a debug file:
+3 -2
View File
@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific # linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume # Read-all functionality
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume 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_SHIFT,KEY_V=announce_fenrir_version
KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout
+3 -2
View File
@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
# linux specific # linux specific
KEY_FENRIR,KEY_F7=import_clipboard_from_x KEY_FENRIR,KEY_F7=import_clipboard_from_x
KEY_FENRIR,KEY_F8=export_clipboard_to_x KEY_FENRIR,KEY_F8=export_clipboard_to_x
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume # Read-all functionality
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume 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_SHIFT,KEY_V=announce_fenrir_version
KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout
+11 -4
View File
@@ -3,9 +3,9 @@
enabled=True enabled=True
# Select the driver used to play sounds, choices are genericDriver and gstreamerDriver. # Select the driver used to play sounds, choices are genericDriver and gstreamerDriver.
# Sox is the default. # Gstreamer is the default.
#driver=gstreamerDriver driver=gstreamerDriver
driver=genericDriver #driver=genericDriver
# Sound themes. These are the pack of sounds used for sound alerts. # Sound themes. These are the pack of sounds used for sound alerts.
# Sound packs may be located at /usr/share/sounds # Sound packs may be located at /usr/share/sounds
@@ -110,7 +110,7 @@ keyboardLayout=desktop
# echo chars while typing. # echo chars while typing.
# 0 = None # 0 = None
# 1 = always # 1 = always
# 2 = only while capslock # 2 = only while capslock (not compatible with capslock as fenrir key)
charEchoMode=1 charEchoMode=1
# echo deleted chars # echo deleted chars
charDeleteEcho=True charDeleteEcho=True
@@ -217,6 +217,13 @@ list=
[menu] [menu]
vmenuPath= 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 quickMenu=speech#rate;speech#pitch;speech#volume
[prompt] [prompt]
+6 -6
View File
@@ -7,14 +7,14 @@ configurable and easy to customize and extend.
=== Credit and intended audience === Credit and intended audience
This document is just a customization for Slint of the genuine 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. Manual] motly written by Chrys, main developer of Fenrir.
It has been adapted to its intended audience: end users of Fenrir on It has been adapted to its intended audience: end users of Fenrir on
Slint where it is already installed, thus concentrates on its setting Slint where it is already installed, thus concentrates on its setting
and usage. You will find more information about its features, and usage. You will find more information about its features,
installation and how customize and troubleshoot it and contribute to its 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]. repository].
=== Getting started with Fenrir === Getting started with Fenrir
@@ -2193,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 assign them to a shortcut using the filename without an extension or
place them in a hook trigger like OnInput or OnScreenChange. For further place them in a hook trigger like OnInput or OnScreenChange. For further
information see developer guide. Good Examples: 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), (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: (interrupt output) the basic scheme for a command is as follows:
.... ....
@@ -2218,7 +2218,7 @@ class command():
pass 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] lives here]
* The class needs to have the name "command". * The class needs to have the name "command".
* "initialize" is running once whilst loading the command. * "initialize" is running once whilst loading the command.
@@ -2276,7 +2276,7 @@ root.
=== Bugreports and feature requests === Bugreports and feature requests
Please report Bugs and feature requests to: 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 for bugs please provide a link:#Howto create a debug file[debug] file
that shows the issue. that shows the issue.
+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. - Download the latest stable version from the [[https://linux-a11y.org/index.php?page=fenrir-screenreader|Fenrir-Project]] site.
- Unpack the archive - 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: - install the missing dependencies an standard installation requires the following:
* python3 >= 3.3 (and all the following is needed for python3 ) * python3 >= 3.3 (and all the following is needed for python3 )
* python3-speechd (screen) * python3-speechd (screen)
@@ -171,7 +171,7 @@ For Arch there are PKGBUILDs in the AUR:
* python3-pyenchant (spellchecker) * python3-pyenchant (spellchecker)
* your language for aspell (aspell-<lang>) (spellchecker) * your language for aspell (aspell-<lang>) (spellchecker)
* sox (sound) * 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 - run "install.sh" as root
this installs Fenrir as the following 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: 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 ===== ===== Auto Start =====
@@ -1270,7 +1270,7 @@ File: ''/usr/share/fenrirscreenreader/scripts/helloWorld__-__key_h.sh'':
===== Commands ===== ===== Commands =====
You can place your own commands in "/usr/share/fenrirscreenreader/commands" (path is configurable in settings.conf). 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. 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: the basic scheme for a command is as follows:
from core import debug from core import debug
@@ -1289,7 +1289,7 @@ the basic scheme for a command is as follows:
def setCallback(self, callback): def setCallback(self, callback):
pass 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". * The class needs to have the name "command".
* "initialize" is running once whilst loading the command. * "initialize" is running once whilst loading the command.
* "shutdown" is running on unload like the command (quit fenrir) * "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"'' - You can test if speech-dispatcher works by invoking it as root\\ ''sudo spd-say "hello world"''
===== Bugreports and feature requests ===== ===== Bugreports and feature requests =====
Please report Bugs and feature requests to: 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. for bugs please provide a [[#Howto create a debug file|debug]] file that shows the issue.
==== How-to create a debug file ==== ==== How-to create a debug file ====
+1 -1
View File
@@ -56,7 +56,7 @@ To test Fenrir:
sudo fenrir sudo fenrir
To have Fenrir start on system boot using systemd: 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 move the service file to: /etc/systemd/system/fenrir.service
sudo systemctl enable fenrir 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,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import importlib.util
import os
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "adjustment_base.py")
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
adjustment_command = _module.adjustment_command
class command(adjustment_command):
def __init__(self):
super().__init__("alsa", "volume", "dec")
@@ -1,21 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import importlib.util
import os
from fenrirscreenreader.core.i18n import _
_base_path = os.path.join(os.path.dirname(__file__), "adjustment_base.py")
_spec = importlib.util.spec_from_file_location("adjustment_base", _base_path)
_module = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(_module)
adjustment_command = _module.adjustment_command
class command(adjustment_command):
def __init__(self):
super().__init__("alsa", "volume", "inc")
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Read all text line by line from current position")
def run(self):
self.env["runtime"]["OutputManager"].present_text(
"Read-all functionality temporarily disabled",
interrupt=True
)
return
# Check if speechd is available for callbacks
try:
speech_driver = self.env['runtime']['SpeechDriver']
if not (hasattr(speech_driver, '_sd') and speech_driver._sd):
self.env["runtime"]["OutputManager"].present_text(
_("Read all requires speech-dispatcher - not available with current speech driver"),
interrupt=True
)
return
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllByLine run: Error checking speech driver: {e}",
debug.DebugLevel.ERROR
)
return
# Check if ReadAllManager is available
if "ReadAllManager" not in self.env["runtime"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager not in runtime"),
interrupt=True
)
return
elif not self.env["runtime"]["ReadAllManager"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager is None"),
interrupt=True
)
return
# Start continuous line-by-line reading
if self.env["runtime"]["ReadAllManager"].is_active():
# Stop if already active
self.env["runtime"]["ReadAllManager"].stop_read_all()
else:
# Start line-by-line reading
self.env["runtime"]["ReadAllManager"].start_read_all('line')
def set_callback(self, callback):
pass
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Read all text page by page from current position")
def run(self):
# Check if speechd is available for callbacks
try:
speech_driver = self.env['runtime']['SpeechDriver']
if not (hasattr(speech_driver, '_sd') and speech_driver._sd):
self.env["runtime"]["OutputManager"].present_text(
_("Read all requires speech-dispatcher - not available with current speech driver"),
interrupt=True
)
return
except Exception as e:
self.env["runtime"]["DebugManager"].write_debug_out(
f"ReadAllByPage run: Error checking speech driver: {e}",
debug.DebugLevel.ERROR
)
return
# Check if ReadAllManager is available
if "ReadAllManager" not in self.env["runtime"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager not in runtime"),
interrupt=True
)
return
elif not self.env["runtime"]["ReadAllManager"]:
self.env["runtime"]["OutputManager"].present_text(
_("Read all manager is None"),
interrupt=True
)
return
# Start continuous page-by-page reading
if self.env["runtime"]["ReadAllManager"].is_active():
# Stop if already active
self.env["runtime"]["ReadAllManager"].stop_read_all()
else:
# Start page-by-page reading
self.env["runtime"]["ReadAllManager"].start_read_all('page')
def set_callback(self, callback):
pass
@@ -4,7 +4,7 @@
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors. # By Chrys, Storm Dragon, and contributors.
import time
from fenrirscreenreader.core.i18n import _ from fenrirscreenreader.core.i18n import _
@@ -14,55 +14,150 @@ class command:
def initialize(self, environment): def initialize(self, environment):
self.env = environment self.env = environment
# Initialize tab completion state tracking
if "tabCompletion" not in self.env["commandBuffer"]:
self.env["commandBuffer"]["tabCompletion"] = {
"lastTabTime": 0,
"pendingCompletion": None,
"retryCount": 0
}
def shutdown(self): def shutdown(self):
pass pass
def get_description(self): def get_description(self):
return "No Description found" return _("Announces tab completions when detected")
def _is_recent_tab_input(self):
"""Check if TAB was pressed recently (within 200ms window)"""
current_time = time.time()
tab_detected = False
# Check KEY mode
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
if (self.env["runtime"]["InputManager"].get_last_deepest_input()
in [["KEY_TAB"]]):
tab_detected = True
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
# Check BYTE mode
elif self.env["runtime"]["InputManager"].get_shortcut_type() in ["BYTE"]:
for currByte in self.env["runtime"]["ByteManager"].get_last_byte_key():
if currByte == 9: # Tab character
tab_detected = True
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
# Check if tab was pressed recently (200ms window)
if not tab_detected:
time_since_tab = current_time - self.env["commandBuffer"]["tabCompletion"]["lastTabTime"]
if time_since_tab <= 0.2: # 200ms window
tab_detected = True
return tab_detected
def _is_flexible_completion_match(self, x_move, delta_text):
"""Use flexible matching instead of strict equality"""
if not delta_text:
return False
delta_len = len(delta_text)
# Exact match (preserve original behavior)
if x_move == delta_len:
return True
# Flexible range: allow ±2 characters difference
# Handles spacing adjustments and unicode width variations
if abs(x_move - delta_len) <= 2 and delta_len > 0:
return True
# For longer completions, allow proportional variance
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
return True
return False
def _detect_completion_patterns(self, delta_text):
"""Detect common tab completion patterns for improved accuracy"""
if not delta_text:
return False
delta_stripped = delta_text.strip()
# File extension completion
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
return True
# Path completion (contains / or \)
if '/' in delta_stripped or '\\' in delta_stripped:
return True
# Command parameter completion (starts with -)
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
return True
# Word boundary completion (alphanumeric content)
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
return True
return False
def run(self): def run(self):
# try to detect the tab completion by cursor change """Enhanced tab completion detection with improved reliability"""
# Basic cursor movement check (preserve original logic)
x_move = ( x_move = (
self.env["screen"]["new_cursor"]["x"] self.env["screen"]["new_cursor"]["x"]
- self.env["screen"]["old_cursor"]["x"] - self.env["screen"]["old_cursor"]["x"]
) )
if x_move <= 0: if x_move <= 0:
return return
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
if not ( # Enhanced tab input detection with persistence
self.env["runtime"]["InputManager"].get_last_deepest_input() tab_detected = self._is_recent_tab_input()
in [["KEY_TAB"]]
): # Fallback for non-tab movements (preserve original thresholds)
if x_move < 5: if not tab_detected:
return if x_move < 5:
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [ return
"BYTE"
]: # Screen delta availability check
found = False
for currByte in self.env["runtime"][
"ByteManager"
].get_last_byte_key():
if currByte == 9:
found = True
if not found:
if x_move < 5:
return
# is there any change?
if not self.env["runtime"]["ScreenManager"].is_delta(): if not self.env["runtime"]["ScreenManager"].is_delta():
# If tab was detected but no delta yet, store for potential retry
if tab_detected and self.env["commandBuffer"]["tabCompletion"]["retryCount"] < 2:
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = {
"x_move": x_move,
"timestamp": time.time()
}
self.env["commandBuffer"]["tabCompletion"]["retryCount"] += 1
return return
if not x_move == len(self.env["screen"]["new_delta"]):
return delta_text = self.env["screen"]["new_delta"]
# filter unneded space on word begin
curr_delta = self.env["screen"]["new_delta"] # Enhanced correlation checking with flexible matching
if ( if not self._is_flexible_completion_match(x_move, delta_text):
len(curr_delta.strip()) != len(curr_delta) # Additional pattern-based validation for edge cases
and curr_delta.strip() != "" if not (tab_detected and self._detect_completion_patterns(delta_text)):
): return
# Reset retry counter on successful detection
self.env["commandBuffer"]["tabCompletion"]["retryCount"] = 0
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = None
# Mark that we've handled this delta to prevent duplicate announcements
# This prevents the incoming text handler from also announcing the same content
self.env["commandBuffer"]["tabCompletion"]["lastProcessedDelta"] = delta_text
self.env["commandBuffer"]["tabCompletion"]["lastProcessedTime"] = time.time()
# Text filtering and announcement (preserve original behavior)
curr_delta = delta_text
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
curr_delta = curr_delta.strip() curr_delta = curr_delta.strip()
self.env["runtime"]["OutputManager"].present_text(
curr_delta, interrupt=True, announce_capital=True, flush=False # Enhanced announcement with better handling of empty completions
) if curr_delta:
self.env["runtime"]["OutputManager"].present_text(
curr_delta, interrupt=True, announce_capital=True, flush=False
)
def set_callback(self, callback): def set_callback(self, callback):
pass pass
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
import time
from fenrirscreenreader.core.i18n import _
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _("Handles delayed retry for tab completion detection")
def run(self):
"""Check for and process pending tab completions with slight delay"""
# Only process if we have tab completion state
if "tabCompletion" not in self.env["commandBuffer"]:
return
tab_state = self.env["commandBuffer"]["tabCompletion"]
pending = tab_state.get("pendingCompletion")
if not pending:
return
current_time = time.time()
# Process pending completion after 50ms delay
if current_time - pending["timestamp"] < 0.05:
return
# Check if screen delta is now available
if not self.env["runtime"]["ScreenManager"].is_delta():
# Give up after 200ms total
if current_time - pending["timestamp"] > 0.2:
tab_state["pendingCompletion"] = None
tab_state["retryCount"] = 0
return
# Process the delayed completion
delta_text = self.env["screen"]["new_delta"]
x_move = pending["x_move"]
# Use the same flexible matching logic as main tab completion
match_found = self._is_flexible_completion_match(x_move, delta_text)
if not match_found:
# Try pattern-based detection as final fallback
match_found = self._detect_completion_patterns(delta_text)
if match_found and delta_text:
# Mark that we've handled this delta to prevent duplicate announcements
tab_state["lastProcessedDelta"] = delta_text
tab_state["lastProcessedTime"] = current_time
# Filter and announce the completion
curr_delta = delta_text
if (len(curr_delta.strip()) != len(curr_delta) and
curr_delta.strip() != ""):
curr_delta = curr_delta.strip()
if curr_delta:
self.env["runtime"]["OutputManager"].present_text(
curr_delta, interrupt=True, announce_capital=True, flush=False
)
# Clear pending completion
tab_state["pendingCompletion"] = None
tab_state["retryCount"] = 0
def _is_flexible_completion_match(self, x_move, delta_text):
"""Use flexible matching (duplicated from main command for heartbeat use)"""
if not delta_text:
return False
delta_len = len(delta_text)
# Exact match
if x_move == delta_len:
return True
# Flexible range: allow ±2 characters difference
if abs(x_move - delta_len) <= 2 and delta_len > 0:
return True
# For longer completions, allow proportional variance
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
return True
return False
def _detect_completion_patterns(self, delta_text):
"""Detect common tab completion patterns (duplicated from main command)"""
if not delta_text:
return False
delta_stripped = delta_text.strip()
# File extension completion
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
return True
# Path completion
if '/' in delta_stripped or '\\' in delta_stripped:
return True
# Command parameter completion
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
return True
# Word boundary completion
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
return True
return False
def set_callback(self, callback):
pass
@@ -24,11 +24,22 @@ class command:
def run(self): def run(self):
if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]: if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]:
return return
# Only announce numlock changes if an actual numlock key was pressed # Only announce numlock changes if an actual numlock key was pressed
# This prevents spurious announcements from external numpad automatic state changes # AND the LED state actually changed (some numpads send spurious NUMLOCK events)
current_input = self.env["input"]["currInput"] current_input = self.env["input"]["currInput"]
if current_input and "KEY_NUMLOCK" in current_input:
# Check if this is a genuine numlock key press by verifying:
# 1. KEY_NUMLOCK is in the current input sequence
# 2. The LED state has actually changed
# 3. This isn't just a side effect from a KP_ key (which some buggy numpads do)
is_genuine_numlock = (
current_input and
"KEY_NUMLOCK" in current_input and
not any(key.startswith("KEY_KP") for key in current_input if isinstance(key, str))
)
if is_genuine_numlock:
if self.env["input"]["newNumLock"]: if self.env["input"]["newNumLock"]:
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
_("Numlock on"), interrupt=True _("Numlock on"), interrupt=True
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _(
"TUI focus mode handler - suppresses screen update spam "
"for interactive TUI applications"
)
def run(self):
# Check if TUI mode is enabled
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"focus", "tui"
):
return
# TUI mode is active - set suppression flag for incoming handler
# This prevents the 70000-incoming.py command from announcing
# screen updates
self.env["commandBuffer"]["tuiSuppressIncoming"] = True
self.env["runtime"]["DebugManager"].write_debug_out(
"tui_focus_handler: TUI mode active, suppressing incoming text",
debug.DebugLevel.INFO
)
def set_callback(self, callback):
pass
@@ -66,12 +66,18 @@ class command:
# Check if delta is too large (screen change) vs small incremental # Check if delta is too large (screen change) vs small incremental
# updates # updates
delta_length = len(self.env["screen"]["new_delta"]) delta_text = self.env["screen"]["new_delta"]
delta_length = len(delta_text)
if ( if (
delta_length > 200 delta_length > 200
): # Allow longer progress lines like Claude Code's status ): # Allow longer progress lines like Claude Code's status
return False return False
# If delta contains newlines and is substantial, let incoming handler
# deal with it to avoid interfering with multi-line text output
if '\n' in delta_text and delta_length > 50:
return False
# Check if current line looks like a prompt - progress unlikely during # Check if current line looks like a prompt - progress unlikely during
# prompts # prompts
if self.is_current_line_prompt(): if self.is_current_line_prompt():
@@ -270,7 +276,7 @@ class command:
self.env["commandBuffer"]["lastProgressTime"] = current_time self.env["commandBuffer"]["lastProgressTime"] = current_time
return return
# Pattern 5: Braille progress indicators # Pattern 5: Braille spinner indicators
braille_match = re.search(r'[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⡿⣟⣯⣷⣾⣽⣻⢿]', text) braille_match = re.search(r'[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⡿⣟⣯⣷⣾⣽⣻⢿]', text)
if braille_match: if braille_match:
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0: if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
@@ -279,26 +285,46 @@ class command:
return return
# Pattern 6: Claude Code progress indicators # Pattern 6: Claude Code progress indicators
claude_progress_match = re.search(r'^[·✶✢✻*]\s+[\w\s-]+[…\.]*\s*\(esc to interrupt\)\s*$', text) claude_progress_match = re.search(r'[·✶✢✻*].*?\(esc to interrupt[^)]*\)', text)
if claude_progress_match: if claude_progress_match:
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0: if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
self.play_activity_beep() self.play_activity_beep()
self.env["commandBuffer"]["lastProgressTime"] = current_time self.env["commandBuffer"]["lastProgressTime"] = current_time
return return
# Pattern 7: Moon phase progress indicators # Pattern 7: Moon phase spinner indicators
moon_match = re.search(r'[🌑🌒🌓🌔🌕🌖🌗🌘]', text) moon_match = re.search(r'[🌑🌒🌓🌔🌕🌖🌗🌘]', text)
if moon_match: if moon_match:
moon_phases = { if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
'🌑': 0, '🌒': 12.5, '🌓': 25, '🌔': 37.5, self.play_activity_beep()
'🌕': 50, '🌖': 62.5, '🌗': 75, '🌘': 87.5 self.env["commandBuffer"]["lastProgressTime"] = current_time
} return
moon_char = moon_match.group(0)
if moon_char in moon_phases: # Pattern 8: Thinking/processing with timing (🔄 Thinking... 23s)
percentage = moon_phases[moon_char] thinking_match = re.search(r'🔄[^\w]*(?:thinking|processing|working|analyzing)[^\d]*(\d+)s?\b', text, re.IGNORECASE)
if percentage != self.env["commandBuffer"]["lastProgressValue"]: if thinking_match:
self.play_progress_tone(percentage) # Extract timing value for activity beep frequency adjustment
self.env["commandBuffer"]["lastProgressValue"] = percentage seconds = int(thinking_match.group(1))
# Use slightly longer interval for thinking patterns to avoid spam
thinking_interval = 1.5 if seconds < 10 else 2.0
if (
current_time - self.env["commandBuffer"]["lastProgressTime"]
>= thinking_interval
):
self.env["runtime"]["DebugManager"].write_debug_out(
f"Playing thinking activity beep (timing: {seconds}s)",
debug.DebugLevel.INFO,
)
self.play_activity_beep()
self.env["commandBuffer"]["lastProgressTime"] = current_time
return
# Pattern 9: Half-circle/circle progress indicators (◐ ◓ ◒ ◑)
circle_match = re.search(r'[◐◓◒◑]', text)
if circle_match:
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
self.play_activity_beep()
self.env["commandBuffer"]["lastProgressTime"] = current_time
return return
def play_progress_tone(self, percentage): def play_progress_tone(self, percentage):
@@ -4,7 +4,7 @@
# Fenrir TTY screen reader # Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors. # By Chrys, Storm Dragon, and contributors.
import time
from fenrirscreenreader.core.i18n import _ from fenrirscreenreader.core.i18n import _
@@ -19,7 +19,26 @@ class command:
pass pass
def get_description(self): def get_description(self):
return "No Description found" return _("Announces incoming text changes")
def _was_handled_by_tab_completion(self, delta_text):
"""Check if this delta was already handled by tab completion to avoid duplicates"""
if "tabCompletion" not in self.env["commandBuffer"]:
return False
tab_state = self.env["commandBuffer"]["tabCompletion"]
# Check if this exact delta was processed recently by tab completion
if (tab_state.get("lastProcessedDelta") == delta_text and
tab_state.get("lastProcessedTime")):
# Only suppress if processed within the last 50ms to avoid stale suppression
# Reduced from 100ms to minimize false positives with rapid multi-line updates
time_since_processed = time.time() - tab_state["lastProcessedTime"]
if time_since_processed <= 0.05:
return True
return False
def run(self): def run(self):
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool( if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
@@ -30,6 +49,12 @@ class command:
if not self.env["runtime"]["ScreenManager"].is_delta(ignoreSpace=True): if not self.env["runtime"]["ScreenManager"].is_delta(ignoreSpace=True):
return return
delta_text = self.env["screen"]["new_delta"]
# Skip if tab completion already handled this delta
if self._was_handled_by_tab_completion(delta_text):
return
# this must be a keyecho or something # this must be a keyecho or something
# if len(self.env['screen']['new_delta'].strip(' \n\t')) <= 1: # if len(self.env['screen']['new_delta'].strip(' \n\t')) <= 1:
x_move = abs( x_move = abs(
@@ -41,14 +66,14 @@ class command:
- self.env["screen"]["old_cursor"]["y"] - self.env["screen"]["old_cursor"]["y"]
) )
if (x_move >= 1) and x_move == len(self.env["screen"]["new_delta"]): if (x_move >= 1) and x_move == len(delta_text):
# if len(self.env['screen']['new_delta'].strip(' \n\t0123456789')) # if len(self.env['screen']['new_delta'].strip(' \n\t0123456789'))
# <= 2: # <= 2:
if "\n" not in self.env["screen"]["new_delta"]: if "\n" not in delta_text:
return return
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta'])) # print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
self.env["screen"]["new_delta"], interrupt=False, flush=False delta_text, interrupt=False, flush=False
) )
def set_callback(self, callback): def set_callback(self, callback):
@@ -1 +1 @@
# Emoji VMenu category # Emoji VMenu category
@@ -1 +1 @@
# Flags emoji subcategory # Flags emoji subcategory
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added Canada flag to clipboard", "Added Canada flag to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added UK flag to clipboard", "Added UK flag to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added USA flag to clipboard", "Added USA flag to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -1 +1 @@
# Food emoji subcategory # Food emoji subcategory
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added red apple to clipboard", "Added red apple to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added avocado to clipboard", "Added avocado to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added beer to clipboard", "Added beer to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added birthday cake to clipboard", "Added birthday cake to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added coffee to clipboard", "Added coffee to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added donut to clipboard", "Added donut to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added hamburger to clipboard", "Added hamburger to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added pizza to clipboard", "Added pizza to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added strawberry to clipboard", "Added strawberry to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added taco to clipboard", "Added taco to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -1 +1 @@
# Holidays emoji subcategory # Holidays emoji subcategory
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added alien monster to clipboard", "Added alien monster to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added bat to clipboard", "Added bat to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added black cat to clipboard", "Added black cat to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added bunny to clipboard", "Added bunny to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added mage to clipboard", "Added mage to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added Christmas tree to clipboard", "Added Christmas tree to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added coffin to clipboard", "Added coffin to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added Easter egg to clipboard", "Added Easter egg to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added fireworks to clipboard", "Added fireworks to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added ghost to clipboard", "Added ghost to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added gift to clipboard", "Added gift to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added jack o'lantern to clipboard", "Added jack o'lantern to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added mummy to clipboard", "Added mummy to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added Santa to clipboard", "Added Santa to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added shamrock to clipboard", "Added shamrock to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added skull to clipboard", "Added skull to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added snowman to clipboard", "Added snowman to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added spider to clipboard", "Added spider to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added turkey to clipboard", "Added turkey to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added vampire to clipboard", "Added vampire to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added spider web to clipboard", "Added spider web to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added witch to clipboard", "Added witch to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added zombie to clipboard", "Added zombie to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -1 +1 @@
# Nature emoji subcategory # Nature emoji subcategory
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added butterfly to clipboard", "Added butterfly to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added cat to clipboard", "Added cat to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added cherry blossom to clipboard", "Added cherry blossom to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added dog to clipboard", "Added dog to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added moon to clipboard", "Added moon to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added rainbow to clipboard", "Added rainbow to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added rose to clipboard", "Added rose to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added sun to clipboard", "Added sun to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added sunflower to clipboard", "Added sunflower to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added tree to clipboard", "Added tree to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added the mighty wolf Fenrir to clipboard", "Added the mighty wolf Fenrir to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -1 +1 @@
# People emoji subcategory # People emoji subcategory
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added grinning face to clipboard", "Added grinning face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added angry face to clipboard", "Added angry face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added beaming face to clipboard", "Added beaming face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added face blowing kiss to clipboard", "Added face blowing kiss to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added cool face to clipboard", "Added cool face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added crying face to clipboard", "Added crying face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added devil face to clipboard", "Added devil face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added dizzy face to clipboard", "Added dizzy face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added exploding head to clipboard", "Added exploding head to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added face with symbols to clipboard", "Added face with symbols to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added grinning face with sweat to clipboard", "Added grinning face with sweat to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added heart-eyes face to clipboard", "Added heart-eyes face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added smiling face with hearts to clipboard", "Added smiling face with hearts to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added angry face with horns to clipboard", "Added angry face with horns to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added laughing face to clipboard", "Added laughing face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added nauseated face to clipboard", "Added nauseated face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added pleading face to clipboard", "Added pleading face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added poop to clipboard", "Added poop to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added rolling on floor laughing to clipboard", "Added rolling on floor laughing to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added sad face to clipboard", "Added sad face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added face savoring food to clipboard", "Added face savoring food to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added screaming face to clipboard", "Added screaming face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added shocked face to clipboard", "Added shocked face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added smiling face to clipboard", "Added smiling face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added smirking face to clipboard", "Added smirking face to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )
@@ -19,4 +19,4 @@ class command():
self.env["runtime"]["OutputManager"].present_text( self.env["runtime"]["OutputManager"].present_text(
"Added thumbs up to clipboard", "Added thumbs up to clipboard",
interrupt=False, flush=False interrupt=False, flush=False
) )

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