#!/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