From 7f7faa17d33e714e39f0cd3c807ffdd9fa2b3f0b Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 27 Nov 2025 22:42:36 -0500 Subject: [PATCH] keyboard layout fixed in vmenu. --- .../onScreenUpdate/55000-tui_focus_handler.py | 45 ++++ .../KEY/fenrir/keyboard/set_layout_desktop.py | 38 ---- .../KEY/fenrir/keyboard/set_layout_laptop.py | 38 ---- .../KEY/fenrir/keyboard/set_layout_pty.py | 38 ---- .../KEY/fenrir/keyboard/set_layout_pty2.py | 38 ---- .../core/dynamicKeyboardLayoutMenu.py | 203 ++++++++++++++++++ src/fenrirscreenreader/core/vmenuManager.py | 10 + src/fenrirscreenreader/fenrirVersion.py | 2 +- 8 files changed, 259 insertions(+), 153 deletions(-) create mode 100644 src/fenrirscreenreader/commands/onScreenUpdate/55000-tui_focus_handler.py delete mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_desktop.py delete mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_laptop.py delete mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty.py delete mode 100644 src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty2.py create mode 100644 src/fenrirscreenreader/core/dynamicKeyboardLayoutMenu.py diff --git a/src/fenrirscreenreader/commands/onScreenUpdate/55000-tui_focus_handler.py b/src/fenrirscreenreader/commands/onScreenUpdate/55000-tui_focus_handler.py new file mode 100644 index 00000000..1c210e43 --- /dev/null +++ b/src/fenrirscreenreader/commands/onScreenUpdate/55000-tui_focus_handler.py @@ -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 diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_desktop.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_desktop.py deleted file mode 100644 index bf657c52..00000000 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_desktop.py +++ /dev/null @@ -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") \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_laptop.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_laptop.py deleted file mode 100644 index 39b2ec8e..00000000 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_laptop.py +++ /dev/null @@ -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") \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty.py deleted file mode 100644 index c9d6c717..00000000 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty.py +++ /dev/null @@ -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") \ No newline at end of file diff --git a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty2.py b/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty2.py deleted file mode 100644 index b1161449..00000000 --- a/src/fenrirscreenreader/commands/vmenu-profiles/KEY/fenrir/keyboard/set_layout_pty2.py +++ /dev/null @@ -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") \ No newline at end of file diff --git a/src/fenrirscreenreader/core/dynamicKeyboardLayoutMenu.py b/src/fenrirscreenreader/core/dynamicKeyboardLayoutMenu.py new file mode 100644 index 00000000..9b570e81 --- /dev/null +++ b/src/fenrirscreenreader/core/dynamicKeyboardLayoutMenu.py @@ -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 diff --git a/src/fenrirscreenreader/core/vmenuManager.py b/src/fenrirscreenreader/core/vmenuManager.py index 5b558e19..998199ee 100755 --- a/src/fenrirscreenreader/core/vmenuManager.py +++ b/src/fenrirscreenreader/core/vmenuManager.py @@ -217,6 +217,16 @@ class VmenuManager: except Exception as 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? if self.curr_index is not None: try: diff --git a/src/fenrirscreenreader/fenrirVersion.py b/src/fenrirscreenreader/fenrirVersion.py index 93de9bcb..e9253681 100644 --- a/src/fenrirscreenreader/fenrirVersion.py +++ b/src/fenrirscreenreader/fenrirVersion.py @@ -4,5 +4,5 @@ # Fenrir TTY screen reader # By Chrys, Storm Dragon, and contributors. -version = "2025.11.24" +version = "2025.11.27" code_name = "testing"