Bug fix in vmenu for keyboard layouts.
This commit is contained in:
@@ -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
|
||||||
@@ -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")
|
|
||||||
@@ -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")
|
|
||||||
@@ -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")
|
|
||||||
@@ -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")
|
|
||||||
203
src/fenrirscreenreader/core/dynamicKeyboardLayoutMenu.py
Normal file
203
src/fenrirscreenreader/core/dynamicKeyboardLayoutMenu.py
Normal 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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user