From 38ef1c2d72ee1b02e985e2093f92b4701c6a6c7c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Tue, 12 May 2026 02:26:02 -0400 Subject: [PATCH] Attempt to fix forward keypress and numpad state. --- .../core/settingsManager.py.bak | 742 ++++++++++++++++++ .../inputDriver/x11Driver.py | 64 +- 2 files changed, 795 insertions(+), 11 deletions(-) create mode 100644 src/fenrirscreenreader/core/settingsManager.py.bak diff --git a/src/fenrirscreenreader/core/settingsManager.py.bak b/src/fenrirscreenreader/core/settingsManager.py.bak new file mode 100644 index 00000000..8105bc46 --- /dev/null +++ b/src/fenrirscreenreader/core/settingsManager.py.bak @@ -0,0 +1,742 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Fenrir TTY screen reader +# By Chrys, Storm Dragon, and contributors. + +import inspect +import os +from configparser import ConfigParser + +from fenrirscreenreader.core import applicationManager +from fenrirscreenreader.core import attributeManager +from fenrirscreenreader.core import barrierManager +from fenrirscreenreader.core import commandManager +from fenrirscreenreader.core import cursorManager +from fenrirscreenreader.core import debug +from fenrirscreenreader.core import debugManager +from fenrirscreenreader.core import diffReviewManager +from fenrirscreenreader.core import environment +from fenrirscreenreader.core import eventManager +from fenrirscreenreader.core import helpManager +from fenrirscreenreader.core import inputManager +from fenrirscreenreader.core import memoryManager +from fenrirscreenreader.core import outputManager +from fenrirscreenreader.core import processManager +from fenrirscreenreader.core import punctuationManager +from fenrirscreenreader.core import quickMenuManager +from fenrirscreenreader.core import readAllManager +from fenrirscreenreader.core import remoteManager +from fenrirscreenreader.core import sayAllManager +from fenrirscreenreader.core import screenManager +from fenrirscreenreader.core import tableManager +from fenrirscreenreader.core import textManager +from fenrirscreenreader.core import vmenuManager +from fenrirscreenreader.core.settingsData import settings_data +from fenrirscreenreader.utils import module_utils + +currentdir = os.path.dirname( + os.path.realpath(os.path.abspath(inspect.getfile(inspect.currentframe()))) +) +fenrir_path = os.path.dirname(currentdir) + + +class SettingsManager: + def __init__(self): + self.settings = settings_data + self.settingArgDict = {} + self.bindingsBackup = None + self.settings_file = "" + + def initialize(self, environment): + self.env = environment + + def shutdown(self): + pass + + def get_binding_backup(self): + return self.bindingsBackup.copy() + + def load_sound_icons(self, soundIconPath, environment=None): + # Use passed environment or fall back to self.env + env = environment if environment is not None else self.env + try: + with open(soundIconPath + "/soundicons.conf", "r") as siConfig: + while True: + line = siConfig.readline() + if not line: + break + line = line.replace("\n", "") + if line.replace(" ", "") == "": + continue + if line.replace(" ", "").startswith("#"): + continue + if line.count("=") != 1: + continue + values = line.split("=") + sound_icon = values[0].upper() + values[1] = values[1].replace("'", "") + values[1] = values[1].replace('"', "") + sound_icon_file = "" + if os.path.exists(values[1]): + sound_icon_file = values[1] + else: + if not soundIconPath.endswith("/"): + soundIconPath += "/" + if os.path.exists(soundIconPath + values[1]): + sound_icon_file = soundIconPath + values[1] + env["soundIcons"][sound_icon] = sound_icon_file + env["runtime"]["DebugManager"].write_debug_out( + "SoundIcon: " + sound_icon + "." + sound_icon_file, + debug.DebugLevel.INFO, + on_any_level=True, + ) + except (IOError, OSError) as e: + env["runtime"]["DebugManager"].write_debug_out( + "load_sound_icons: failed to load sound icons from " + + soundIconPath + + ". Error: " + + str(e), + debug.DebugLevel.ERROR, + ) + + def get_settings_file(self): + return self.settings_file + + def set_settings_file(self, settings_file): + if not os.path.exists(settings_file): + return + if not os.access(settings_file, os.R_OK): + return + self.settings_file = settings_file + + def load_settings(self, setting_config_path): + if not os.path.exists(setting_config_path): + return False + if not os.access(setting_config_path, os.R_OK): + return False + self.env["settings"] = ConfigParser() + try: + self.env["settings"].read(setting_config_path) + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "settings_manager load_settings: Error reading config file: " + + str(e), + debug.DebugLevel.ERROR, + ) + return False + self.set_settings_file(setting_config_path) + return True + + def save_settings(self, setting_config_path): + # set opt dict here + # save file + try: + # print('file: ',setting_config_path) + for section, settings in self.settingArgDict.items(): + for setting, value in settings.items(): + # print(section, setting, value) + self.env["settings"].set(section, setting, value) + # print('full',self.env['settings']) + + config_file = open(setting_config_path, "w") + self.env["settings"].write(config_file) + config_file.close() + os.chmod(setting_config_path, 0o644) + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "save_settings: save settingsfile:" + + setting_config_path + + "failed. Error:" + + str(e), + debug.DebugLevel.ERROR, + ) + + def set_setting(self, section, setting, value): + self.set_option_arg_dict(section, setting, value) + # self.env['settings'].set(section, setting, value) + + def get_setting(self, section, setting): + value = "" + try: + value = self.settingArgDict[section][setting] + return value + except Exception as e: + pass + try: + value = self.env["settings"].get(section, setting) + except Exception as e: + value = str(self.settings[section][setting]) + return value + + def get_setting_as_int(self, section, setting): + value = 0 + try: + value = int(self.settingArgDict[section][setting]) + return value + except Exception as e: + pass + try: + value = self.env["settings"].getint(section, setting) + except Exception as e: + value = self.settings[section][setting] + return value + + def get_setting_as_float(self, section, setting): + value = 0.0 + try: + value = float(self.settingArgDict[section][setting]) + return value + except Exception as e: + pass + try: + value = self.env["settings"].getfloat(section, setting) + except Exception as e: + value = self.settings[section][setting] + return value + + def get_setting_as_bool(self, section, setting): + value = False + try: + value = self.settingArgDict[section][setting].upper() in [ + "1", + "YES", + "JA", + "TRUE", + ] + return value + except Exception as e: + pass + try: + value = self.env["settings"].getboolean(section, setting) + except Exception as e: + value = self.settings[section][setting] + return value + + def load_driver(self, driverName, driverType): + # Map runtime keys to actual directory names + driver_dir_map = { + "InputDriver": "inputDriver", + "ScreenDriver": "screenDriver", + "SpeechDriver": "speechDriver", + "SoundDriver": "soundDriver", + "RemoteDriver": "remoteDriver", + } + driver_dir = driver_dir_map.get(driverType, driverType) + + try: + if self.env["runtime"][driverType] is not None: + self.env["runtime"][driverType].shutdown(self.env) + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "settings_manager load_driver: Error shutting down driver: " + + str(e), + debug.DebugLevel.ERROR, + ) + try: + driver_mod = module_utils.import_module( + driverName, + fenrir_path + "/" + driver_dir + "/" + driverName + ".py", + ) + self.env["runtime"][driverType] = driver_mod.driver() + self.env["runtime"][driverType].initialize(self.env) + self.env["runtime"]["DebugManager"].write_debug_out( + "Loading Driver " + driverType + " (" + driverName + ") OK", + debug.DebugLevel.INFO, + on_any_level=True, + ) + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "Loading Driver " + + driverType + + " (" + + driverName + + ") FAILED:" + + str(e), + debug.DebugLevel.ERROR, + ) + try: + driver_mod = module_utils.import_module( + driverName, + fenrir_path + "/" + driver_dir + "/dummyDriver.py", + ) + self.env["runtime"][driverType] = driver_mod.driver() + self.env["runtime"][driverType].initialize(self.env) + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "(fallback) Loading Driver " + + driverType + + " (dummyDriver) FAILED:" + + str(e), + debug.DebugLevel.ERROR, + ) + + def shutdown_driver(self, driverType): + try: + self.env["runtime"][driverType].shutdown() + except Exception as e: + pass + del self.env["runtime"][driverType] + + def set_fenrir_keys(self, keys): + keys = keys.upper() + key_list = keys.split(",") + for key in key_list: + if key not in self.env["input"]["fenrir_key"]: + self.env["input"]["fenrir_key"].append(key) + + def set_script_keys(self, keys): + keys = keys.upper() + key_list = keys.split(",") + for key in key_list: + if key not in self.env["input"]["script_key"]: + self.env["input"]["script_key"].append(key) + + def reset_setting_arg_dict(self): + self.settingArgDict = {} + self.env["runtime"]["OutputManager"].reset_SpeechDriver() + + def set_option_arg_dict(self, section, setting, value): + # section = section.lower() + # setting = setting.lower() + try: + e = self.settingArgDict[section] + except KeyError: + self.settingArgDict[section] = {} + try: + t = self.settings[section][setting] + except Exception as e: + print(section, setting, "not found") + return + try: + v = value # Initialize v with the original value + if isinstance(self.settings[section][setting], str): + v = str(value) + elif isinstance(self.settings[section][setting], bool): + if value not in ["True", "False"]: + raise ValueError( + "could not convert string to bool: " + value + ) + v = value == "True" + elif isinstance(self.settings[section][setting], int): + v = int(value) + elif isinstance(self.settings[section][setting], float): + v = float(value) + + # Content validation for critical settings + self._validate_setting_value(section, setting, v) + + self.settingArgDict[section][setting] = str(value) + except Exception as e: + print( + "settings_manager:set_option_arg_dict:Datatype missmatch: " + + section + + "#" + + setting + + "=" + + str(value) + + " Error:" + + str(e) + ) + # self.env['runtime']['DebugManager'].write_debug_out('settings_manager:set_option_arg_dict:Datatype + # missmatch: '+ section + '#' + setting + '=' + value + ' Error:' + # + str(e), debug.DebugLevel.ERROR) + return + + def _validate_setting_value(self, section, setting, value): + """Validate setting values for critical screen reader functionality. + Only validates settings that could cause crashes or accessibility issues. + Invalid values raise ValueError which is caught by the calling method. + """ + + # Speech settings validation - critical for accessibility + if section == "speech": + if setting == "rate": + if not (0.0 <= value <= 3.0): + raise ValueError( + f"Speech rate must be between 0.0 and 3.0, got {value}" + ) + elif setting == "pitch": + if not (0.0 <= value <= 2.0): + raise ValueError( + f"Speech pitch must be between 0.0 and 2.0, got {value}" + ) + elif setting == "volume": + if not (0.0 <= value <= 1.5): + raise ValueError( + f"Speech volume must be between 0.0 and 1.5, got {value}" + ) + elif setting == "driver": + valid_drivers = [ + "speechdDriver", + "genericDriver", + "dummyDriver", + ] + if value not in valid_drivers: + raise ValueError( + f"Invalid speech driver: {value}. Valid options: {valid_drivers}" + ) + + # Sound settings validation + elif section == "sound": + if setting == "volume": + if not (0.0 <= value <= 1.5): + raise ValueError( + f"Sound volume must be between 0.0 and 1.5, got {value}" + ) + elif setting == "driver": + valid_drivers = [ + "genericDriver", + "gstreamerDriver", + "dummyDriver", + ] + if value not in valid_drivers: + raise ValueError( + f"Invalid sound driver: {value}. Valid options: {valid_drivers}" + ) + + # Screen settings validation + elif section == "screen": + if setting == "driver": + valid_drivers = ["vcsaDriver", "ptyDriver", "dummyDriver"] + if value not in valid_drivers: + raise ValueError( + f"Invalid screen driver: {value}. Valid options: {valid_drivers}" + ) + + # Input settings validation + elif section == "keyboard": + if setting == "driver": + valid_drivers = [ + "evdevDriver", + "x11Driver", + "dummyDriver", + ] + if value not in valid_drivers: + raise ValueError( + f"Invalid input driver: {value}. Valid options: {valid_drivers}" + ) + + # General settings validation + elif section == "general": + if setting == "debug_level": + if not (0 <= value <= 3): + raise ValueError( + f"Debug level must be between 0 and 3, got {value}" + ) + + def parse_setting_args(self, settingArgs): + if settingArgs is None: + return + for optionElem in settingArgs.split(";"): + setting_val_list = [] + section_option_list = [] + section = "" + option = "" + value = "" + setting_val_list = optionElem.split("=", 1) + if len(setting_val_list) != 2: + continue + if "#" in setting_val_list[0]: + section_option_list = setting_val_list[0].split("#", 1) + elif "." in setting_val_list[0]: + section_option_list = setting_val_list[0].split(".", 1) + elif "," in setting_val_list[0]: + section_option_list = setting_val_list[0].split(",", 1) + elif "!" in setting_val_list[0]: + section_option_list = setting_val_list[0].split("!", 1) + else: + continue + if len(section_option_list) != 2: + continue + + section = str(section_option_list[0]) + option = str(section_option_list[1]) + value = str(setting_val_list[1]) + self.set_option_arg_dict(section, option, value) + + def init_fenrir_config( + self, cliArgs, fenrir_manager=None, environment=environment.environment + ): + settings_root = "/etc/fenrirscreenreader/" + settings_file = cliArgs.setting + sound_root = "/usr/share/sounds/fenrirscreenreader/" + # get fenrir settings root + if not os.path.exists(settings_root): + if os.path.exists(fenrir_path + "/../../config/"): + settings_root = fenrir_path + "/../../config/" + else: + return None + # get settings file + if settings_file is None or not os.path.exists(settings_file): + if os.path.exists(settings_root + "/settings/settings.conf"): + settings_file = settings_root + "/settings/settings.conf" + else: + return None + # get sound themes root + if not os.path.exists(sound_root): + if os.path.exists(fenrir_path + "/../../config/sound/"): + sound_root = fenrir_path + "/../../config/sound/" + + environment["runtime"]["SettingsManager"] = self + environment["runtime"]["SettingsManager"].initialize(environment) + + valid_config = environment["runtime"]["SettingsManager"].load_settings( + settings_file + ) + if not valid_config: + return None + if cliArgs.options != "": + self.parse_setting_args(cliArgs.options) + if cliArgs.debug: + self.set_setting("general", "debug_level", 3) + if cliArgs.print: + self.set_setting("general", "debug_level", 3) + self.set_setting("general", "debug_mode", "PRINT") + if cliArgs.x11: + self.set_setting("screen", "driver", "ptyDriver") + self.set_setting("keyboard", "driver", "x11Driver") + if cliArgs.x11_window_id: + self.set_setting( + "keyboard", "x11_window_id", cliArgs.x11_window_id + ) + + self.set_fenrir_keys(self.get_setting("general", "fenrir_keys")) + self.set_script_keys(self.get_setting("general", "script_keys")) + + environment["runtime"]["DebugManager"] = debugManager.DebugManager( + self.env["runtime"]["SettingsManager"].get_setting( + "general", "debug_file" + ) + ) + environment["runtime"]["DebugManager"].initialize(environment) + + if cliArgs.force_all_screens: + environment["runtime"]["force_all_screens"] = True + + if cliArgs.ignore_screen: + current_ignore_screen = self.get_setting("screen", "ignore_screen") + if current_ignore_screen: + ignore_screens = ( + current_ignore_screen.split(",") + cliArgs.ignore_screen + ) + else: + ignore_screens = cliArgs.ignore_screen + self.set_setting( + "screen", "ignore_screen", ",".join(ignore_screens) + ) + + if not os.path.exists( + self.get_setting("sound", "theme") + "/soundicons.conf" + ): + if os.path.exists(sound_root + self.get_setting("sound", "theme")): + self.set_setting( + "sound", + "theme", + sound_root + self.get_setting("sound", "theme"), + ) + if os.path.exists( + self.get_setting("sound", "theme") + "/soundicons.conf" + ): + environment["runtime"]["SettingsManager"].load_sound_icons( + self.get_setting("sound", "theme"), environment + ) + else: + environment["runtime"]["SettingsManager"].load_sound_icons( + self.get_setting("sound", "theme"), environment + ) + + environment["runtime"][ + "PunctuationManager" + ] = punctuationManager.PunctuationManager() + environment["runtime"]["PunctuationManager"].initialize(environment) + + environment["runtime"]["TextManager"] = textManager.TextManager() + environment["runtime"]["TextManager"].initialize(environment) + + if not os.path.exists( + self.get_setting("general", "punctuation_profile") + ): + if os.path.exists( + settings_root + + "punctuation/" + + self.get_setting("general", "punctuation_profile") + ): + self.set_setting( + "general", + "punctuation_profile", + settings_root + + "punctuation/" + + self.get_setting("general", "punctuation_profile"), + ) + environment["runtime"]["PunctuationManager"].load_dicts( + self.get_setting("general", "punctuation_profile") + ) + if os.path.exists( + settings_root + + "punctuation/" + + self.get_setting("general", "punctuation_profile") + + ".conf" + ): + self.set_setting( + "general", + "punctuation_profile", + settings_root + + "punctuation/" + + self.get_setting("general", "punctuation_profile") + + ".conf", + ) + environment["runtime"]["PunctuationManager"].load_dicts( + self.get_setting("general", "punctuation_profile") + ) + else: + environment["runtime"]["PunctuationManager"].load_dicts( + self.get_setting("general", "punctuation_profile") + ) + + if fenrir_manager: + environment["runtime"]["FenrirManager"] = fenrir_manager + + environment["runtime"]["MemoryManager"] = memoryManager.MemoryManager() + environment["runtime"]["MemoryManager"].initialize(environment) + + environment["runtime"][ + "AttributeManager" + ] = attributeManager.AttributeManager() + environment["runtime"]["AttributeManager"].initialize(environment) + + environment["runtime"]["EventManager"] = eventManager.EventManager() + environment["runtime"]["EventManager"].initialize(environment) + + environment["runtime"][ + "ProcessManager" + ] = processManager.ProcessManager() + environment["runtime"]["ProcessManager"].initialize(environment) + + environment["runtime"]["OutputManager"] = outputManager.OutputManager() + environment["runtime"]["OutputManager"].initialize(environment) + + environment["runtime"]["InputManager"] = inputManager.InputManager() + environment["runtime"]["InputManager"].initialize(environment) + + environment["runtime"]["ScreenManager"] = screenManager.ScreenManager() + environment["runtime"]["ScreenManager"].initialize(environment) + + environment["runtime"][ + "CommandManager" + ] = commandManager.CommandManager() + environment["runtime"]["CommandManager"].initialize(environment) + + environment["runtime"]["HelpManager"] = helpManager.HelpManager() + environment["runtime"]["HelpManager"].initialize(environment) + + environment["runtime"]["RemoteManager"] = remoteManager.RemoteManager() + environment["runtime"]["RemoteManager"].initialize(environment) + + environment["runtime"][ + "DiffReviewManager" + ] = diffReviewManager.DiffReviewManager() + environment["runtime"]["DiffReviewManager"].initialize(environment) + + if not os.path.exists( + self.get_setting("keyboard", "keyboard_layout") + ): + if os.path.exists( + settings_root + + "keyboard/" + + self.get_setting("keyboard", "keyboard_layout") + ): + self.set_setting( + "keyboard", + "keyboard_layout", + settings_root + + "keyboard/" + + self.get_setting("keyboard", "keyboard_layout"), + ) + environment["runtime"]["InputManager"].load_shortcuts( + self.get_setting("keyboard", "keyboard_layout") + ) + if os.path.exists( + settings_root + + "keyboard/" + + self.get_setting("keyboard", "keyboard_layout") + + ".conf" + ): + self.set_setting( + "keyboard", + "keyboard_layout", + settings_root + + "keyboard/" + + self.get_setting("keyboard", "keyboard_layout") + + ".conf", + ) + environment["runtime"]["InputManager"].load_shortcuts( + self.get_setting("keyboard", "keyboard_layout") + ) + else: + environment["runtime"]["InputManager"].load_shortcuts( + self.get_setting("keyboard", "keyboard_layout") + ) + + environment["runtime"]["CursorManager"] = cursorManager.CursorManager() + environment["runtime"]["CursorManager"].initialize(environment) + environment["runtime"][ + "ApplicationManager" + ] = applicationManager.ApplicationManager() + environment["runtime"]["ApplicationManager"].initialize(environment) + environment["runtime"]["TextManager"] = textManager.TextManager() + environment["runtime"]["TextManager"].initialize(environment) + environment["runtime"]["TableManager"] = tableManager.TableManager() + environment["runtime"]["TableManager"].initialize(environment) + environment["runtime"][ + "BarrierManager" + ] = barrierManager.BarrierManager() + environment["runtime"]["BarrierManager"].initialize(environment) + environment["runtime"]["SayAllManager"] = sayAllManager.SayAllManager() + environment["runtime"]["SayAllManager"].initialize(environment) + environment["runtime"]["VmenuManager"] = vmenuManager.VmenuManager() + environment["runtime"]["VmenuManager"].initialize(environment) + environment["runtime"][ + "QuickMenuManager" + ] = quickMenuManager.QuickMenuManager() + environment["runtime"]["QuickMenuManager"].initialize(environment) + + environment["runtime"][ + "ReadAllManager" + ] = readAllManager.ReadAllManager() + environment["runtime"]["ReadAllManager"].initialize(environment) + + # only possible after having input and screen managers with clean + # buffer + environment["runtime"]["InputManager"].write_event_buffer() + environment["runtime"]["InputManager"].handle_device_grab(force=True) + + environment["runtime"]["DebugManager"].write_debug_out( + r"/-------environment-------/", + debug.DebugLevel.INFO, + on_any_level=True, + ) + environment["runtime"]["DebugManager"].write_debug_out( + str(environment), debug.DebugLevel.INFO, on_any_level=True + ) + environment["runtime"]["DebugManager"].write_debug_out( + r"/-------settings.conf-------/", + debug.DebugLevel.INFO, + on_any_level=True, + ) + environment["runtime"]["DebugManager"].write_debug_out( + str(environment["settings"]._sections), + debug.DebugLevel.INFO, + on_any_level=True, + ) + environment["runtime"]["DebugManager"].write_debug_out( + r"/-------self.settingArgDict-------/", + debug.DebugLevel.INFO, + on_any_level=True, + ) + environment["runtime"]["DebugManager"].write_debug_out( + str(self.settingArgDict), debug.DebugLevel.INFO, on_any_level=True + ) + self.bindingsBackup = environment["bindings"].copy() + + return environment diff --git a/src/fenrirscreenreader/inputDriver/x11Driver.py b/src/fenrirscreenreader/inputDriver/x11Driver.py index b8934d61..8ab406ff 100644 --- a/src/fenrirscreenreader/inputDriver/x11Driver.py +++ b/src/fenrirscreenreader/inputDriver/x11Driver.py @@ -437,8 +437,8 @@ class driver(inputDriver): self.refresh_interesting_keys() passive_grabs = self.build_passive_grabs() failed_before = self.failed_grabs - for key_name, modifier_mask in passive_grabs: - self.grab_key_name(key_name, modifier_mask) + for key_name, modifier_mask, include_num_lock in passive_grabs: + self.grab_key_name(key_name, modifier_mask, include_num_lock) self.display.flush() self.grab_signature = signature self.write_debug( @@ -454,9 +454,10 @@ class driver(inputDriver): def build_passive_grabs(self): grabs = set() for fenrir_key in self.env["input"]["fenrir_key"]: - grabs.add((fenrir_key, 0)) + grabs.add((fenrir_key, 0, True)) for script_key in self.env["input"]["script_key"]: - grabs.add((script_key, 0)) + grabs.add((script_key, 0, True)) + grabs.add(("KEY_NUMLOCK", 0, True)) for shortcut in self.env.get("rawBindings", {}).values(): keys = shortcut[1] expanded_keys = self.expand_special_keys(keys) @@ -470,12 +471,40 @@ class driver(inputDriver): final_key = non_modifier_keys[-1] if "KEY_FENRIR" in keys: for fenrir_key in self.env["input"]["fenrir_key"]: - grabs.add((fenrir_key, modifier_mask)) + grabs.add((fenrir_key, modifier_mask, True)) + fenrir_modifier_mask = self.modifier_masks.get( + fenrir_key, 0 + ) + if fenrir_modifier_mask: + grabs.add( + ( + final_key, + modifier_mask | fenrir_modifier_mask, + not final_key.startswith("KEY_KP"), + ) + ) elif "KEY_SCRIPT" in keys: for script_key in self.env["input"]["script_key"]: - grabs.add((script_key, modifier_mask)) + grabs.add((script_key, modifier_mask, True)) + script_modifier_mask = self.modifier_masks.get( + script_key, 0 + ) + if script_modifier_mask: + grabs.add( + ( + final_key, + modifier_mask | script_modifier_mask, + not final_key.startswith("KEY_KP"), + ) + ) else: - grabs.add((final_key, modifier_mask)) + grabs.add( + ( + final_key, + modifier_mask, + not final_key.startswith("KEY_KP"), + ) + ) return grabs def expand_special_keys(self, keys): @@ -495,7 +524,9 @@ class driver(inputDriver): modifier_mask |= self.modifier_masks.get(key_name, 0) return modifier_mask - def grab_key_name(self, key_name, modifier_mask=0): + def grab_key_name( + self, key_name, modifier_mask=0, include_num_lock=True + ): keysym_names = self.key_name_to_keysym_names(key_name) for keysym_name in keysym_names: keysym = XK.string_to_keysym(keysym_name) @@ -504,7 +535,9 @@ class driver(inputDriver): keycode = self.display.keysym_to_keycode(keysym) if not keycode: continue - for effective_mask in self.optional_modifier_masks(modifier_mask): + for effective_mask in self.optional_modifier_masks( + modifier_mask, include_num_lock + ): try: self.window.grab_key( keycode, @@ -524,9 +557,9 @@ class driver(inputDriver): debug.DebugLevel.WARNING, ) - def optional_modifier_masks(self, modifier_mask): + def optional_modifier_masks(self, modifier_mask, include_num_lock=True): optional_masks = [0, X.LockMask] - if self.num_lock_mask: + if include_num_lock and self.num_lock_mask: optional_masks += [self.num_lock_mask, self.num_lock_mask | X.LockMask] return {modifier_mask | optional for optional in optional_masks} @@ -610,6 +643,15 @@ class driver(inputDriver): self.ungrab_all_devices() def get_led_state(self, led=0): + try: + pointer = self.root.query_pointer() + mask = getattr(pointer, "mask", 0) + if led == 0: + return bool(self.num_lock_mask and mask & self.num_lock_mask) + if led == 1: + return bool(mask & X.LockMask) + except Exception: + pass return False def set_led_state(self, led_dict):