Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f462ca7990 | |||
| f0bbcb8a38 | |||
| aed627ec2a | |||
| e62b887e9c | |||
| bf0d134187 | |||
| c66a9ba9c2 | |||
| 2092a3e257 | |||
| d46d8de3ee | |||
| 75a8447759 | |||
| 1650eec768 | |||
| 5bb786ef4c | |||
| 7f7faa17d3 | |||
| 2766f70c5d | |||
| 8d781643bc | |||
| c184cf023a | |||
| 841c221c7b | |||
| 87553bdc38 | |||
| 77a3aae5a4 | |||
| aabc202d83 | |||
| 2f3a114790 | |||
| c797974560 | |||
| af4740d5ad | |||
| 5ef5faaebe | |||
| 7041d2567a | |||
| 2c38bcf5f4 | |||
| 96cdda99c4 | |||
| 0658d37ae8 | |||
| a6bb3e1301 | |||
| 5ff653bd00 | |||
| 356f4b01c1 | |||
| c7ad4d9200 | |||
| 90ffc2fc08 | |||
| b635f7538b | |||
| e255651c28 | |||
| 98b9c56af7 | |||
| 8bada48a09 | |||
| e9a0101fe7 | |||
| 914535d12b | |||
| 2dd732dc9d | |||
| e177c7f486 | |||
| b9abf02b12 | |||
| fe5e2c065e | |||
| ef3ebee10c | |||
| 271c4fc18f | |||
| ea56b90b48 | |||
| 1268d989b7 | |||
| 23c3ad20a1 | |||
| 8af1cca879 | |||
| a394ea0222 | |||
| efb308ac72 | |||
| f6be6c54fb | |||
| f18c31df6c | |||
| 3dca3e5b23 | |||
| 1b9a9a90b1 | |||
| 4c8c8d896d | |||
| 4672592dba | |||
| 7a12992b88 | |||
| 7a87fb51bb | |||
| 2cc2fda28c | |||
| c99d0f6ee1 | |||
| 5b642cd9e2 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
|
||||
# linux specific
|
||||
KEY_FENRIR,KEY_F7=import_clipboard_from_x
|
||||
KEY_FENRIR,KEY_F8=export_clipboard_to_x
|
||||
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
|
||||
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
|
||||
# Read-all functionality
|
||||
KEY_FENRIR,KEY_CTRL,KEY_DOWN=read_all_by_line
|
||||
KEY_FENRIR,KEY_CTRL,KEY_PAGEDOWN=read_all_by_page
|
||||
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
|
||||
KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout
|
||||
|
||||
@@ -126,7 +126,8 @@ KEY_FENRIR,KEY_CTRL,KEY_S=save_settings
|
||||
# linux specific
|
||||
KEY_FENRIR,KEY_F7=import_clipboard_from_x
|
||||
KEY_FENRIR,KEY_F8=export_clipboard_to_x
|
||||
KEY_FENRIR,KEY_CTRL,KEY_UP=inc_alsa_volume
|
||||
KEY_FENRIR,KEY_CTRL,KEY_DOWN=dec_alsa_volume
|
||||
# Read-all functionality
|
||||
KEY_FENRIR,KEY_CTRL,KEY_DOWN=read_all_by_line
|
||||
KEY_FENRIR,KEY_CTRL,KEY_PAGEDOWN=read_all_by_page
|
||||
KEY_FENRIR,KEY_SHIFT,KEY_V=announce_fenrir_version
|
||||
KEY_FENRIR,KEY_LEFTCTRL,KEY_F4=cycle_keyboard_layout
|
||||
|
||||
@@ -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
|
||||
@@ -110,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
|
||||
@@ -217,6 +217,13 @@ 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]
|
||||
|
||||
+6
-6
@@ -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
|
||||
@@ -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
|
||||
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:
|
||||
|
||||
....
|
||||
@@ -2218,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.
|
||||
@@ -2276,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.
|
||||
|
||||
+6
-6
@@ -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
@@ -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
@@ -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
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
|
||||
import time
|
||||
from fenrirscreenreader.core.i18n import _
|
||||
|
||||
|
||||
@@ -14,55 +14,150 @@ class command:
|
||||
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
# Initialize tab completion state tracking
|
||||
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||
self.env["commandBuffer"]["tabCompletion"] = {
|
||||
"lastTabTime": 0,
|
||||
"pendingCompletion": None,
|
||||
"retryCount": 0
|
||||
}
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
return "No Description found"
|
||||
return _("Announces tab completions when detected")
|
||||
|
||||
def _is_recent_tab_input(self):
|
||||
"""Check if TAB was pressed recently (within 200ms window)"""
|
||||
current_time = time.time()
|
||||
tab_detected = False
|
||||
|
||||
# Check KEY mode
|
||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
||||
if (self.env["runtime"]["InputManager"].get_last_deepest_input()
|
||||
in [["KEY_TAB"]]):
|
||||
tab_detected = True
|
||||
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
||||
|
||||
# Check BYTE mode
|
||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in ["BYTE"]:
|
||||
for currByte in self.env["runtime"]["ByteManager"].get_last_byte_key():
|
||||
if currByte == 9: # Tab character
|
||||
tab_detected = True
|
||||
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
||||
|
||||
# Check if tab was pressed recently (200ms window)
|
||||
if not tab_detected:
|
||||
time_since_tab = current_time - self.env["commandBuffer"]["tabCompletion"]["lastTabTime"]
|
||||
if time_since_tab <= 0.2: # 200ms window
|
||||
tab_detected = True
|
||||
|
||||
return tab_detected
|
||||
|
||||
def _is_flexible_completion_match(self, x_move, delta_text):
|
||||
"""Use flexible matching instead of strict equality"""
|
||||
if not delta_text:
|
||||
return False
|
||||
|
||||
delta_len = len(delta_text)
|
||||
|
||||
# Exact match (preserve original behavior)
|
||||
if x_move == delta_len:
|
||||
return True
|
||||
|
||||
# Flexible range: allow ±2 characters difference
|
||||
# Handles spacing adjustments and unicode width variations
|
||||
if abs(x_move - delta_len) <= 2 and delta_len > 0:
|
||||
return True
|
||||
|
||||
# For longer completions, allow proportional variance
|
||||
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _detect_completion_patterns(self, delta_text):
|
||||
"""Detect common tab completion patterns for improved accuracy"""
|
||||
if not delta_text:
|
||||
return False
|
||||
|
||||
delta_stripped = delta_text.strip()
|
||||
|
||||
# File extension completion
|
||||
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
|
||||
return True
|
||||
|
||||
# Path completion (contains / or \)
|
||||
if '/' in delta_stripped or '\\' in delta_stripped:
|
||||
return True
|
||||
|
||||
# Command parameter completion (starts with -)
|
||||
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
|
||||
return True
|
||||
|
||||
# Word boundary completion (alphanumeric content)
|
||||
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
# try to detect the tab completion by cursor change
|
||||
"""Enhanced tab completion detection with improved reliability"""
|
||||
# Basic cursor movement check (preserve original logic)
|
||||
x_move = (
|
||||
self.env["screen"]["new_cursor"]["x"]
|
||||
- self.env["screen"]["old_cursor"]["x"]
|
||||
)
|
||||
if x_move <= 0:
|
||||
return
|
||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
||||
if not (
|
||||
self.env["runtime"]["InputManager"].get_last_deepest_input()
|
||||
in [["KEY_TAB"]]
|
||||
):
|
||||
if x_move < 5:
|
||||
return
|
||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
||||
"BYTE"
|
||||
]:
|
||||
found = False
|
||||
for currByte in self.env["runtime"][
|
||||
"ByteManager"
|
||||
].get_last_byte_key():
|
||||
if currByte == 9:
|
||||
found = True
|
||||
if not found:
|
||||
if x_move < 5:
|
||||
return
|
||||
# is there any change?
|
||||
|
||||
# Enhanced tab input detection with persistence
|
||||
tab_detected = self._is_recent_tab_input()
|
||||
|
||||
# Fallback for non-tab movements (preserve original thresholds)
|
||||
if not tab_detected:
|
||||
if x_move < 5:
|
||||
return
|
||||
|
||||
# Screen delta availability check
|
||||
if not self.env["runtime"]["ScreenManager"].is_delta():
|
||||
# If tab was detected but no delta yet, store for potential retry
|
||||
if tab_detected and self.env["commandBuffer"]["tabCompletion"]["retryCount"] < 2:
|
||||
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = {
|
||||
"x_move": x_move,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
self.env["commandBuffer"]["tabCompletion"]["retryCount"] += 1
|
||||
return
|
||||
if not x_move == len(self.env["screen"]["new_delta"]):
|
||||
return
|
||||
# filter unneded space on word begin
|
||||
curr_delta = self.env["screen"]["new_delta"]
|
||||
if (
|
||||
len(curr_delta.strip()) != len(curr_delta)
|
||||
and curr_delta.strip() != ""
|
||||
):
|
||||
|
||||
delta_text = self.env["screen"]["new_delta"]
|
||||
|
||||
# Enhanced correlation checking with flexible matching
|
||||
if not self._is_flexible_completion_match(x_move, delta_text):
|
||||
# Additional pattern-based validation for edge cases
|
||||
if not (tab_detected and self._detect_completion_patterns(delta_text)):
|
||||
return
|
||||
|
||||
# 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()
|
||||
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):
|
||||
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):
|
||||
if self.env["input"]["oldNumLock"] == self.env["input"]["newNumLock"]:
|
||||
return
|
||||
|
||||
|
||||
# Only announce numlock changes if an actual numlock key was pressed
|
||||
# This prevents spurious announcements from external numpad automatic state changes
|
||||
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
|
||||
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"]:
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
_("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
|
||||
# updates
|
||||
delta_length = len(self.env["screen"]["new_delta"])
|
||||
delta_text = self.env["screen"]["new_delta"]
|
||||
delta_length = len(delta_text)
|
||||
if (
|
||||
delta_length > 200
|
||||
): # Allow longer progress lines like Claude Code's status
|
||||
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
|
||||
# prompts
|
||||
if self.is_current_line_prompt():
|
||||
@@ -270,7 +276,7 @@ class command:
|
||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||
return
|
||||
|
||||
# Pattern 5: Braille progress indicators
|
||||
# Pattern 5: Braille spinner indicators
|
||||
braille_match = re.search(r'[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⡿⣟⣯⣷⣾⣽⣻⢿]', text)
|
||||
if braille_match:
|
||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||
@@ -279,26 +285,46 @@ class command:
|
||||
return
|
||||
|
||||
# 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 current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||
self.play_activity_beep()
|
||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||
return
|
||||
|
||||
# Pattern 7: Moon phase progress indicators
|
||||
# Pattern 7: Moon phase spinner indicators
|
||||
moon_match = re.search(r'[🌑🌒🌓🌔🌕🌖🌗🌘]', text)
|
||||
if moon_match:
|
||||
moon_phases = {
|
||||
'🌑': 0, '🌒': 12.5, '🌓': 25, '🌔': 37.5,
|
||||
'🌕': 50, '🌖': 62.5, '🌗': 75, '🌘': 87.5
|
||||
}
|
||||
moon_char = moon_match.group(0)
|
||||
if moon_char in moon_phases:
|
||||
percentage = moon_phases[moon_char]
|
||||
if percentage != self.env["commandBuffer"]["lastProgressValue"]:
|
||||
self.play_progress_tone(percentage)
|
||||
self.env["commandBuffer"]["lastProgressValue"] = percentage
|
||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||
self.play_activity_beep()
|
||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||
return
|
||||
|
||||
# Pattern 8: Thinking/processing with timing (🔄 Thinking... 23s)
|
||||
thinking_match = re.search(r'🔄[^\w]*(?:thinking|processing|working|analyzing)[^\d]*(\d+)s?\b', text, re.IGNORECASE)
|
||||
if thinking_match:
|
||||
# Extract timing value for activity beep frequency adjustment
|
||||
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
|
||||
|
||||
def play_progress_tone(self, percentage):
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
|
||||
import time
|
||||
from fenrirscreenreader.core.i18n import _
|
||||
|
||||
|
||||
@@ -19,7 +19,26 @@ class command:
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
return "No Description found"
|
||||
return _("Announces incoming text changes")
|
||||
|
||||
def _was_handled_by_tab_completion(self, delta_text):
|
||||
"""Check if this delta was already handled by tab completion to avoid duplicates"""
|
||||
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||
return False
|
||||
|
||||
tab_state = self.env["commandBuffer"]["tabCompletion"]
|
||||
|
||||
# Check if this exact delta was processed recently by tab completion
|
||||
if (tab_state.get("lastProcessedDelta") == delta_text and
|
||||
tab_state.get("lastProcessedTime")):
|
||||
|
||||
# Only suppress if processed within the last 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):
|
||||
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):
|
||||
return
|
||||
|
||||
delta_text = self.env["screen"]["new_delta"]
|
||||
|
||||
# Skip if tab completion already handled this delta
|
||||
if self._was_handled_by_tab_completion(delta_text):
|
||||
return
|
||||
|
||||
# this must be a keyecho or something
|
||||
# if len(self.env['screen']['new_delta'].strip(' \n\t')) <= 1:
|
||||
x_move = abs(
|
||||
@@ -41,14 +66,14 @@ class command:
|
||||
- self.env["screen"]["old_cursor"]["y"]
|
||||
)
|
||||
|
||||
if (x_move >= 1) and x_move == len(self.env["screen"]["new_delta"]):
|
||||
if (x_move >= 1) and x_move == len(delta_text):
|
||||
# if len(self.env['screen']['new_delta'].strip(' \n\t0123456789'))
|
||||
# <= 2:
|
||||
if "\n" not in self.env["screen"]["new_delta"]:
|
||||
if "\n" not in delta_text:
|
||||
return
|
||||
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
self.env["screen"]["new_delta"], interrupt=False, flush=False
|
||||
delta_text, interrupt=False, flush=False
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
|
||||
@@ -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(
|
||||
"Added Canada flag to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added UK flag to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added USA flag to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Food emoji subcategory
|
||||
# Food emoji subcategory
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added red apple to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added avocado to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added beer to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added birthday cake to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added coffee to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added donut to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added hamburger to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added pizza to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added strawberry to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added taco to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Holidays emoji subcategory
|
||||
# Holidays emoji subcategory
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added alien monster to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added bat to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added black cat to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added bunny to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added mage to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Christmas tree to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added coffin to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Easter egg to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added fireworks to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added ghost to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added gift to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added jack o'lantern to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added mummy to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Santa to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added shamrock to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added skull to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added snowman to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added spider to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added turkey to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added vampire to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added spider web to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added witch to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added zombie to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Nature emoji subcategory
|
||||
# Nature emoji subcategory
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added butterfly to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added cat to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added cherry blossom to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added dog to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added moon to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added rainbow to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added rose to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added sun to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added sunflower to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added tree to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added the mighty wolf Fenrir to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# People emoji subcategory
|
||||
# People emoji subcategory
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added grinning face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added angry face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added beaming face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added face blowing kiss to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added cool face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added crying face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added devil face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added dizzy face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added exploding head to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
+1
-1
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added face with symbols to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added grinning face with sweat to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added heart-eyes face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added smiling face with hearts to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added angry face with horns to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added laughing face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added nauseated face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added pleading face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added poop to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added rolling on floor laughing to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added sad face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added face savoring food to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added screaming face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added shocked face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added smiling face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added smirking face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,4 +19,4 @@ class command():
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added thumbs up to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user