Compare commits
5 Commits
5ff653bd00
...
7041d2567a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7041d2567a | |||
| 2c38bcf5f4 | |||
| 96cdda99c4 | |||
| 0658d37ae8 | |||
| a6bb3e1301 |
@@ -4,7 +4,7 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
import time
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
@@ -14,55 +14,150 @@ class command:
|
|||||||
|
|
||||||
def initialize(self, environment):
|
def initialize(self, environment):
|
||||||
self.env = environment
|
self.env = environment
|
||||||
|
# Initialize tab completion state tracking
|
||||||
|
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||||
|
self.env["commandBuffer"]["tabCompletion"] = {
|
||||||
|
"lastTabTime": 0,
|
||||||
|
"pendingCompletion": None,
|
||||||
|
"retryCount": 0
|
||||||
|
}
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return "No Description found"
|
return _("Announces tab completions when detected")
|
||||||
|
|
||||||
|
def _is_recent_tab_input(self):
|
||||||
|
"""Check if TAB was pressed recently (within 200ms window)"""
|
||||||
|
current_time = time.time()
|
||||||
|
tab_detected = False
|
||||||
|
|
||||||
|
# Check KEY mode
|
||||||
|
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
||||||
|
if (self.env["runtime"]["InputManager"].get_last_deepest_input()
|
||||||
|
in [["KEY_TAB"]]):
|
||||||
|
tab_detected = True
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
||||||
|
|
||||||
|
# Check BYTE mode
|
||||||
|
elif self.env["runtime"]["InputManager"].get_shortcut_type() in ["BYTE"]:
|
||||||
|
for currByte in self.env["runtime"]["ByteManager"].get_last_byte_key():
|
||||||
|
if currByte == 9: # Tab character
|
||||||
|
tab_detected = True
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["lastTabTime"] = current_time
|
||||||
|
|
||||||
|
# Check if tab was pressed recently (200ms window)
|
||||||
|
if not tab_detected:
|
||||||
|
time_since_tab = current_time - self.env["commandBuffer"]["tabCompletion"]["lastTabTime"]
|
||||||
|
if time_since_tab <= 0.2: # 200ms window
|
||||||
|
tab_detected = True
|
||||||
|
|
||||||
|
return tab_detected
|
||||||
|
|
||||||
|
def _is_flexible_completion_match(self, x_move, delta_text):
|
||||||
|
"""Use flexible matching instead of strict equality"""
|
||||||
|
if not delta_text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
delta_len = len(delta_text)
|
||||||
|
|
||||||
|
# Exact match (preserve original behavior)
|
||||||
|
if x_move == delta_len:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Flexible range: allow ±2 characters difference
|
||||||
|
# Handles spacing adjustments and unicode width variations
|
||||||
|
if abs(x_move - delta_len) <= 2 and delta_len > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# For longer completions, allow proportional variance
|
||||||
|
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _detect_completion_patterns(self, delta_text):
|
||||||
|
"""Detect common tab completion patterns for improved accuracy"""
|
||||||
|
if not delta_text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
delta_stripped = delta_text.strip()
|
||||||
|
|
||||||
|
# File extension completion
|
||||||
|
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Path completion (contains / or \)
|
||||||
|
if '/' in delta_stripped or '\\' in delta_stripped:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Command parameter completion (starts with -)
|
||||||
|
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Word boundary completion (alphanumeric content)
|
||||||
|
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# try to detect the tab completion by cursor change
|
"""Enhanced tab completion detection with improved reliability"""
|
||||||
|
# Basic cursor movement check (preserve original logic)
|
||||||
x_move = (
|
x_move = (
|
||||||
self.env["screen"]["new_cursor"]["x"]
|
self.env["screen"]["new_cursor"]["x"]
|
||||||
- self.env["screen"]["old_cursor"]["x"]
|
- self.env["screen"]["old_cursor"]["x"]
|
||||||
)
|
)
|
||||||
if x_move <= 0:
|
if x_move <= 0:
|
||||||
return
|
return
|
||||||
if self.env["runtime"]["InputManager"].get_shortcut_type() in ["KEY"]:
|
|
||||||
if not (
|
# Enhanced tab input detection with persistence
|
||||||
self.env["runtime"]["InputManager"].get_last_deepest_input()
|
tab_detected = self._is_recent_tab_input()
|
||||||
in [["KEY_TAB"]]
|
|
||||||
):
|
# Fallback for non-tab movements (preserve original thresholds)
|
||||||
if x_move < 5:
|
if not tab_detected:
|
||||||
return
|
if x_move < 5:
|
||||||
elif self.env["runtime"]["InputManager"].get_shortcut_type() in [
|
return
|
||||||
"BYTE"
|
|
||||||
]:
|
# Screen delta availability check
|
||||||
found = False
|
|
||||||
for currByte in self.env["runtime"][
|
|
||||||
"ByteManager"
|
|
||||||
].get_last_byte_key():
|
|
||||||
if currByte == 9:
|
|
||||||
found = True
|
|
||||||
if not found:
|
|
||||||
if x_move < 5:
|
|
||||||
return
|
|
||||||
# is there any change?
|
|
||||||
if not self.env["runtime"]["ScreenManager"].is_delta():
|
if not self.env["runtime"]["ScreenManager"].is_delta():
|
||||||
|
# If tab was detected but no delta yet, store for potential retry
|
||||||
|
if tab_detected and self.env["commandBuffer"]["tabCompletion"]["retryCount"] < 2:
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = {
|
||||||
|
"x_move": x_move,
|
||||||
|
"timestamp": time.time()
|
||||||
|
}
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["retryCount"] += 1
|
||||||
return
|
return
|
||||||
if not x_move == len(self.env["screen"]["new_delta"]):
|
|
||||||
return
|
delta_text = self.env["screen"]["new_delta"]
|
||||||
# filter unneded space on word begin
|
|
||||||
curr_delta = self.env["screen"]["new_delta"]
|
# Enhanced correlation checking with flexible matching
|
||||||
if (
|
if not self._is_flexible_completion_match(x_move, delta_text):
|
||||||
len(curr_delta.strip()) != len(curr_delta)
|
# Additional pattern-based validation for edge cases
|
||||||
and curr_delta.strip() != ""
|
if not (tab_detected and self._detect_completion_patterns(delta_text)):
|
||||||
):
|
return
|
||||||
|
|
||||||
|
# Reset retry counter on successful detection
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["retryCount"] = 0
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["pendingCompletion"] = None
|
||||||
|
|
||||||
|
# Mark that we've handled this delta to prevent duplicate announcements
|
||||||
|
# This prevents the incoming text handler from also announcing the same content
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["lastProcessedDelta"] = delta_text
|
||||||
|
self.env["commandBuffer"]["tabCompletion"]["lastProcessedTime"] = time.time()
|
||||||
|
|
||||||
|
# Text filtering and announcement (preserve original behavior)
|
||||||
|
curr_delta = delta_text
|
||||||
|
if (len(curr_delta.strip()) != len(curr_delta) and curr_delta.strip() != ""):
|
||||||
curr_delta = curr_delta.strip()
|
curr_delta = curr_delta.strip()
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
|
||||||
curr_delta, interrupt=True, announce_capital=True, flush=False
|
# Enhanced announcement with better handling of empty completions
|
||||||
)
|
if curr_delta:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
curr_delta, interrupt=True, announce_capital=True, flush=False
|
||||||
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Fenrir TTY screen reader
|
||||||
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
import time
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
class command:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def initialize(self, environment):
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return _("Handles delayed retry for tab completion detection")
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Check for and process pending tab completions with slight delay"""
|
||||||
|
# Only process if we have tab completion state
|
||||||
|
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
tab_state = self.env["commandBuffer"]["tabCompletion"]
|
||||||
|
pending = tab_state.get("pendingCompletion")
|
||||||
|
|
||||||
|
if not pending:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# Process pending completion after 50ms delay
|
||||||
|
if current_time - pending["timestamp"] < 0.05:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if screen delta is now available
|
||||||
|
if not self.env["runtime"]["ScreenManager"].is_delta():
|
||||||
|
# Give up after 200ms total
|
||||||
|
if current_time - pending["timestamp"] > 0.2:
|
||||||
|
tab_state["pendingCompletion"] = None
|
||||||
|
tab_state["retryCount"] = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
# Process the delayed completion
|
||||||
|
delta_text = self.env["screen"]["new_delta"]
|
||||||
|
x_move = pending["x_move"]
|
||||||
|
|
||||||
|
# Use the same flexible matching logic as main tab completion
|
||||||
|
match_found = self._is_flexible_completion_match(x_move, delta_text)
|
||||||
|
|
||||||
|
if not match_found:
|
||||||
|
# Try pattern-based detection as final fallback
|
||||||
|
match_found = self._detect_completion_patterns(delta_text)
|
||||||
|
|
||||||
|
if match_found and delta_text:
|
||||||
|
# Mark that we've handled this delta to prevent duplicate announcements
|
||||||
|
tab_state["lastProcessedDelta"] = delta_text
|
||||||
|
tab_state["lastProcessedTime"] = current_time
|
||||||
|
|
||||||
|
# Filter and announce the completion
|
||||||
|
curr_delta = delta_text
|
||||||
|
if (len(curr_delta.strip()) != len(curr_delta) and
|
||||||
|
curr_delta.strip() != ""):
|
||||||
|
curr_delta = curr_delta.strip()
|
||||||
|
|
||||||
|
if curr_delta:
|
||||||
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
curr_delta, interrupt=True, announce_capital=True, flush=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear pending completion
|
||||||
|
tab_state["pendingCompletion"] = None
|
||||||
|
tab_state["retryCount"] = 0
|
||||||
|
|
||||||
|
def _is_flexible_completion_match(self, x_move, delta_text):
|
||||||
|
"""Use flexible matching (duplicated from main command for heartbeat use)"""
|
||||||
|
if not delta_text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
delta_len = len(delta_text)
|
||||||
|
|
||||||
|
# Exact match
|
||||||
|
if x_move == delta_len:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Flexible range: allow ±2 characters difference
|
||||||
|
if abs(x_move - delta_len) <= 2 and delta_len > 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# For longer completions, allow proportional variance
|
||||||
|
if delta_len > 10 and abs(x_move - delta_len) <= (delta_len * 0.2):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _detect_completion_patterns(self, delta_text):
|
||||||
|
"""Detect common tab completion patterns (duplicated from main command)"""
|
||||||
|
if not delta_text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
delta_stripped = delta_text.strip()
|
||||||
|
|
||||||
|
# File extension completion
|
||||||
|
if '.' in delta_stripped and delta_stripped.count('.') <= 2:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Path completion
|
||||||
|
if '/' in delta_stripped or '\\' in delta_stripped:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Command parameter completion
|
||||||
|
if delta_stripped.startswith('-') and len(delta_stripped) > 1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Word boundary completion
|
||||||
|
if delta_stripped.isalnum() and len(delta_stripped) >= 2:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def set_callback(self, callback):
|
||||||
|
pass
|
||||||
@@ -26,9 +26,20 @@ class command:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Only announce numlock changes if an actual numlock key was pressed
|
# Only announce numlock changes if an actual numlock key was pressed
|
||||||
# This prevents spurious announcements from external numpad automatic state changes
|
# AND the LED state actually changed (some numpads send spurious NUMLOCK events)
|
||||||
current_input = self.env["input"]["currInput"]
|
current_input = self.env["input"]["currInput"]
|
||||||
if current_input and "KEY_NUMLOCK" in current_input:
|
|
||||||
|
# Check if this is a genuine numlock key press by verifying:
|
||||||
|
# 1. KEY_NUMLOCK is in the current input sequence
|
||||||
|
# 2. The LED state has actually changed
|
||||||
|
# 3. This isn't just a side effect from a KP_ key (which some buggy numpads do)
|
||||||
|
is_genuine_numlock = (
|
||||||
|
current_input and
|
||||||
|
"KEY_NUMLOCK" in current_input and
|
||||||
|
not any(key.startswith("KEY_KP") for key in current_input if isinstance(key, str))
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_genuine_numlock:
|
||||||
if self.env["input"]["newNumLock"]:
|
if self.env["input"]["newNumLock"]:
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
_("Numlock on"), interrupt=True
|
_("Numlock on"), interrupt=True
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ class command:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 6: Claude Code progress indicators
|
# Pattern 6: Claude Code progress indicators
|
||||||
claude_progress_match = re.search(r'^[·✶✢✻*]\s+[^(]+[…\.]*\s*\(esc to interrupt[^)]*\)\s*$', text)
|
claude_progress_match = re.search(r'[·✶✢✻*]\s+[^(]+[…\.]*\s*\(esc to interrupt[^)]*\)', text)
|
||||||
if claude_progress_match:
|
if claude_progress_match:
|
||||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
self.play_activity_beep()
|
self.play_activity_beep()
|
||||||
@@ -301,6 +301,25 @@ class command:
|
|||||||
self.env["commandBuffer"]["lastProgressValue"] = percentage
|
self.env["commandBuffer"]["lastProgressValue"] = percentage
|
||||||
return
|
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
|
||||||
|
|
||||||
def play_progress_tone(self, percentage):
|
def play_progress_tone(self, percentage):
|
||||||
# Map 0-100% to 400-1200Hz frequency range
|
# Map 0-100% to 400-1200Hz frequency range
|
||||||
frequency = 400 + (percentage * 8)
|
frequency = 400 + (percentage * 8)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
|
import time
|
||||||
from fenrirscreenreader.core.i18n import _
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +19,25 @@ class command:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return "No Description found"
|
return _("Announces incoming text changes")
|
||||||
|
|
||||||
|
def _was_handled_by_tab_completion(self, delta_text):
|
||||||
|
"""Check if this delta was already handled by tab completion to avoid duplicates"""
|
||||||
|
if "tabCompletion" not in self.env["commandBuffer"]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
tab_state = self.env["commandBuffer"]["tabCompletion"]
|
||||||
|
|
||||||
|
# Check if this exact delta was processed recently by tab completion
|
||||||
|
if (tab_state.get("lastProcessedDelta") == delta_text and
|
||||||
|
tab_state.get("lastProcessedTime")):
|
||||||
|
|
||||||
|
# Only suppress if processed within the last 100ms to avoid stale suppression
|
||||||
|
time_since_processed = time.time() - tab_state["lastProcessedTime"]
|
||||||
|
if time_since_processed <= 0.1:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
|
||||||
@@ -30,6 +48,12 @@ class command:
|
|||||||
if not self.env["runtime"]["ScreenManager"].is_delta(ignoreSpace=True):
|
if not self.env["runtime"]["ScreenManager"].is_delta(ignoreSpace=True):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
delta_text = self.env["screen"]["new_delta"]
|
||||||
|
|
||||||
|
# Skip if tab completion already handled this delta
|
||||||
|
if self._was_handled_by_tab_completion(delta_text):
|
||||||
|
return
|
||||||
|
|
||||||
# this must be a keyecho or something
|
# this must be a keyecho or something
|
||||||
# if len(self.env['screen']['new_delta'].strip(' \n\t')) <= 1:
|
# if len(self.env['screen']['new_delta'].strip(' \n\t')) <= 1:
|
||||||
x_move = abs(
|
x_move = abs(
|
||||||
@@ -41,14 +65,14 @@ class command:
|
|||||||
- self.env["screen"]["old_cursor"]["y"]
|
- self.env["screen"]["old_cursor"]["y"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if (x_move >= 1) and x_move == len(self.env["screen"]["new_delta"]):
|
if (x_move >= 1) and x_move == len(delta_text):
|
||||||
# if len(self.env['screen']['new_delta'].strip(' \n\t0123456789'))
|
# if len(self.env['screen']['new_delta'].strip(' \n\t0123456789'))
|
||||||
# <= 2:
|
# <= 2:
|
||||||
if "\n" not in self.env["screen"]["new_delta"]:
|
if "\n" not in delta_text:
|
||||||
return
|
return
|
||||||
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
# print(x_move, y_move, len(self.env['screen']['new_delta']), len(self.env['screen']['newNegativeDelta']))
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
self.env["screen"]["new_delta"], interrupt=False, flush=False
|
delta_text, interrupt=False, flush=False
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_callback(self, callback):
|
def set_callback(self, callback):
|
||||||
|
|||||||
@@ -147,3 +147,18 @@ class config_command:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.present_text(f"Failed to create basic defaults: {str(e)}")
|
self.present_text(f"Failed to create basic defaults: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_setting(self, section, setting, default=None):
|
||||||
|
"""Get setting value from settings manager"""
|
||||||
|
try:
|
||||||
|
return self.env["runtime"]["SettingsManager"].get_setting(section, setting)
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def set_setting(self, section, setting, value):
|
||||||
|
"""Set setting value via settings manager"""
|
||||||
|
try:
|
||||||
|
self.env["runtime"]["SettingsManager"].set_setting(section, setting, value)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set punctuation to All (every punctuation mark)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_level = self.get_setting("general", "punctuationLevel", "some")
|
||||||
|
|
||||||
|
if current_level.lower() == "all":
|
||||||
|
self.present_text("Punctuation level already set to All")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("general", "punctuationLevel", "all")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Punctuation level set to All - every punctuation mark will be spoken")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change punctuation level")
|
||||||
|
self.play_sound("Error")
|
||||||
-54
@@ -1,54 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
# Load base configuration class
|
|
||||||
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
|
||||||
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
|
||||||
_module = importlib.util.module_from_spec(_spec)
|
|
||||||
_spec.loader.exec_module(_module)
|
|
||||||
config_command = _module.config_command
|
|
||||||
|
|
||||||
|
|
||||||
class command(config_command):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return "Set punctuation verbosity level"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
current_level = self.get_setting("general", "punctuationLevel", "some")
|
|
||||||
|
|
||||||
# Present current level
|
|
||||||
level_descriptions = {
|
|
||||||
"none": "None - no punctuation spoken",
|
|
||||||
"some": "Some - basic punctuation only",
|
|
||||||
"most": "Most - detailed punctuation",
|
|
||||||
"all": "All - every punctuation mark",
|
|
||||||
}
|
|
||||||
|
|
||||||
current_description = level_descriptions.get(current_level, "Unknown")
|
|
||||||
self.present_text(f"Current punctuation level: {current_description}")
|
|
||||||
|
|
||||||
# Cycle through the four levels
|
|
||||||
levels = ["none", "some", "most", "all"]
|
|
||||||
try:
|
|
||||||
current_index = levels.index(current_level)
|
|
||||||
next_index = (current_index + 1) % len(levels)
|
|
||||||
new_level = levels[next_index]
|
|
||||||
except ValueError:
|
|
||||||
new_level = "some" # Default to some
|
|
||||||
|
|
||||||
success = self.set_setting("general", "punctuationLevel", new_level)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
new_description = level_descriptions[new_level]
|
|
||||||
self.present_text(f"Punctuation level set to: {new_description}")
|
|
||||||
self.play_sound("Accept")
|
|
||||||
else:
|
|
||||||
self.present_text("Failed to change punctuation level")
|
|
||||||
self.play_sound("Error")
|
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set punctuation to Most (detailed punctuation)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_level = self.get_setting("general", "punctuationLevel", "some")
|
||||||
|
|
||||||
|
if current_level.lower() == "most":
|
||||||
|
self.present_text("Punctuation level already set to Most")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("general", "punctuationLevel", "most")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Punctuation level set to Most - detailed punctuation will be spoken")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change punctuation level")
|
||||||
|
self.play_sound("Error")
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set punctuation to None (no punctuation spoken)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_level = self.get_setting("general", "punctuationLevel", "some")
|
||||||
|
|
||||||
|
if current_level.lower() == "none":
|
||||||
|
self.present_text("Punctuation level already set to None")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("general", "punctuationLevel", "none")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Punctuation level set to None - no punctuation will be spoken")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change punctuation level")
|
||||||
|
self.play_sound("Error")
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set punctuation to Some (basic punctuation only)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_level = self.get_setting("general", "punctuationLevel", "some")
|
||||||
|
|
||||||
|
if current_level.lower() == "some":
|
||||||
|
self.present_text("Punctuation level already set to Some")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("general", "punctuationLevel", "some")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Punctuation level set to Some - basic punctuation will be spoken")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change punctuation level")
|
||||||
|
self.play_sound("Error")
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
# Load base configuration class
|
|
||||||
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
|
||||||
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
|
||||||
_module = importlib.util.module_from_spec(_spec)
|
|
||||||
_spec.loader.exec_module(_module)
|
|
||||||
config_command = _module.config_command
|
|
||||||
|
|
||||||
|
|
||||||
class command(config_command):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return "Select keyboard layout (desktop or laptop)"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
current_layout = self.get_setting(
|
|
||||||
"keyboard", "keyboardLayout", "desktop"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Present current layout
|
|
||||||
self.present_text(f"Current keyboard layout: {current_layout}")
|
|
||||||
|
|
||||||
# Find available keyboard layouts
|
|
||||||
keyboard_path = "/etc/fenrirscreenreader/keyboard"
|
|
||||||
if not os.path.isdir(keyboard_path):
|
|
||||||
# Development path
|
|
||||||
keyboard_path = os.path.join(
|
|
||||||
os.path.dirname(self.settings_file), "..", "keyboard"
|
|
||||||
)
|
|
||||||
|
|
||||||
available_layouts = self.get_available_layouts(keyboard_path)
|
|
||||||
|
|
||||||
if len(available_layouts) > 1:
|
|
||||||
# Cycle through available layouts
|
|
||||||
try:
|
|
||||||
current_index = available_layouts.index(current_layout)
|
|
||||||
next_index = (current_index + 1) % len(available_layouts)
|
|
||||||
new_layout = available_layouts[next_index]
|
|
||||||
except ValueError:
|
|
||||||
# Current layout not found, use first available
|
|
||||||
new_layout = available_layouts[0]
|
|
||||||
|
|
||||||
success = self.set_setting(
|
|
||||||
"keyboard", "keyboardLayout", new_layout
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.present_text(f"Keyboard layout changed to: {new_layout}")
|
|
||||||
self.present_text(
|
|
||||||
"Please restart Fenrir for this change to take effect."
|
|
||||||
)
|
|
||||||
self.play_sound("Accept")
|
|
||||||
else:
|
|
||||||
self.present_text("Failed to change keyboard layout")
|
|
||||||
self.play_sound("Error")
|
|
||||||
else:
|
|
||||||
self.present_text("Only default keyboard layout is available")
|
|
||||||
self.play_sound("Cancel")
|
|
||||||
|
|
||||||
def get_available_layouts(self, keyboard_path):
|
|
||||||
"""Find available keyboard layout files"""
|
|
||||||
layouts = []
|
|
||||||
|
|
||||||
if os.path.isdir(keyboard_path):
|
|
||||||
try:
|
|
||||||
for file in os.listdir(keyboard_path):
|
|
||||||
if file.endswith(".conf") and not file.startswith("."):
|
|
||||||
layout_name = file[:-5] # Remove .conf extension
|
|
||||||
layouts.append(layout_name)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Ensure we have at least the default layouts
|
|
||||||
if not layouts:
|
|
||||||
layouts = ["desktop", "laptop"]
|
|
||||||
|
|
||||||
return sorted(layouts)
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
# Load base configuration class
|
|
||||||
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
|
||||||
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
|
||||||
_module = importlib.util.module_from_spec(_spec)
|
|
||||||
_spec.loader.exec_module(_module)
|
|
||||||
config_command = _module.config_command
|
|
||||||
|
|
||||||
|
|
||||||
class command(config_command):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return "Set character echo mode"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
|
||||||
|
|
||||||
# Present current mode
|
|
||||||
mode_descriptions = {
|
|
||||||
"0": "None - no character echo",
|
|
||||||
"1": "Always - echo all typed characters",
|
|
||||||
"2": "Caps Lock - echo only when caps lock is on",
|
|
||||||
}
|
|
||||||
|
|
||||||
current_description = mode_descriptions.get(current_mode, "Unknown")
|
|
||||||
self.present_text(
|
|
||||||
f"Current character echo mode: {current_description}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Cycle through the three modes
|
|
||||||
modes = ["0", "1", "2"]
|
|
||||||
try:
|
|
||||||
current_index = modes.index(current_mode)
|
|
||||||
next_index = (current_index + 1) % len(modes)
|
|
||||||
new_mode = modes[next_index]
|
|
||||||
except ValueError:
|
|
||||||
new_mode = "1" # Default to always
|
|
||||||
|
|
||||||
success = self.set_setting("keyboard", "charEchoMode", new_mode)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
new_description = mode_descriptions[new_mode]
|
|
||||||
self.present_text(f"Character echo mode set to: {new_description}")
|
|
||||||
self.play_sound("Accept")
|
|
||||||
else:
|
|
||||||
self.present_text("Failed to change character echo mode")
|
|
||||||
self.play_sound("Error")
|
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set character echo to Always (echo all typed characters)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
||||||
|
|
||||||
|
if current_mode == "1":
|
||||||
|
self.present_text("Character echo already set to Always")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "charEchoMode", "1")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Character echo set to Always - all typed characters will be spoken")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change character echo mode")
|
||||||
|
self.play_sound("Error")
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set character echo to Caps Lock (echo only when caps lock is on)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
||||||
|
|
||||||
|
if current_mode == "2":
|
||||||
|
self.present_text("Character echo already set to Caps Lock mode")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "charEchoMode", "2")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Character echo set to Caps Lock mode - characters will be spoken only when caps lock is on")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change character echo mode")
|
||||||
|
self.play_sound("Error")
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set character echo to None (no character echo)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_mode = self.get_setting("keyboard", "charEchoMode", "1")
|
||||||
|
|
||||||
|
if current_mode == "0":
|
||||||
|
self.present_text("Character echo already set to None")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "charEchoMode", "0")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Character echo set to None - no typed characters will be spoken")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change character echo mode")
|
||||||
|
self.play_sound("Error")
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set keyboard layout to Desktop"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_layout = self.get_setting("keyboard", "keyboardLayout", "desktop")
|
||||||
|
|
||||||
|
if current_layout.lower() == "desktop":
|
||||||
|
self.present_text("Keyboard layout already set to Desktop")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "keyboardLayout", "desktop")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Keyboard layout set to Desktop")
|
||||||
|
self.present_text("Please restart Fenrir for this change to take effect")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change keyboard layout")
|
||||||
|
self.play_sound("Error")
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set keyboard layout to Laptop"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_layout = self.get_setting("keyboard", "keyboardLayout", "desktop")
|
||||||
|
|
||||||
|
if current_layout.lower() == "laptop":
|
||||||
|
self.present_text("Keyboard layout already set to Laptop")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "keyboardLayout", "laptop")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Keyboard layout set to Laptop")
|
||||||
|
self.present_text("Please restart Fenrir for this change to take effect")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change keyboard layout")
|
||||||
|
self.play_sound("Error")
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set keyboard layout to PTY (terminal emulation)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_layout = self.get_setting("keyboard", "keyboardLayout", "desktop")
|
||||||
|
|
||||||
|
if current_layout.lower() == "pty":
|
||||||
|
self.present_text("Keyboard layout already set to PTY")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "keyboardLayout", "pty")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Keyboard layout set to PTY for terminal emulation")
|
||||||
|
self.present_text("Please restart Fenrir for this change to take effect")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change keyboard layout")
|
||||||
|
self.play_sound("Error")
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set keyboard layout to PTY2 (alternative terminal layout)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_layout = self.get_setting("keyboard", "keyboardLayout", "desktop")
|
||||||
|
|
||||||
|
if current_layout.lower() == "pty2":
|
||||||
|
self.present_text("Keyboard layout already set to PTY2")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("keyboard", "keyboardLayout", "pty2")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Keyboard layout set to PTY2 alternative terminal layout")
|
||||||
|
self.present_text("Please restart Fenrir for this change to take effect")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change keyboard layout")
|
||||||
|
self.play_sound("Error")
|
||||||
+1
-2
@@ -159,8 +159,7 @@ class command(config_command):
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.present_text(
|
self.present_text(
|
||||||
f"Failed to create default configuration: {
|
f"Failed to create default configuration: {str(e)}",
|
||||||
str(e)}",
|
|
||||||
interrupt=False,
|
interrupt=False,
|
||||||
flush=False,
|
flush=False,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-2
@@ -45,8 +45,7 @@ class command:
|
|||||||
sound_driver.initialize(self.env)
|
sound_driver.initialize(self.env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(
|
||||||
f"revert_to_saved sound_driver: Error reinitializing sound driver: {
|
f"revert_to_saved sound_driver: Error reinitializing sound driver: {str(e)}"
|
||||||
str(e)}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import os
|
|
||||||
|
|
||||||
from fenrirscreenreader.core.i18n import _
|
|
||||||
|
|
||||||
# Load base configuration class
|
|
||||||
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
|
||||||
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
|
||||||
_module = importlib.util.module_from_spec(_spec)
|
|
||||||
_spec.loader.exec_module(_module)
|
|
||||||
config_command = _module.config_command
|
|
||||||
|
|
||||||
|
|
||||||
class command(config_command):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return "Set screen text encoding"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
current_encoding = self.get_setting("screen", "encoding", "auto")
|
|
||||||
|
|
||||||
# Present current encoding
|
|
||||||
self.present_text(f"Current screen encoding: {current_encoding}")
|
|
||||||
|
|
||||||
# Cycle through available encodings
|
|
||||||
encodings = ["auto", "utf-8", "cp1252", "iso-8859-1"]
|
|
||||||
try:
|
|
||||||
current_index = encodings.index(current_encoding)
|
|
||||||
next_index = (current_index + 1) % len(encodings)
|
|
||||||
new_encoding = encodings[next_index]
|
|
||||||
except ValueError:
|
|
||||||
new_encoding = "auto" # Default to auto
|
|
||||||
|
|
||||||
success = self.set_setting("screen", "encoding", new_encoding)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.present_text(f"Screen encoding set to: {new_encoding}")
|
|
||||||
if new_encoding == "auto":
|
|
||||||
self.present_text(
|
|
||||||
"Fenrir will automatically detect text encoding"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.present_text(f"Fenrir will use {new_encoding} encoding")
|
|
||||||
self.play_sound("Accept")
|
|
||||||
else:
|
|
||||||
self.present_text("Failed to change screen encoding")
|
|
||||||
self.play_sound("Error")
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set screen encoding to Auto (automatic detection)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_encoding = self.get_setting("screen", "encoding", "auto")
|
||||||
|
|
||||||
|
if current_encoding.lower() == "auto":
|
||||||
|
self.present_text("Screen encoding already set to Auto")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("screen", "encoding", "auto")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Screen encoding set to Auto - Fenrir will automatically detect text encoding")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change screen encoding")
|
||||||
|
self.play_sound("Error")
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set screen encoding to CP1252 (Windows Western European)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_encoding = self.get_setting("screen", "encoding", "auto")
|
||||||
|
|
||||||
|
if current_encoding.lower() == "cp1252":
|
||||||
|
self.present_text("Screen encoding already set to CP1252")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("screen", "encoding", "cp1252")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Screen encoding set to CP1252 - Windows Western European encoding")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change screen encoding")
|
||||||
|
self.play_sound("Error")
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set screen encoding to ISO-8859-1 (Latin-1)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_encoding = self.get_setting("screen", "encoding", "auto")
|
||||||
|
|
||||||
|
if current_encoding.lower() == "iso-8859-1":
|
||||||
|
self.present_text("Screen encoding already set to ISO-8859-1")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("screen", "encoding", "iso-8859-1")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Screen encoding set to ISO-8859-1 - Latin-1 encoding")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change screen encoding")
|
||||||
|
self.play_sound("Error")
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
|
from fenrirscreenreader.core.i18n import _
|
||||||
|
|
||||||
|
# Load base configuration class
|
||||||
|
_base_path = os.path.join(os.path.dirname(__file__), "..", "config_base.py")
|
||||||
|
_spec = importlib.util.spec_from_file_location("config_base", _base_path)
|
||||||
|
_module = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(_module)
|
||||||
|
config_command = _module.config_command
|
||||||
|
|
||||||
|
|
||||||
|
class command(config_command):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return "Set screen encoding to UTF-8 (Unicode)"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
current_encoding = self.get_setting("screen", "encoding", "auto")
|
||||||
|
|
||||||
|
if current_encoding.lower() == "utf-8":
|
||||||
|
self.present_text("Screen encoding already set to UTF-8")
|
||||||
|
return
|
||||||
|
|
||||||
|
success = self.set_setting("screen", "encoding", "utf-8")
|
||||||
|
|
||||||
|
if success:
|
||||||
|
self.present_text("Screen encoding set to UTF-8 - Unicode text encoding")
|
||||||
|
self.play_sound("Accept")
|
||||||
|
else:
|
||||||
|
self.present_text("Failed to change screen encoding")
|
||||||
|
self.play_sound("Error")
|
||||||
@@ -189,9 +189,7 @@ class DynamicApplyVoiceCommand:
|
|||||||
|
|
||||||
# Debug: verify what was actually set
|
# Debug: verify what was actually set
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
f"Speech driver now has module: {
|
f"Speech driver now has module: {SpeechDriver.module}, voice: {SpeechDriver.voice}",
|
||||||
SpeechDriver.module}, voice: {
|
|
||||||
SpeechDriver.voice}",
|
|
||||||
interrupt=True,
|
interrupt=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -233,8 +231,7 @@ class DynamicApplyVoiceCommand:
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.env["runtime"]["OutputManager"].present_text(
|
self.env["runtime"]["OutputManager"].present_text(
|
||||||
f"Failed to apply voice, reverted: {
|
f"Failed to apply voice, reverted: {str(e)}",
|
||||||
str(e)}",
|
|
||||||
interrupt=False,
|
interrupt=False,
|
||||||
flush=False,
|
flush=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2025.08.31"
|
version = "2025.09.26"
|
||||||
code_name = "master"
|
code_name = "testing"
|
||||||
|
|||||||
@@ -438,11 +438,25 @@ class driver(inputDriver):
|
|||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
if len(cap[event_type.EV_KEY]) < 60:
|
# Check if device has numpad keys - use lower threshold for dedicated numpads
|
||||||
|
numpad_keys = [
|
||||||
|
evdev.ecodes.KEY_KP0, evdev.ecodes.KEY_KP1, evdev.ecodes.KEY_KP2,
|
||||||
|
evdev.ecodes.KEY_KP3, evdev.ecodes.KEY_KP4, evdev.ecodes.KEY_KP5,
|
||||||
|
evdev.ecodes.KEY_KP6, evdev.ecodes.KEY_KP7, evdev.ecodes.KEY_KP8,
|
||||||
|
evdev.ecodes.KEY_KP9, evdev.ecodes.KEY_KPPLUS, evdev.ecodes.KEY_KPMINUS,
|
||||||
|
evdev.ecodes.KEY_KPASTERISK, evdev.ecodes.KEY_KPSLASH, evdev.ecodes.KEY_KPENTER,
|
||||||
|
evdev.ecodes.KEY_KPDOT
|
||||||
|
]
|
||||||
|
|
||||||
|
has_numpad_keys = any(key in cap[event_type.EV_KEY] for key in numpad_keys)
|
||||||
|
min_key_threshold = 10 if has_numpad_keys else 60
|
||||||
|
|
||||||
|
if len(cap[event_type.EV_KEY]) < min_key_threshold:
|
||||||
|
threshold_type = "numpad" if has_numpad_keys else "keyboard"
|
||||||
self.env["runtime"][
|
self.env["runtime"][
|
||||||
"DebugManager"
|
"DebugManager"
|
||||||
].write_debug_out(
|
].write_debug_out(
|
||||||
"Device Skipped (< 60 keys):"
|
f"Device Skipped (< {min_key_threshold} keys for {threshold_type}):"
|
||||||
+ curr_device.name,
|
+ curr_device.name,
|
||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
)
|
)
|
||||||
|
|||||||
+12
-5
@@ -43,12 +43,19 @@ if [ -n "$STAGED_PYTHON_FILES" ]; then
|
|||||||
|
|
||||||
for file in $STAGED_PYTHON_FILES; do
|
for file in $STAGED_PYTHON_FILES; do
|
||||||
if [ -f "$file" ]; then
|
if [ -f "$file" ]; then
|
||||||
# Check for unterminated strings (the main issue from the email)
|
# Check for broken f-strings (multiline issues that cause syntax errors)
|
||||||
if grep -n 'f".*{$' "$file" >/dev/null 2>&1; then
|
# Pattern 1: f-string with opening brace at end of line (likely broken across lines)
|
||||||
echo -e "${RED}✗ $file: Potential unterminated f-string${NC}"
|
if grep -n 'f"[^"]*{[^}]*$' "$file" >/dev/null 2>&1; then
|
||||||
|
echo -e "${RED}✗ $file: Potential broken multiline f-string${NC}"
|
||||||
|
grep -n 'f"[^"]*{[^}]*$' "$file" | head -3
|
||||||
ISSUES_FOUND=1
|
ISSUES_FOUND=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Pattern 2: Lines that end with just an opening brace (common in broken f-strings)
|
||||||
|
if grep -n '^\s*[^#]*{$' "$file" >/dev/null 2>&1; then
|
||||||
|
echo -e "${YELLOW}⚠ $file: Lines ending with lone opening brace (check f-strings)${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check for missing imports that are commonly used
|
# Check for missing imports that are commonly used
|
||||||
if grep -q 'debug\.DebugLevel\.' "$file" && ! grep -q 'from.*debug' "$file" && ! grep -q 'import.*debug' "$file"; then
|
if grep -q 'debug\.DebugLevel\.' "$file" && ! grep -q 'from.*debug' "$file" && ! grep -q 'import.*debug' "$file"; then
|
||||||
echo -e "${YELLOW}⚠ $file: Uses debug.DebugLevel but no debug import found${NC}"
|
echo -e "${YELLOW}⚠ $file: Uses debug.DebugLevel but no debug import found${NC}"
|
||||||
@@ -110,7 +117,7 @@ if [ -n "$STAGED_PYTHON_FILES" ]; then
|
|||||||
for file in $STAGED_PYTHON_FILES; do
|
for file in $STAGED_PYTHON_FILES; do
|
||||||
if [ -f "$file" ]; then
|
if [ -f "$file" ]; then
|
||||||
# Check for potential passwords, keys, tokens
|
# Check for potential passwords, keys, tokens
|
||||||
if grep -i -E '(password|passwd|pwd|key|token|secret|api_key).*=.*["\'][^"\']{8,}["\']' "$file" >/dev/null 2>&1; then
|
if grep -i -E '(password|passwd|pwd|key|token|secret|api_key).*=.*["'"'"'][^"'"'"']{8,}["'"'"']' "$file" >/dev/null 2>&1; then
|
||||||
echo -e "${RED}✗ $file: Potential hardcoded secret detected${NC}"
|
echo -e "${RED}✗ $file: Potential hardcoded secret detected${NC}"
|
||||||
SECRETS_FOUND=1
|
SECRETS_FOUND=1
|
||||||
fi
|
fi
|
||||||
@@ -126,7 +133,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Summary
|
# Summary
|
||||||
echo -e "\n${'='*50}"
|
echo -e "\n=================================================="
|
||||||
if [ $VALIDATION_FAILED -eq 0 ]; then
|
if [ $VALIDATION_FAILED -eq 0 ]; then
|
||||||
echo -e "${GREEN}✓ All pre-commit validations passed${NC}"
|
echo -e "${GREEN}✓ All pre-commit validations passed${NC}"
|
||||||
echo -e "${GREEN}Commit allowed to proceed${NC}"
|
echo -e "${GREEN}Commit allowed to proceed${NC}"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ Usage:
|
|||||||
|
|
||||||
import ast
|
import ast
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -25,12 +26,33 @@ class SyntaxValidator:
|
|||||||
self.warnings = []
|
self.warnings = []
|
||||||
self.fixed = []
|
self.fixed = []
|
||||||
|
|
||||||
|
def check_fstring_issues(self, content):
|
||||||
|
"""Check for common f-string formatting issues that cause syntax errors."""
|
||||||
|
issues = []
|
||||||
|
lines = content.split('\n')
|
||||||
|
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
# Check for f-strings that appear to be broken across lines
|
||||||
|
# Pattern: f"some text {
|
||||||
|
if re.search(r'f"[^"]*{[^}]*$', line.strip()):
|
||||||
|
issues.append(f"Line {i}: Potential broken multiline f-string")
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
def validate_file(self, filepath):
|
def validate_file(self, filepath):
|
||||||
"""Validate syntax of a single Python file."""
|
"""Validate syntax of a single Python file."""
|
||||||
try:
|
try:
|
||||||
with open(filepath, 'r', encoding='utf-8') as f:
|
with open(filepath, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
|
# Check for specific f-string issues before AST parsing
|
||||||
|
fstring_issues = self.check_fstring_issues(content)
|
||||||
|
if fstring_issues:
|
||||||
|
# Create a synthetic syntax error for f-string issues
|
||||||
|
error_msg = f"F-string issues: {'; '.join(fstring_issues)}"
|
||||||
|
self.errors.append((filepath, SyntaxError(error_msg), content))
|
||||||
|
return False, content
|
||||||
|
|
||||||
# Parse with AST (catches syntax errors)
|
# Parse with AST (catches syntax errors)
|
||||||
ast.parse(content, filename=str(filepath))
|
ast.parse(content, filename=str(filepath))
|
||||||
return True, content
|
return True, content
|
||||||
|
|||||||
Reference in New Issue
Block a user