Compare commits
19 Commits
testing
...
ef3ebee10c
Author | SHA1 | Date | |
---|---|---|---|
ef3ebee10c | |||
271c4fc18f | |||
ea56b90b48 | |||
1268d989b7 | |||
23c3ad20a1 | |||
8af1cca879 | |||
a394ea0222 | |||
efb308ac72 | |||
f6be6c54fb | |||
f18c31df6c | |||
3dca3e5b23 | |||
1b9a9a90b1 | |||
4c8c8d896d | |||
4672592dba | |||
7a12992b88 | |||
7a87fb51bb | |||
2cc2fda28c | |||
c99d0f6ee1 | |||
5b642cd9e2 |
173
README.md
173
README.md
@ -12,8 +12,6 @@ This software is licensed under the LGPL v3.
|
||||
- **Multiple Interface Support**: Works in Linux TTY, and terminal emulators
|
||||
- **Flexible Driver System**: Modular architecture with multiple drivers for speech, sound, input, and screen
|
||||
- **Review Mode**: Navigate and review screen content without moving the edit cursor
|
||||
- **Table Navigation**: Advanced table mode with column headers, cell-by-cell navigation, and boundary feedback
|
||||
- **Progress Bar Monitoring**: Automatic detection and audio feedback for progress indicators with ascending tones
|
||||
- **Multiple Clipboard Support**: Manage multiple clipboard entries
|
||||
- **Configurable Key Bindings**: Desktop and laptop keyboard layouts
|
||||
- **Sound Icons**: Audio feedback for various events
|
||||
@ -158,7 +156,6 @@ By default Fenrir uses:
|
||||
- `Keypad 2` - Read current character
|
||||
- `Fenrir + T` - Announce time
|
||||
- `Fenrir + S` - Spell check current word
|
||||
- `Fenrir + Keypad *` - Toggle table mode / highlight tracking
|
||||
|
||||
### Keyboard Layouts
|
||||
|
||||
@ -405,176 +402,6 @@ setting <action> [parameters]
|
||||
- `time#delaySec=seconds` - Announcement interval
|
||||
- `time#onMinutes=00,30` - Specific minutes to announce
|
||||
|
||||
## Table Navigation
|
||||
|
||||
Fenrir includes advanced table navigation capabilities for working with tabular data in terminal applications, CSV files, and formatted text output.
|
||||
|
||||
### Entering Table Mode
|
||||
|
||||
Table mode is activated through the **toggle_highlight_tracking** command, which cycles through three focus modes:
|
||||
|
||||
1. **Highlight tracking mode** (default) - Follows text highlighting
|
||||
2. **Cursor tracking mode** - Follows text cursor movement
|
||||
3. **Table mode** - Enables table navigation
|
||||
|
||||
**Key bindings:**
|
||||
- **Desktop layout**: `Fenrir + Keypad *` (asterisk)
|
||||
- **Laptop layout**: `Fenrir + Y`
|
||||
|
||||
Press the key combination repeatedly to cycle through modes until you hear "table mode enabled".
|
||||
|
||||
### Table Navigation Commands
|
||||
|
||||
#### Column Navigation (Desktop Layout)
|
||||
- **Next column**: `Keypad 6` - Move to next table column
|
||||
- **Previous column**: `Keypad 4` - Move to previous table column
|
||||
- **First column**: `Fenrir + Keypad 4` - Jump to first column of current row
|
||||
- **Last column**: `Fenrir + Keypad 6` - Jump to last column of current row
|
||||
|
||||
#### Column Navigation (Laptop Layout)
|
||||
- **Next column**: `Fenrir + L` - Move to next table column
|
||||
- **Previous column**: `Fenrir + J` - Move to previous table column
|
||||
- **First column**: `Fenrir + Shift + J` - Jump to first column of current row
|
||||
- **Last column**: `Fenrir + Shift + L` - Jump to last column of current row
|
||||
|
||||
#### Cell Character Navigation
|
||||
- **First character in cell**: `Fenrir + Keypad 1` (desktop) or `Fenrir + Ctrl + J` (laptop)
|
||||
- **Last character in cell**: `Fenrir + Keypad 3` (desktop) or `Fenrir + Ctrl + L` (laptop)
|
||||
|
||||
### Setting Column Headers
|
||||
|
||||
For better navigation experience, you can set column headers:
|
||||
|
||||
1. **Navigate to header row**: Use normal navigation to reach the row containing column headers
|
||||
2. **Set headers**: Press `Fenrir + X` to mark the current line as the header row
|
||||
3. **Navigation feedback**: Column headers will be announced along with cell content
|
||||
|
||||
### Table Detection
|
||||
|
||||
Fenrir automatically detects table structures using multiple strategies:
|
||||
- **Delimited text**: CSV, pipe-separated (`|`), semicolon-separated (`;`), tab-separated
|
||||
- **Aligned columns**: Space-aligned columns (2+ spaces between columns)
|
||||
- **Flexible parsing**: Handles various table formats commonly found in terminal applications
|
||||
|
||||
### Table Mode Features
|
||||
|
||||
- **Cell-by-cell navigation**: Navigate through table cells with precise positioning
|
||||
- **Column header support**: Set and announce column headers for better context
|
||||
- **Boundary feedback**: Audio cues when reaching start/end of rows
|
||||
- **Empty cell handling**: Blank cells are announced as "blank"
|
||||
- **Independent tracking**: Table position is maintained independently of cursor movement
|
||||
|
||||
### Speech Output in Table Mode
|
||||
|
||||
When navigating in table mode, Fenrir announces:
|
||||
- **Cell content** followed by **column header/name**
|
||||
- **Boundary notifications**: "end of line", "start of line"
|
||||
- **Position indicators**: "first character in cell [column name]"
|
||||
|
||||
### Example Usage
|
||||
|
||||
```bash
|
||||
# Working with CSV data
|
||||
cat data.csv
|
||||
Name,Age,City
|
||||
Alice,30,New York
|
||||
Bob,25,Los Angeles
|
||||
|
||||
# 1. Press Fenrir + Keypad * until "table mode enabled"
|
||||
# 2. Navigate to "Name,Age,City" line
|
||||
# 3. Press Fenrir + X to set headers
|
||||
# 4. Use Keypad 4/6 to navigate between columns
|
||||
# 5. Each cell will be announced with its column header
|
||||
```
|
||||
|
||||
## Progress Bar Monitoring
|
||||
|
||||
Fenrir provides intelligent progress bar detection and audio feedback for various progress indicators commonly found in terminal applications.
|
||||
|
||||
### Enabling Progress Monitoring
|
||||
|
||||
**Command**: `progress_bar_monitor` (no default key binding - assign manually)
|
||||
|
||||
To enable progress monitoring:
|
||||
1. Add a key binding in your keyboard layout file
|
||||
2. Or use the remote control system: `echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock`
|
||||
|
||||
### Progress Detection Patterns
|
||||
|
||||
Fenrir automatically detects various progress indicator formats:
|
||||
|
||||
#### 1. Percentage Progress
|
||||
```
|
||||
Download: 45%
|
||||
Processing: 67.5%
|
||||
Installing: 100%
|
||||
```
|
||||
|
||||
#### 2. Fraction Progress
|
||||
```
|
||||
Files: 15/100
|
||||
Progress: 3 of 10
|
||||
Step 7/15
|
||||
```
|
||||
|
||||
#### 3. Progress Bars
|
||||
```
|
||||
[#### ] 40%
|
||||
[====> ] 50%
|
||||
[**********] 100%
|
||||
```
|
||||
|
||||
#### 4. Activity Indicators
|
||||
```
|
||||
Loading...
|
||||
Processing...
|
||||
Working...
|
||||
Installing...
|
||||
Downloading...
|
||||
Compiling...
|
||||
Building...
|
||||
```
|
||||
|
||||
### Audio Feedback
|
||||
|
||||
#### Progress Tones
|
||||
- **Ascending tones**: 400Hz to 1200Hz frequency range
|
||||
- **Percentage mapping**: 0% = 400Hz, 100% = 1200Hz
|
||||
- **Smooth progression**: Frequency increases proportionally with progress
|
||||
|
||||
#### Activity Indicators
|
||||
- **Steady beep**: 800Hz tone every 2 seconds for ongoing activity
|
||||
- **Non-intrusive**: Beeps don't interrupt speech or other audio
|
||||
|
||||
### Progress Monitoring Features
|
||||
|
||||
- **Automatic detection**: No manual configuration required
|
||||
- **Multiple format support**: Handles various progress indicator styles
|
||||
- **Prompt awareness**: Automatically pauses when command prompts are detected
|
||||
- **Non-blocking**: Progress tones don't interrupt speech or other functionality
|
||||
- **Configurable**: Can be enabled/disabled as needed
|
||||
|
||||
### Usage Examples
|
||||
|
||||
```bash
|
||||
# Enable progress monitoring
|
||||
echo "command progress_bar_monitor" | socat - UNIX-CLIENT:/tmp/fenrirscreenreader-deamon.sock
|
||||
|
||||
# Common scenarios where progress monitoring is useful:
|
||||
wget https://example.com/large-file.zip # Download progress
|
||||
tar -xvf archive.tar.gz # Extraction progress
|
||||
make -j4 # Compilation progress
|
||||
pacman -S package # Package installation
|
||||
rsync -av source/ destination/ # File synchronization
|
||||
```
|
||||
|
||||
### Customization
|
||||
|
||||
Progress monitoring can be configured through settings:
|
||||
- **Default enabled**: Set `progressMonitoring=True` in sound section
|
||||
- **Sound integration**: Works with all sound drivers (sox, gstreamer)
|
||||
- **Remote control**: Enable/disable through remote commands
|
||||
|
||||
### Scripting Examples
|
||||
|
||||
#### Bash Script for Speech Notifications
|
||||
|
@ -50,7 +50,7 @@ def check_dependency(dep: Dependency) -> bool:
|
||||
dependencyList = [
|
||||
# Core dependencies
|
||||
Dependency('FenrirCore', 'core', 'core',
|
||||
pythonImports=['daemonize', 'enchant', 'pyperclip', 'setproctitle']),
|
||||
pythonImports=['daemonize', 'enchant']),
|
||||
|
||||
# Screen drivers
|
||||
Dependency('DummyScreen', 'screen', 'dummyDriver'),
|
||||
@ -58,7 +58,7 @@ dependencyList = [
|
||||
pythonImports=['dbus'],
|
||||
devicePaths=['/dev/vcsa']),
|
||||
Dependency('PTY', 'screen', 'ptyDriver',
|
||||
pythonImports=['pyte', 'xdg']),
|
||||
pythonImports=['pyte']),
|
||||
|
||||
# Input drivers
|
||||
Dependency('DummyInput', 'input', 'dummyDriver'),
|
||||
@ -82,11 +82,7 @@ dependencyList = [
|
||||
Dependency('Speechd', 'speech', 'speechdDriver',
|
||||
pythonImports=['speechd']),
|
||||
Dependency('GenericSpeech', 'speech', 'genericDriver',
|
||||
checkCommands=['espeak-ng']),
|
||||
|
||||
# Additional dependencies
|
||||
Dependency('Pexpect', 'core', 'pexpectDriver',
|
||||
pythonImports=['pexpect'])
|
||||
checkCommands=['espeak-ng'])
|
||||
]
|
||||
|
||||
defaultModules = {
|
||||
@ -94,8 +90,7 @@ defaultModules = {
|
||||
'VCSA',
|
||||
'Evdev',
|
||||
'GenericSpeech',
|
||||
'GenericSound',
|
||||
'Pexpect'
|
||||
'GenericSound'
|
||||
}
|
||||
|
||||
def check_all_dependencies():
|
||||
|
@ -1,46 +1,4 @@
|
||||
|
||||
# Fenrir Keyboard Configuration
|
||||
|
||||
This directory contains keyboard layout files for Fenrir screen reader.
|
||||
|
||||
## Available Layouts
|
||||
|
||||
- **desktop.conf** - Desktop layout using numeric keypad (recommended)
|
||||
- **laptop.conf** - Laptop layout for keyboards without numeric keypad
|
||||
- **nvda-desktop.conf** - NVDA-compatible desktop layout
|
||||
- **nvda-laptop.conf** - NVDA-compatible laptop layout
|
||||
- **pty.conf** - PTY emulation layout for terminal use
|
||||
- **pty2.conf** - Alternative PTY emulation layout
|
||||
|
||||
## Key Features
|
||||
|
||||
### Table Navigation
|
||||
- **Toggle table mode**: `Fenrir + Keypad *` (desktop) or `Fenrir + Y` (laptop)
|
||||
- **Column navigation**: `Keypad 4/6` (desktop) or `Fenrir + J/L` (laptop)
|
||||
- **Row boundaries**: `Fenrir + Keypad 4/6` (desktop) or `Fenrir + Shift + J/L` (laptop)
|
||||
- **Set headers**: `Fenrir + X` in table mode
|
||||
|
||||
### Progress Bar Monitoring
|
||||
- **Monitor progress**: `progress_bar_monitor` command (assign key binding manually)
|
||||
- **Auto-detection**: Percentage, fractions, progress bars, activity indicators
|
||||
- **Audio feedback**: Ascending tones (400Hz-1200Hz) for progress
|
||||
|
||||
### Review Mode
|
||||
- **Basic navigation**: `Keypad 7/8/9` (lines), `Keypad 4/5/6` (words), `Keypad 1/2/3` (characters)
|
||||
- **Exit review**: `Fenrir + Keypad .`
|
||||
- **Screen reading**: `Fenrir + Keypad 5` (current screen)
|
||||
|
||||
## Configuration
|
||||
|
||||
To change keyboard layout, edit `/etc/fenrir/settings/settings.conf`:
|
||||
|
||||
```ini
|
||||
[keyboard]
|
||||
keyboardLayout=desktop # or laptop, nvda-desktop, nvda-laptop, pty, pty2
|
||||
```
|
||||
|
||||
## Available Key Constants
|
||||
|
||||
Keymap for Fenrir
|
||||
KEY_RESERVED
|
||||
KEY_ESC
|
||||
|
47
docs/user.md
47
docs/user.md
@ -50,12 +50,6 @@ Navigate the screen without moving the text cursor. Essential for examining cont
|
||||
- `Keypad 1/3` - Previous/next character
|
||||
- `Fenrir + Keypad dot` - Exit review mode
|
||||
|
||||
### Table Navigation
|
||||
- `Fenrir + Keypad *` - Toggle table mode / highlight tracking
|
||||
- `Keypad 4/6` - Previous/next column (in table mode)
|
||||
- `Fenrir + Keypad 4/6` - First/last column (in table mode)
|
||||
- `Fenrir + X` - Set column headers (in table mode)
|
||||
|
||||
### Information
|
||||
- `Fenrir + T` - Announce time
|
||||
- `Fenrir + T T` - Announce date
|
||||
@ -246,47 +240,6 @@ send_fenrir_command("command say Process complete")
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Table Navigation Mode
|
||||
|
||||
Fenrir includes advanced table navigation capabilities for working with tabular data in terminal applications, CSV files, and formatted text output.
|
||||
|
||||
#### Entering Table Mode
|
||||
1. Press `Fenrir + Keypad *` (desktop) or `Fenrir + Y` (laptop)
|
||||
2. Cycle through: Highlight tracking → Cursor tracking → Table mode
|
||||
3. Listen for "table mode enabled" announcement
|
||||
|
||||
#### Table Navigation Commands
|
||||
- **Column navigation**: `Keypad 4/6` - Move between columns
|
||||
- **Row boundaries**: `Fenrir + Keypad 4/6` - Jump to first/last column
|
||||
- **Cell characters**: `Fenrir + Keypad 1/3` - First/last character in cell
|
||||
- **Set headers**: `Fenrir + X` - Mark current line as column headers
|
||||
|
||||
#### Table Features
|
||||
- **Automatic detection**: Supports CSV, pipe-separated, space-aligned columns
|
||||
- **Column headers**: Set and announce headers for better context
|
||||
- **Boundary feedback**: Audio cues when reaching row boundaries
|
||||
- **Cell-by-cell navigation**: Precise positioning within tables
|
||||
|
||||
### Progress Bar Monitoring
|
||||
|
||||
Fenrir automatically detects and provides audio feedback for progress indicators.
|
||||
|
||||
#### Progress Detection
|
||||
- **Percentage**: 45%, 67.5%, 100%
|
||||
- **Fractions**: 15/100, 3 of 10, Step 7/15
|
||||
- **Progress bars**: [#### ], [====> ]
|
||||
- **Activity indicators**: Loading..., Processing...
|
||||
|
||||
#### Audio Feedback
|
||||
- **Progress tones**: Ascending 400Hz-1200Hz frequency range
|
||||
- **Activity beeps**: 800Hz tone every 2 seconds
|
||||
- **Non-intrusive**: Doesn't interrupt speech or other audio
|
||||
|
||||
#### Usage
|
||||
- **Enable**: Use `progress_bar_monitor` command (assign key binding)
|
||||
- **Automatic**: Works with downloads, compilations, installations
|
||||
- **Remote control**: Enable via socket commands
|
||||
|
||||
### Spell Checking
|
||||
- `Fenrir + S` - Spell check current word
|
||||
- `Fenrir + S S` - Add word to dictionary
|
||||
|
@ -99,13 +99,6 @@ class command:
|
||||
"Progress detector checking: '" + text + "'", debug.DebugLevel.INFO
|
||||
)
|
||||
|
||||
# Filter out URLs to prevent false positives
|
||||
if self.contains_url(text):
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
"Skipping progress detection - text contains URL", debug.DebugLevel.INFO
|
||||
)
|
||||
return
|
||||
|
||||
# Note: Auto-disable on 100% completion removed to respect user
|
||||
# settings
|
||||
|
||||
@ -154,16 +147,8 @@ class command:
|
||||
curl_match = re.search(
|
||||
r"(\d+\s+\d+\s+\d+\s+\d+.*?(?:k|M|G)?.*?--:--:--|Speed)", text
|
||||
)
|
||||
# Pattern 1e: General transfer progress (size, rate, time patterns)
|
||||
transfer_match = re.search(
|
||||
r"\d+\s+\d+[kMGT]?\s+\d+\s+\d+[kMGT]?.*?\d+\.\d+[kMGT].*?\d+:\d+:\d+", text
|
||||
)
|
||||
# Pattern 1f: Pacman-style transfer progress (flexible size/speed/time)
|
||||
pacman_match = re.search(
|
||||
r"\d+(?:\.\d+)?\s+[kKmMgGtT]iB\s+\d+(?:\.\d+)?\s+[kKmMgGtT]iB/s\s+\d+:\d+", text
|
||||
)
|
||||
|
||||
if time_match or token_match or dd_match or curl_match or transfer_match or pacman_match:
|
||||
if time_match or token_match or dd_match or curl_match:
|
||||
# For non-percentage progress, use a single activity beep every 2
|
||||
# seconds
|
||||
if (
|
||||
@ -198,7 +183,7 @@ class command:
|
||||
|
||||
# Pattern 3: Progress bars ([#### ], [====> ], etc.)
|
||||
# Improved pattern to avoid matching IRC channels like [#channel]
|
||||
bar_match = re.search(r"\[([#=\*]+)([\s\.\-]*)\]", text)
|
||||
bar_match = re.search(r"\[([#=\-\*]+)([\s\.]*)\]", text)
|
||||
if bar_match:
|
||||
filled = len(bar_match.group(1))
|
||||
unfilled = len(bar_match.group(2))
|
||||
@ -235,30 +220,6 @@ class command:
|
||||
):
|
||||
self.play_activity_beep()
|
||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||
return
|
||||
|
||||
# Pattern 5: Braille progress indicators
|
||||
braille_match = re.search(r'[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⡿⣟⣯⣷⣾⣽⣻⢿]', text)
|
||||
if braille_match:
|
||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||
self.play_activity_beep()
|
||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||
return
|
||||
|
||||
# Pattern 6: Moon phase progress indicators
|
||||
moon_match = re.search(r'[🌑🌒🌓🌔🌕🌖🌗🌘]', text)
|
||||
if moon_match:
|
||||
moon_phases = {
|
||||
'🌑': 0, '🌒': 12.5, '🌓': 25, '🌔': 37.5,
|
||||
'🌕': 50, '🌖': 62.5, '🌗': 75, '🌘': 87.5
|
||||
}
|
||||
moon_char = moon_match.group(0)
|
||||
if moon_char in moon_phases:
|
||||
percentage = moon_phases[moon_char]
|
||||
if percentage != self.env["commandBuffer"]["lastProgressValue"]:
|
||||
self.play_progress_tone(percentage)
|
||||
self.env["commandBuffer"]["lastProgressValue"] = percentage
|
||||
return
|
||||
|
||||
def play_progress_tone(self, percentage):
|
||||
# Map 0-100% to 400-1200Hz frequency range
|
||||
@ -389,22 +350,5 @@ class command:
|
||||
# If anything fails, assume it's not a prompt to be safe
|
||||
return False
|
||||
|
||||
def contains_url(self, text):
|
||||
"""Check if text contains URLs that might cause false progress detection"""
|
||||
import re
|
||||
|
||||
# Common URL patterns that might contain progress-like patterns
|
||||
url_patterns = [
|
||||
r"https?://[^\s]+", # http:// or https:// URLs
|
||||
r"ftp://[^\s]+", # ftp:// URLs
|
||||
r"www\.[^\s]+", # www. domains
|
||||
r"[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[/\w.-]*", # domain.com/path patterns
|
||||
]
|
||||
|
||||
for pattern in url_patterns:
|
||||
if re.search(pattern, text, re.IGNORECASE):
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_callback(self, callback):
|
||||
pass
|
||||
|
@ -1 +0,0 @@
|
||||
# Emoji VMenu category
|
@ -1 +0,0 @@
|
||||
# Flags emoji subcategory
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🇨🇦"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add Canada flag emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Canada flag to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🇬🇧"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add UK flag emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added UK flag to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🇺🇸"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add USA flag emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added USA flag to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1 +0,0 @@
|
||||
# Food emoji subcategory
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🍺"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Beer emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added beer to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "☕"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add coffee emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added coffee to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🍩"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Donut emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added donut to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🍔"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add hamburger emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added hamburger to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🍕"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add pizza emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added pizza to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🌮"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Taco emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added taco to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1 +0,0 @@
|
||||
# Holidays emoji subcategory
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🦇"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add bat emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added bat to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🐰"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add bunny emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added bunny to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🎄"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add Christmas tree emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Christmas tree to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🥚"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add Easter egg emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Easter egg to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🎆"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add fireworks emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added fireworks to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "👻"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add ghost emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added ghost to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🎁"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add gift emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added gift to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🎃"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add jack o'lantern emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added jack o'lantern to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🎅"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add Santa emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added Santa to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "☘️"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add shamrock emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added shamrock to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "💀"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add skull emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added skull to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "⛄"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add snowman emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added snowman to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🕷"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add spider emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added spider to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🦃"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add turkey emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added turkey to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1 +0,0 @@
|
||||
# Nature emoji subcategory
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🐱"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Cat emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added cat to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🐶"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Dog emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added dog to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🌙"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add moon emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added moon to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🌈"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Rainbow emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added rainbow to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "☀️"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add sun emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added sun to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🌳"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add tree emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added tree to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1 +0,0 @@
|
||||
# People emoji subcategory
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😠"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Angry face emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added angry face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😎"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Cool face emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added cool face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😭"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Crying face emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added crying face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😈"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Devil face emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added devil face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😂"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add laughing face emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added laughing face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "💩"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Poop emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added poop to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😢"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Sad face emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added sad face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😱"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Shocked face emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added shocked face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😊"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add smiling face emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added smiling face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "👍"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add thumbs up emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added thumbs up to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "😉"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add winking face emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added winking face to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1 +0,0 @@
|
||||
# Symbols emoji subcategory
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "✅"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add checkmark emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added checkmark to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🔥"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Fire emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added fire to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "❤️"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add heart emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added heart to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "⚡"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Lightning emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added lightning to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "✌️"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Peace sign emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added peace sign to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "🤘"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Sign of the horns emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added sign of the horns to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "☠️"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Skull and crossbones emoji"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added skull and crossbones to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -1,22 +0,0 @@
|
||||
class command():
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.emoji = "⭐"
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def setCallback(self, callback):
|
||||
pass
|
||||
|
||||
def getDescription(self):
|
||||
return "Add star emoji to clipboard"
|
||||
|
||||
def run(self):
|
||||
self.env["runtime"]["MemoryManager"].add_value_to_first_index(
|
||||
"clipboardHistory", self.emoji
|
||||
)
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
"Added star to clipboard",
|
||||
interrupt=False, flush=False
|
||||
)
|
@ -20,15 +20,22 @@ class command:
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
return _("Test mc search functionality")
|
||||
return _("presents the date")
|
||||
|
||||
def run(self):
|
||||
# Test command for mc search operations
|
||||
test_message = _("MC search test: This demonstrates search functionality")
|
||||
|
||||
# present the test message
|
||||
date_format = self.env["runtime"]["SettingsManager"].get_setting(
|
||||
"general", "date_format"
|
||||
)
|
||||
|
||||
# get the time formatted
|
||||
date_string = datetime.datetime.strftime(
|
||||
datetime.datetime.now(), date_format
|
||||
)
|
||||
|
||||
# present the time via speak and braile, there is no soundicon,
|
||||
# interrupt the current speech
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
test_message, sound_icon="", interrupt=True
|
||||
date_string, sound_icon="", interrupt=True
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
|
@ -20,15 +20,22 @@ class command:
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
return _("Test mutt search functionality")
|
||||
return _("presents the date")
|
||||
|
||||
def run(self):
|
||||
# Test command for mutt search operations
|
||||
test_message = _("Mutt search test: This demonstrates search functionality")
|
||||
|
||||
# present the test message
|
||||
date_format = self.env["runtime"]["SettingsManager"].get_setting(
|
||||
"general", "date_format"
|
||||
)
|
||||
|
||||
# get the time formatted
|
||||
date_string = datetime.datetime.strftime(
|
||||
datetime.datetime.now(), date_format
|
||||
)
|
||||
|
||||
# present the time via speak and braile, there is no soundicon,
|
||||
# interrupt the current speech
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
test_message, sound_icon="", interrupt=True
|
||||
date_string, sound_icon="", interrupt=True
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
|
@ -20,15 +20,22 @@ class command:
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
return _("Test nano search functionality")
|
||||
return _("presents the date")
|
||||
|
||||
def run(self):
|
||||
# Test command for nano search operations
|
||||
test_message = _("Nano search test: This demonstrates search functionality")
|
||||
|
||||
# present the test message
|
||||
date_format = self.env["runtime"]["SettingsManager"].get_setting(
|
||||
"general", "date_format"
|
||||
)
|
||||
|
||||
# get the time formatted
|
||||
date_string = datetime.datetime.strftime(
|
||||
datetime.datetime.now(), date_format
|
||||
)
|
||||
|
||||
# present the time via speak and braile, there is no soundicon,
|
||||
# interrupt the current speech
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
test_message, sound_icon="", interrupt=True
|
||||
date_string, sound_icon="", interrupt=True
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
|
@ -20,15 +20,22 @@ class command:
|
||||
pass
|
||||
|
||||
def get_description(self):
|
||||
return _("Test vim search functionality")
|
||||
return _("presents the date")
|
||||
|
||||
def run(self):
|
||||
# Test command for vim search operations
|
||||
test_message = _("Vim search test: This demonstrates search functionality")
|
||||
|
||||
# present the test message
|
||||
date_format = self.env["runtime"]["SettingsManager"].get_setting(
|
||||
"general", "date_format"
|
||||
)
|
||||
|
||||
# get the time formatted
|
||||
date_string = datetime.datetime.strftime(
|
||||
datetime.datetime.now(), date_format
|
||||
)
|
||||
|
||||
# present the time via speak and braile, there is no soundicon,
|
||||
# interrupt the current speech
|
||||
self.env["runtime"]["OutputManager"].present_text(
|
||||
test_message, sound_icon="", interrupt=True
|
||||
date_string, sound_icon="", interrupt=True
|
||||
)
|
||||
|
||||
def set_callback(self, callback):
|
||||
|
@ -64,7 +64,7 @@ class ProcessManager:
|
||||
args=(event_queue, function, pargs, run_once),
|
||||
)
|
||||
self._Processes.append(t)
|
||||
else: # use thread instead of process
|
||||
else: # thread not implemented yet
|
||||
t = Thread(
|
||||
target=self.custom_event_worker_thread,
|
||||
args=(event_queue, function, pargs, run_once),
|
||||
|
@ -494,14 +494,11 @@ class SettingsManager:
|
||||
self.set_setting("general", "debug_level", 3)
|
||||
self.set_setting("general", "debug_mode", "PRINT")
|
||||
if cliArgs.emulated_pty:
|
||||
# Set PTY driver settings
|
||||
pty_settings = {
|
||||
"screen": {"driver": "ptyDriver"},
|
||||
"keyboard": {"driver": "ptyDriver", "keyboardLayout": "pty"}
|
||||
}
|
||||
for section, settings in pty_settings.items():
|
||||
for key, value in settings.items():
|
||||
self.set_setting(section, key, value)
|
||||
self.set_setting("screen", "driver", "ptyDriver")
|
||||
self.set_setting("keyboard", "driver", "ptyDriver")
|
||||
# TODO needs cleanup use dict
|
||||
# self.set_option_arg_dict('keyboard', 'keyboardLayout', 'pty')
|
||||
self.set_setting("keyboard", "keyboardLayout", "pty")
|
||||
if cliArgs.emulated_evdev:
|
||||
self.set_setting("screen", "driver", "ptyDriver")
|
||||
self.set_setting("keyboard", "driver", "evdevDriver")
|
||||
|
@ -4,6 +4,5 @@
|
||||
# Fenrir TTY screen reader
|
||||
# By Chrys, Storm Dragon, and contributors.
|
||||
|
||||
version = "2025.07.16"
|
||||
codeName = "testing"
|
||||
code_name = "testing"
|
||||
version = "2025.07.09"
|
||||
code_name = "master"
|
||||
|
@ -39,177 +39,7 @@ class driver(inputDriver):
|
||||
|
||||
Args:
|
||||
environment: Fenrir environment dictionary
|
||||
|
||||
Returns:
|
||||
bool: True if initialization successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
if environment is None:
|
||||
raise ValueError("Environment cannot be None")
|
||||
|
||||
self.env = environment
|
||||
|
||||
# Validate required managers are available
|
||||
if "runtime" not in self.env:
|
||||
raise ValueError("Runtime environment missing")
|
||||
if "InputManager" not in self.env["runtime"]:
|
||||
raise ValueError("InputManager not available")
|
||||
|
||||
self.env["runtime"]["InputManager"].set_shortcut_type("BYTE")
|
||||
self._is_initialized = True
|
||||
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
"PTY inputDriver: Initialized with byte-based shortcuts",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# Log error if possible, otherwise fallback to print
|
||||
try:
|
||||
if hasattr(self, 'env') and self.env and "runtime" in self.env:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY inputDriver: Initialization failed: {e}",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
else:
|
||||
print(f"PTY inputDriver initialization error: {e}")
|
||||
except:
|
||||
print(f"PTY inputDriver initialization error: {e}")
|
||||
|
||||
self._is_initialized = False
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
"""Shutdown the PTY input driver.
|
||||
|
||||
Performs cleanup operations when the driver is being stopped.
|
||||
For PTY driver, this involves cleaning up any resources and
|
||||
logging the shutdown.
|
||||
"""
|
||||
if not self._is_initialized:
|
||||
return
|
||||
|
||||
try:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
"PTY inputDriver: Shutting down",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
except Exception as e:
|
||||
# Fallback logging if debug manager is unavailable
|
||||
print(f"PTY inputDriver shutdown error: {e}")
|
||||
finally:
|
||||
self._is_initialized = False
|
||||
|
||||
def get_input_event(self):
|
||||
"""Get input event from PTY.
|
||||
|
||||
For PTY driver, input events are handled through the byte-based
|
||||
shortcut system rather than direct device events. This method
|
||||
returns None as PTY input is processed through the screen driver
|
||||
and InputManager's byte processing.
|
||||
|
||||
Returns:
|
||||
None: PTY driver uses byte-based processing, not event-based
|
||||
"""
|
||||
return None
|
||||
|
||||
def is_device_connected(self):
|
||||
"""Check if PTY input device is connected.
|
||||
|
||||
For PTY driver, the "device" is the terminal interface itself,
|
||||
which is considered connected if the driver is initialized.
|
||||
|
||||
Returns:
|
||||
bool: True if driver is initialized, False otherwise
|
||||
"""
|
||||
return self._is_initialized
|
||||
|
||||
def get_device_name(self):
|
||||
"""Get the name of the PTY input device.
|
||||
|
||||
Returns:
|
||||
str: Human-readable name of the PTY input device
|
||||
"""
|
||||
return "PTY (Pseudo-terminal) Input"
|
||||
|
||||
def grab_devices(self, grab=True):
|
||||
"""Grab or release input devices.
|
||||
|
||||
For PTY driver, device grabbing is not applicable since input
|
||||
is processed through terminal emulation rather than direct
|
||||
device access.
|
||||
|
||||
Args:
|
||||
grab (bool): Whether to grab (True) or release (False) devices
|
||||
|
||||
Returns:
|
||||
bool: Always returns True for PTY driver (no-op success)
|
||||
"""
|
||||
if not self._is_initialized:
|
||||
return False
|
||||
|
||||
action = "grab" if grab else "release"
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY inputDriver: {action} devices (no-op for PTY)",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
return True
|
||||
|
||||
def has_device_detection(self):
|
||||
"""Check if driver supports device detection.
|
||||
|
||||
PTY driver does not support dynamic device detection since
|
||||
it operates on the terminal interface directly.
|
||||
|
||||
Returns:
|
||||
bool: Always False for PTY driver
|
||||
"""
|
||||
return False
|
||||
|
||||
def get_device_list(self):
|
||||
"""Get list of available input devices.
|
||||
|
||||
For PTY driver, there is only one logical device - the terminal
|
||||
interface itself.
|
||||
|
||||
Returns:
|
||||
list: Single-item list containing PTY device info
|
||||
"""
|
||||
if not self._is_initialized:
|
||||
return []
|
||||
|
||||
return [{
|
||||
'name': 'PTY Terminal',
|
||||
'path': '/dev/pts/*',
|
||||
'type': 'terminal',
|
||||
'connected': True
|
||||
}]
|
||||
|
||||
def get_led_state(self, led_mask=None):
|
||||
"""Get LED state information.
|
||||
|
||||
PTY driver cannot access LED states since it operates through
|
||||
terminal emulation rather than direct hardware access.
|
||||
|
||||
Args:
|
||||
led_mask: LED mask parameter (ignored for PTY)
|
||||
|
||||
Returns:
|
||||
dict: Empty dict (no LED access for PTY)
|
||||
"""
|
||||
return {}
|
||||
|
||||
def set_led_state(self, led_dict):
|
||||
"""Set LED states.
|
||||
|
||||
PTY driver cannot control LEDs since it operates through
|
||||
terminal emulation rather than direct hardware access.
|
||||
|
||||
Args:
|
||||
led_dict (dict): LED state dictionary (ignored for PTY)
|
||||
|
||||
Returns:
|
||||
bool: Always False (LED control not supported)
|
||||
"""
|
||||
return False
|
||||
self.env = environment
|
||||
self.env["runtime"]["InputManager"].set_shortcut_type("BYTE")
|
||||
self._is_initialized = True
|
||||
|
@ -13,7 +13,6 @@ import signal
|
||||
import struct
|
||||
import sys
|
||||
import termios
|
||||
import threading
|
||||
import time
|
||||
import tty
|
||||
from select import select
|
||||
@ -25,27 +24,6 @@ from fenrirscreenreader.core.eventData import FenrirEventType
|
||||
from fenrirscreenreader.core.screenDriver import ScreenDriver as screenDriver
|
||||
from fenrirscreenreader.utils import screen_utils
|
||||
|
||||
# PTY Driver Constants
|
||||
class PTYConstants:
|
||||
# Timeouts (in seconds)
|
||||
DEFAULT_READ_TIMEOUT = 0.3
|
||||
INPUT_READ_TIMEOUT = 0.01
|
||||
OUTPUT_READ_TIMEOUT = 0.05 # Faster than default but allows for network lag
|
||||
SELECT_TIMEOUT = 0.05
|
||||
PROCESS_TERMINATION_TIMEOUT = 3.0
|
||||
PROCESS_KILL_DELAY = 0.5
|
||||
|
||||
# Polling intervals (in seconds)
|
||||
MIN_POLL_INTERVAL = 0.001
|
||||
|
||||
# Limits
|
||||
MAX_TERMINAL_LINES = 10000
|
||||
DEFAULT_READ_BUFFER_SIZE = 65536
|
||||
INPUT_BUFFER_SIZE = 4096
|
||||
|
||||
# Error codes
|
||||
IO_ERROR_ERRNO = 5
|
||||
|
||||
|
||||
class FenrirScreen(pyte.Screen):
|
||||
def set_margins(self, *args, **kwargs):
|
||||
@ -54,39 +32,16 @@ class FenrirScreen(pyte.Screen):
|
||||
|
||||
|
||||
class Terminal:
|
||||
def __init__(self, columns, lines, p_in, env=None):
|
||||
def __init__(self, columns, lines, p_in):
|
||||
self.text = ""
|
||||
self.attributes = None
|
||||
self.screen = FenrirScreen(columns, lines)
|
||||
self.env = env # Environment for proper logging
|
||||
|
||||
# Pre-create default attribute template to avoid repeated allocation
|
||||
self._default_attribute = [
|
||||
"default", "default", False, False, False, False, False, False,
|
||||
"default", "default"
|
||||
]
|
||||
self.screen.write_process_input = lambda data: p_in.write(
|
||||
data.encode()
|
||||
)
|
||||
self.stream = pyte.ByteStream()
|
||||
self.stream.attach(self.screen)
|
||||
|
||||
def _log_error(self, message, level=None):
|
||||
"""Log error message using proper debug manager if available."""
|
||||
if self.env and "runtime" in self.env and "DebugManager" in self.env["runtime"]:
|
||||
try:
|
||||
log_level = level if level else debug.DebugLevel.ERROR
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY Terminal: {message}",
|
||||
log_level
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
pass # Fallback to print if debug manager fails
|
||||
|
||||
# Fallback logging when debug manager unavailable
|
||||
print(f"PTY Terminal: {message}")
|
||||
|
||||
def feed(self, data):
|
||||
self.stream.feed(data)
|
||||
|
||||
@ -97,58 +52,45 @@ class Terminal:
|
||||
lines = self.screen.dirty
|
||||
else:
|
||||
lines = range(self.screen.lines)
|
||||
try:
|
||||
self.attributes = [
|
||||
[
|
||||
list(attribute[1:]) + [False, "default", "default"]
|
||||
if len(attribute) > 1 else [False, "default", "default"]
|
||||
for attribute in line.values()
|
||||
]
|
||||
for line in buffer.values()
|
||||
]
|
||||
except Exception as e:
|
||||
self._log_error(f"Error initializing attributes: {e}")
|
||||
# Fallback to empty attributes
|
||||
self.attributes = [[] for _ in range(self.screen.lines)]
|
||||
for y in lines:
|
||||
# Validate y is within reasonable bounds (prevent memory exhaustion)
|
||||
if y >= PTYConstants.MAX_TERMINAL_LINES:
|
||||
self._log_error(
|
||||
f"Line index {y} exceeds maximum {PTYConstants.MAX_TERMINAL_LINES}, "
|
||||
f"skipping attribute update",
|
||||
debug.DebugLevel.WARNING
|
||||
)
|
||||
continue
|
||||
|
||||
# Check if line y exists in buffer before accessing it
|
||||
if y not in buffer:
|
||||
# Only log this occasionally to prevent spam
|
||||
if y % 10 == 0: # Log every 10th missing line
|
||||
# Pre-format string to avoid repeated f-string operations
|
||||
line_range = f"{y}-{y+9}"
|
||||
self._log_error(
|
||||
f"Lines {line_range} not found in buffer, skipping attribute updates",
|
||||
debug.DebugLevel.WARNING
|
||||
)
|
||||
continue
|
||||
|
||||
# Ensure attributes array is large enough for line y
|
||||
while len(self.attributes) <= y:
|
||||
self.attributes.append([])
|
||||
|
||||
try:
|
||||
self.attributes[y] = [
|
||||
self.attributes = [
|
||||
[
|
||||
list(attribute[1:]) + [False, "default", "default"]
|
||||
for attribute in (buffer[y].values())
|
||||
for attribute in line.values()
|
||||
]
|
||||
for line in buffer.values()
|
||||
]
|
||||
for y in lines:
|
||||
try:
|
||||
t = self.attributes[y]
|
||||
except Exception as e:
|
||||
self._log_error(f"Error updating attributes for line {y}: {e}")
|
||||
# Initialize with empty attributes if update fails
|
||||
self.attributes[y] = []
|
||||
# Terminal class doesn't have access to env, use fallback
|
||||
# logging
|
||||
print(
|
||||
f"ptyDriver Terminal update_attributes: Error accessing "
|
||||
f"attributes: {e}"
|
||||
)
|
||||
self.attributes.append([])
|
||||
|
||||
self.attributes[y] = [
|
||||
list(attribute[1:]) + [False, "default", "default"]
|
||||
for attribute in (buffer[y].values())
|
||||
]
|
||||
if len(self.attributes[y]) < self.screen.columns:
|
||||
diff = self.screen.columns - len(self.attributes[y])
|
||||
# Use pre-created template for efficiency
|
||||
self.attributes[y] += [self._default_attribute[:] for _ in range(diff)]
|
||||
self.attributes[y] += [
|
||||
[
|
||||
"default",
|
||||
"default",
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
"default",
|
||||
"default",
|
||||
]
|
||||
] * diff
|
||||
|
||||
def resize(self, lines, columns):
|
||||
self.screen.resize(lines, columns)
|
||||
@ -156,37 +98,31 @@ class Terminal:
|
||||
self.update_attributes(True)
|
||||
|
||||
def set_cursor(self, x=-1, y=-1):
|
||||
# Determine target cursor position
|
||||
x_pos = x if x != -1 else self.screen.cursor.x
|
||||
y_pos = y if y != -1 else self.screen.cursor.y
|
||||
|
||||
# Validate and clamp cursor position to screen bounds
|
||||
max_x = max(0, self.screen.columns - 1)
|
||||
max_y = max(0, self.screen.lines - 1)
|
||||
|
||||
self.screen.cursor.x = max(0, min(x_pos, max_x))
|
||||
self.screen.cursor.y = max(0, min(y_pos, max_y))
|
||||
x_pos = x
|
||||
y_pos = y
|
||||
if x_pos == -1:
|
||||
x_pos = self.screen.cursor.x
|
||||
if y_pos == -1:
|
||||
y_pos = self.screen.cursor.y
|
||||
self.screen.cursor.x = min(
|
||||
self.screen.cursor.x, self.screen.columns - 1
|
||||
)
|
||||
self.screen.cursor.y = min(self.screen.cursor.y, self.screen.lines - 1)
|
||||
|
||||
def get_screen_content(self):
|
||||
cursor = self.screen.cursor
|
||||
# Only regenerate text if screen is dirty or text doesn't exist
|
||||
if not hasattr(self, 'text') or self.screen.dirty:
|
||||
self.text = "\n".join(self.screen.display)
|
||||
self.text = "\n".join(self.screen.display)
|
||||
self.update_attributes(self.attributes is None)
|
||||
self.screen.dirty.clear()
|
||||
|
||||
# Return screen content without unnecessary copying
|
||||
# Only copy attributes if they exist and need protection
|
||||
screen_data = {
|
||||
return {
|
||||
"cursor": (cursor.x, cursor.y),
|
||||
"lines": self.screen.lines,
|
||||
"columns": self.screen.columns,
|
||||
"text": self.text,
|
||||
"attributes": self.attributes[:] if self.attributes else [], # Shallow copy only if needed
|
||||
"attributes": self.attributes.copy(),
|
||||
"screen": "pty",
|
||||
"screenUpdateTime": time.time(),
|
||||
}
|
||||
return screen_data
|
||||
}.copy()
|
||||
|
||||
|
||||
class driver(screenDriver):
|
||||
@ -196,64 +132,13 @@ class driver(screenDriver):
|
||||
self.p_out = None
|
||||
self.terminal = None
|
||||
self.p_pid = -1
|
||||
self.terminal_lock = threading.Lock() # Synchronize terminal operations
|
||||
signal.signal(signal.SIGWINCH, self.handle_sigwinch)
|
||||
|
||||
# Runtime configuration storage
|
||||
self.pty_config = {}
|
||||
|
||||
def _load_pty_settings(self):
|
||||
"""Load PTY-specific settings from configuration with fallbacks to defaults."""
|
||||
try:
|
||||
settings_manager = self.env["runtime"]["SettingsManager"]
|
||||
|
||||
# Load timeout settings with defaults
|
||||
self.pty_config = {
|
||||
'input_timeout': float(settings_manager.get_setting(
|
||||
'screen', 'ptyInputTimeout', PTYConstants.INPUT_READ_TIMEOUT
|
||||
)),
|
||||
'output_timeout': float(settings_manager.get_setting(
|
||||
'screen', 'ptyOutputTimeout', PTYConstants.OUTPUT_READ_TIMEOUT
|
||||
)),
|
||||
'select_timeout': float(settings_manager.get_setting(
|
||||
'screen', 'ptySelectTimeout', PTYConstants.SELECT_TIMEOUT
|
||||
)),
|
||||
'process_termination_timeout': float(settings_manager.get_setting(
|
||||
'screen', 'ptyProcessTimeout', PTYConstants.PROCESS_TERMINATION_TIMEOUT
|
||||
)),
|
||||
'poll_interval': float(settings_manager.get_setting(
|
||||
'screen', 'ptyPollInterval', PTYConstants.MIN_POLL_INTERVAL
|
||||
))
|
||||
}
|
||||
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Loaded configuration: {self.pty_config}",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Fallback to constants if settings fail
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Failed to load settings, using defaults: {e}",
|
||||
debug.DebugLevel.WARNING
|
||||
)
|
||||
self.pty_config = {
|
||||
'input_timeout': PTYConstants.INPUT_READ_TIMEOUT,
|
||||
'output_timeout': PTYConstants.OUTPUT_READ_TIMEOUT,
|
||||
'select_timeout': PTYConstants.SELECT_TIMEOUT,
|
||||
'process_termination_timeout': PTYConstants.PROCESS_TERMINATION_TIMEOUT,
|
||||
'poll_interval': PTYConstants.MIN_POLL_INTERVAL
|
||||
}
|
||||
|
||||
def initialize(self, environment):
|
||||
self.env = environment
|
||||
self.command = self.env["runtime"]["SettingsManager"].get_setting(
|
||||
"general", "shell"
|
||||
)
|
||||
|
||||
# Load configurable timeouts from settings
|
||||
self._load_pty_settings()
|
||||
|
||||
self.shortcutType = self.env["runtime"][
|
||||
"InputManager"
|
||||
].get_shortcut_type()
|
||||
@ -277,57 +162,29 @@ class driver(screenDriver):
|
||||
self.env["general"]["prev_user"] = getpass.getuser()
|
||||
self.env["general"]["curr_user"] = getpass.getuser()
|
||||
|
||||
def read_all(self, fd, timeout=PTYConstants.DEFAULT_READ_TIMEOUT, interruptFd=None, len=PTYConstants.DEFAULT_READ_BUFFER_SIZE):
|
||||
"""Read all available data from file descriptor with efficient polling.
|
||||
|
||||
Uses progressively longer wait times to balance responsiveness with CPU usage.
|
||||
"""
|
||||
def read_all(self, fd, timeout=0.3, interruptFd=None, len=65536):
|
||||
msg_bytes = b""
|
||||
fd_list = [fd]
|
||||
fd_list = []
|
||||
fd_list += [fd]
|
||||
if interruptFd:
|
||||
fd_list.append(interruptFd)
|
||||
|
||||
fd_list += [interruptFd]
|
||||
starttime = time.time()
|
||||
poll_timeout = self.pty_config.get('poll_interval', PTYConstants.MIN_POLL_INTERVAL) # Use configured interval
|
||||
|
||||
while True:
|
||||
# Use consistent short polling for responsiveness
|
||||
r = screen_utils.has_more_what(fd_list, poll_timeout)
|
||||
|
||||
# Nothing more to read
|
||||
r = screen_utils.has_more_what(fd_list, 0.0001)
|
||||
# nothing more to read
|
||||
if fd not in r:
|
||||
# Check overall timeout
|
||||
if (time.time() - starttime) >= timeout:
|
||||
break
|
||||
continue
|
||||
|
||||
try:
|
||||
data = os.read(fd, len)
|
||||
if data == b"":
|
||||
raise EOFError
|
||||
msg_bytes += data
|
||||
except OSError as e:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver read_all: OS error reading from fd {fd}: {e}",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
# For I/O errors, exit immediately to prevent endless retry loops
|
||||
if e.errno == PTYConstants.IO_ERROR_ERRNO: # Input/output error
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
"PTY screenDriver: Terminal connection lost, stopping read loop",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
raise EOFError("Terminal connection lost")
|
||||
break
|
||||
|
||||
# Exit on interrupt available
|
||||
if interruptFd and interruptFd in r:
|
||||
data = os.read(fd, len)
|
||||
if data == b"":
|
||||
raise EOFError
|
||||
msg_bytes += data
|
||||
# exit on interrupt available
|
||||
if interruptFd in r:
|
||||
break
|
||||
|
||||
# Check overall timeout
|
||||
# respect timeout but wait a little bit of time to see if something
|
||||
# more is here
|
||||
if (time.time() - starttime) >= timeout:
|
||||
break
|
||||
|
||||
return msg_bytes
|
||||
|
||||
def open_terminal(self, columns, lines, command):
|
||||
@ -340,16 +197,16 @@ class driver(screenDriver):
|
||||
if env["TERM"] == "":
|
||||
env["TERM"] = "linux"
|
||||
except Exception as e:
|
||||
# Child process doesn't have access to debug manager
|
||||
# Use fallback logging with more context
|
||||
# Child process doesn't have access to env, use fallback
|
||||
# logging
|
||||
print(
|
||||
f"ptyDriver open_terminal (child): TERM environment error: {e}"
|
||||
f"ptyDriver spawnTerminal: Error checking TERM environment: {e}"
|
||||
)
|
||||
env["TERM"] = "linux"
|
||||
os.execvpe(argv[0], argv, env)
|
||||
# File-like object for I/O with the child process aka command.
|
||||
p_out = os.fdopen(master_fd, "w+b", 0)
|
||||
return Terminal(columns, lines, p_out, self.env), p_pid, p_out
|
||||
return Terminal(columns, lines, p_out), p_pid, p_out
|
||||
|
||||
def resize_terminal(self, fd):
|
||||
s = struct.pack("HHHH", 0, 0, 0, 0)
|
||||
@ -382,7 +239,7 @@ class driver(screenDriver):
|
||||
self.terminal.resize(lines, columns)
|
||||
fd_list = [sys.stdin, self.p_out, self.signalPipe[0]]
|
||||
while active.value:
|
||||
r, _, _ = select(fd_list, [], [], self.pty_config.get('select_timeout', PTYConstants.SELECT_TIMEOUT)) # Configurable timeout
|
||||
r, _, _ = select(fd_list, [], [], 1)
|
||||
# none
|
||||
if r == []:
|
||||
continue
|
||||
@ -394,7 +251,7 @@ class driver(screenDriver):
|
||||
# input
|
||||
if sys.stdin in r:
|
||||
try:
|
||||
msg_bytes = self.read_all(sys.stdin.fileno(), timeout=self.pty_config.get('input_timeout', PTYConstants.INPUT_READ_TIMEOUT), len=PTYConstants.INPUT_BUFFER_SIZE)
|
||||
msg_bytes = self.read_all(sys.stdin.fileno(), len=4096)
|
||||
except (EOFError, OSError):
|
||||
event_queue.put(
|
||||
{
|
||||
@ -432,9 +289,7 @@ class driver(screenDriver):
|
||||
if self.p_out in r:
|
||||
try:
|
||||
msg_bytes = self.read_all(
|
||||
self.p_out.fileno(),
|
||||
timeout=self.pty_config.get('output_timeout', PTYConstants.OUTPUT_READ_TIMEOUT),
|
||||
interruptFd=sys.stdin.fileno()
|
||||
self.p_out.fileno(), interruptFd=sys.stdin.fileno()
|
||||
)
|
||||
except (EOFError, OSError):
|
||||
event_queue.put(
|
||||
@ -444,121 +299,34 @@ class driver(screenDriver):
|
||||
}
|
||||
)
|
||||
break
|
||||
# Synchronize terminal operations to prevent race conditions
|
||||
with self.terminal_lock:
|
||||
# Feed data to terminal and get consistent screen state
|
||||
self.terminal.feed(msg_bytes)
|
||||
screen_content = self.terminal.get_screen_content()
|
||||
|
||||
# Send screen update event with consistent state
|
||||
event_queue.put(
|
||||
{
|
||||
"Type": FenrirEventType.screen_update,
|
||||
"data": screen_utils.create_screen_event_data(
|
||||
screen_content
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
# Inject to actual screen (outside lock to avoid blocking)
|
||||
# feed and send event bevore write, the pyte already has the right state
|
||||
# so fenrir already can progress bevore os.write what
|
||||
# should give some better reaction time
|
||||
self.terminal.feed(msg_bytes)
|
||||
event_queue.put(
|
||||
{
|
||||
"Type": FenrirEventType.screen_update,
|
||||
"data": screen_utils.create_screen_event_data(
|
||||
self.terminal.get_screen_content()
|
||||
),
|
||||
}
|
||||
)
|
||||
self.inject_text_to_screen(
|
||||
msg_bytes, screen=sys.stdout.fileno()
|
||||
)
|
||||
except Exception as e: # Process died?
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver terminal_emulation: Exception occurred: {e}",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
print(e)
|
||||
event_queue.put(
|
||||
{"Type": FenrirEventType.stop_main_loop, "data": None}
|
||||
)
|
||||
finally:
|
||||
self._safe_cleanup_process()
|
||||
self._safe_cleanup_resources(old_attr)
|
||||
event_queue.put(
|
||||
{"Type": FenrirEventType.stop_main_loop, "data": None}
|
||||
)
|
||||
|
||||
def _safe_cleanup_process(self):
|
||||
"""Safely terminate the child process with timeout and fallback to SIGKILL."""
|
||||
if not hasattr(self, 'p_pid') or self.p_pid is None:
|
||||
return
|
||||
|
||||
try:
|
||||
# Check if process is still alive
|
||||
os.kill(self.p_pid, 0) # Signal 0 checks if process exists
|
||||
except OSError:
|
||||
# Process already dead
|
||||
self.p_pid = None
|
||||
return
|
||||
|
||||
try:
|
||||
# Try graceful termination first
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Terminating process {self.p_pid} gracefully",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
os.kill(self.p_pid, signal.SIGTERM)
|
||||
|
||||
# Wait for graceful termination
|
||||
timeout = self.pty_config.get('process_termination_timeout', PTYConstants.PROCESS_TERMINATION_TIMEOUT)
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
os.kill(self.p_pid, 0) # Check if still alive
|
||||
time.sleep(0.1)
|
||||
except OSError:
|
||||
# Process terminated gracefully
|
||||
self.p_pid = None
|
||||
return
|
||||
|
||||
# Process didn't terminate gracefully, use SIGKILL
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Process {self.p_pid} didn't terminate gracefully, using SIGKILL",
|
||||
debug.DebugLevel.WARNING
|
||||
self.p_out.close()
|
||||
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attr)
|
||||
event_queue.put(
|
||||
{"Type": FenrirEventType.stop_main_loop, "data": None}
|
||||
)
|
||||
os.kill(self.p_pid, signal.SIGKILL)
|
||||
time.sleep(PTYConstants.PROCESS_KILL_DELAY) # Give it a moment
|
||||
|
||||
except OSError as e:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Error terminating process {self.p_pid}: {e}",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
finally:
|
||||
self.p_pid = None
|
||||
|
||||
def _safe_cleanup_resources(self, old_attr=None):
|
||||
"""Safely clean up file descriptors and terminal attributes."""
|
||||
# Close output pipe safely
|
||||
if hasattr(self, 'p_out') and self.p_out is not None:
|
||||
try:
|
||||
self.p_out.close()
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
"PTY screenDriver: Closed output pipe",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
except Exception as e:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Error closing output pipe: {e}",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
finally:
|
||||
self.p_out = None
|
||||
|
||||
# Restore terminal attributes safely
|
||||
if old_attr is not None:
|
||||
try:
|
||||
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attr)
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
"PTY screenDriver: Restored terminal attributes",
|
||||
debug.DebugLevel.INFO
|
||||
)
|
||||
except Exception as e:
|
||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||
f"PTY screenDriver: Error restoring terminal attributes: {e}",
|
||||
debug.DebugLevel.ERROR
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
def get_curr_application(self):
|
||||
pass
|
||||
|
@ -170,9 +170,8 @@ class driver(screenDriver):
|
||||
if screen is not None:
|
||||
use_screen = screen
|
||||
with open(use_screen, "w") as fd:
|
||||
text_bytes = text.encode('utf-8')
|
||||
for byte in text_bytes:
|
||||
fcntl.ioctl(fd, termios.TIOCSTI, bytes([byte]))
|
||||
for c in text:
|
||||
fcntl.ioctl(fd, termios.TIOCSTI, c)
|
||||
|
||||
def get_session_information(self):
|
||||
"""Retrieve session information via D-Bus logind interface.
|
||||
|
Reference in New Issue
Block a user