Bug fix in vmenu for keyboard layouts.

This commit is contained in:
Storm Dragon
2025-11-27 22:44:51 -05:00
8 changed files with 259 additions and 153 deletions

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributors.
from fenrirscreenreader.core.i18n import _
from fenrirscreenreader.core import debug
class command:
def __init__(self):
pass
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return _(
"TUI focus mode handler - suppresses screen update spam "
"for interactive TUI applications"
)
def run(self):
# Check if TUI mode is enabled
if not self.env["runtime"]["SettingsManager"].get_setting_as_bool(
"focus", "tui"
):
return
# TUI mode is active - set suppression flag for incoming handler
# This prevents the 70000-incoming.py command from announcing
# screen updates
self.env["commandBuffer"]["tuiSuppressIncoming"] = True
self.env["runtime"]["DebugManager"].write_debug_out(
"tui_focus_handler: TUI mode active, suppressing incoming text",
debug.DebugLevel.INFO
)
def set_callback(self, callback):
pass

View File

@@ -1,38 +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 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

@@ -1,38 +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 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

@@ -1,38 +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 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

@@ -1,38 +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 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

@@ -0,0 +1,203 @@
#!/usr/bin/env python3
import glob
import os
from fenrirscreenreader.core import debug
class DynamicKeyboardLayoutCommand:
"""Dynamic command class for keyboard layout selection"""
def __init__(self, layoutName, layoutPath, env):
self.layoutName = layoutName
self.layoutPath = layoutPath
# Extract just the base name without extension for comparison
self.layoutBaseName = layoutName
self.env = env
def initialize(self, environment):
self.env = environment
def shutdown(self):
pass
def get_description(self):
return f"Set keyboard layout to {self.layoutName}"
def run(self):
try:
settingsManager = self.env["runtime"]["SettingsManager"]
currentLayout = settingsManager.get_setting(
"keyboard", "keyboardLayout"
)
# Check if already set (compare both full path and base name)
currentBaseName = os.path.splitext(os.path.basename(currentLayout))[0] if currentLayout else ""
if currentBaseName.lower() == self.layoutBaseName.lower() or currentLayout.lower() == self.layoutPath.lower():
self.env["runtime"]["OutputManager"].present_text(
f"Keyboard layout already set to {self.layoutName}"
)
return
# Set the new layout in the config file using full path
try:
# Update the setting in memory
settingsManager.set_setting(
"keyboard", "keyboardLayout", self.layoutPath
)
# Save to the actual config file
configFilePath = settingsManager.get_settings_file()
settingsManager.save_settings(configFilePath)
self.env["runtime"]["OutputManager"].present_text(
f"Keyboard layout set to {self.layoutName}. Please restart Fenrir for this change to take effect."
)
# Play accept sound
self.env["runtime"]["OutputManager"].present_text(
"", sound_icon="Accept", interrupt=False
)
except Exception as e:
self.env["runtime"]["OutputManager"].present_text(
f"Failed to change keyboard layout to {self.layoutName}"
)
self.env["runtime"]["DebugManager"].write_debug_out(
f"DynamicKeyboardLayout: Error setting layout {self.layoutName}: {e}",
debug.DebugLevel.ERROR,
)
# Play error sound
self.env["runtime"]["OutputManager"].present_text(
"", sound_icon="ErrorSound", interrupt=False
)
except Exception as e:
self.env["runtime"]["OutputManager"].present_text(
f"Keyboard layout change error: {str(e)}", interrupt=True
)
self.env["runtime"]["DebugManager"].write_debug_out(
f"DynamicKeyboardLayout: Unexpected error for {self.layoutName}: {e}",
debug.DebugLevel.ERROR,
)
def set_callback(self, callback):
pass
def add_dynamic_keyboard_layout_menus(VmenuManager):
"""Add dynamic keyboard layout menus to vmenu system"""
try:
env = VmenuManager.env
# Get keyboard layout files
layouts = get_keyboard_layouts(env)
if not layouts:
return
# Create keyboard layouts submenu
layoutMenu = {}
# Add layout commands
for layoutName, layoutPath in layouts:
layoutCommand = DynamicKeyboardLayoutCommand(
layoutName, layoutPath, env
)
layoutMenu[f"{layoutName} Action"] = layoutCommand
# Find keyboard menu in existing vmenu structure
# If fenrir menu exists, add layouts under it
if "fenrir Menu" in VmenuManager.menuDict:
fenrirMenu = VmenuManager.menuDict["fenrir Menu"]
if "keyboard Menu" in fenrirMenu:
# Add dynamic layouts to existing keyboard menu
keyboardMenu = fenrirMenu["keyboard Menu"]
keyboardMenu["Keyboard Layouts Menu"] = layoutMenu
else:
# Create keyboard menu with layouts
fenrirMenu["keyboard Menu"] = {
"Keyboard Layouts Menu": layoutMenu
}
else:
# Create standalone keyboard layouts menu
VmenuManager.menuDict["Keyboard Layouts Menu"] = layoutMenu
except Exception as e:
# Use debug manager for error logging
if "DebugManager" in env.get("runtime", {}):
env["runtime"]["DebugManager"].write_debug_out(
f"Error creating dynamic keyboard layout menus: {e}",
debug.DebugLevel.ERROR,
)
else:
print(f"Error creating dynamic keyboard layout menus: {e}")
def get_keyboard_layouts(env):
"""Get available keyboard layouts from keyboard directory"""
layouts = []
try:
# Get keyboard directory paths
keyboardDirs = []
# Check system installation path
systemKeyboardPath = "/etc/fenrirscreenreader/keyboard/"
if os.path.exists(systemKeyboardPath):
keyboardDirs.append(systemKeyboardPath)
# Check source/development path
try:
import fenrirscreenreader
fenrirPath = os.path.dirname(fenrirscreenreader.__file__)
devKeyboardPath = os.path.join(
fenrirPath, "..", "..", "config", "keyboard"
)
devKeyboardPath = os.path.abspath(devKeyboardPath)
if os.path.exists(devKeyboardPath):
keyboardDirs.append(devKeyboardPath)
except Exception:
pass
# Get current layout setting path
try:
currentLayoutSetting = env["runtime"]["SettingsManager"].get_setting(
"keyboard", "keyboardLayout"
)
if currentLayoutSetting and os.path.exists(currentLayoutSetting):
currentLayoutDir = os.path.dirname(currentLayoutSetting)
if currentLayoutDir not in keyboardDirs:
keyboardDirs.append(currentLayoutDir)
except Exception:
pass
# Scan for .conf files
seenLayouts = set()
for keyboardDir in keyboardDirs:
try:
confFiles = glob.glob(os.path.join(keyboardDir, "*.conf"))
for confFile in confFiles:
layoutName = os.path.splitext(os.path.basename(confFile))[
0
]
if layoutName not in seenLayouts:
seenLayouts.add(layoutName)
layouts.append((layoutName, confFile))
except Exception as e:
if "DebugManager" in env.get("runtime", {}):
env["runtime"]["DebugManager"].write_debug_out(
f"Error scanning keyboard directory {keyboardDir}: {e}",
debug.DebugLevel.WARNING,
)
# Sort layouts alphabetically
layouts.sort(key=lambda x: x[0].lower())
except Exception as e:
if "DebugManager" in env.get("runtime", {}):
env["runtime"]["DebugManager"].write_debug_out(
f"Error getting keyboard layouts: {e}",
debug.DebugLevel.ERROR,
)
return layouts

View File

@@ -217,6 +217,16 @@ class VmenuManager:
except Exception as e: except Exception as e:
print(f"Error adding dynamic voice menus: {e}") print(f"Error adding dynamic voice menus: {e}")
# Add dynamic keyboard layout menus
try:
from fenrirscreenreader.core.dynamicKeyboardLayoutMenu import (
add_dynamic_keyboard_layout_menus,
)
add_dynamic_keyboard_layout_menus(self)
except Exception as e:
print(f"Error adding dynamic keyboard layout menus: {e}")
# index still valid? # index still valid?
if self.curr_index is not None: if self.curr_index is not None:
try: try:

View File

@@ -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.11.24" version = "2025.11.27"
code_name = "master" code_name = "master"