Separate menu entries for some settings, improves usability. Fixed device detection for devices that do not contain what they do in their name, e.g. numpads that are not listed as numpads. This hopefully fixes a bug with some external numpads.

This commit is contained in:
Storm Dragon
2025-09-12 12:09:00 -04:00
parent 0658d37ae8
commit 96cdda99c4
23 changed files with 719 additions and 248 deletions

View File

@@ -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

View File

@@ -147,3 +147,18 @@ class config_command:
except Exception as e:
self.present_text(f"Failed to create basic defaults: {str(e)}")
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

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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)

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")

View File

@@ -4,5 +4,5 @@
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
version = "2025.08.31"
version = "2025.09.12"
code_name = "testing"

View File

@@ -438,11 +438,25 @@ class driver(inputDriver):
debug.DebugLevel.INFO,
)
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"][
"DebugManager"
].write_debug_out(
"Device Skipped (< 60 keys):"
f"Device Skipped (< {min_key_threshold} keys for {threshold_type}):"
+ curr_device.name,
debug.DebugLevel.INFO,
)