# Orca # # Copyright 2005-2008 Sun Microsystems Inc. # Copyright 2011-2026 Igalia, S.L. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the # Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Boston MA 02110-1301 USA. # pylint: disable=too-many-public-methods # pylint: disable=too-many-lines """Provides braille presentation support.""" from __future__ import annotations import os import time from enum import Enum from typing import TYPE_CHECKING, Any from . import ( braille, braille_monitor, brltablenames, cmdnames, command_manager, dbus_service, debug, document_presenter, focus_manager, gsettings_registry, guilabels, input_event, input_event_manager, messages, preferences_grid_base, ) from .braille_generator import BrailleGeneratorContext from .orca_platform import tablesdir # pylint: disable=import-error if TYPE_CHECKING: import gi from .generator import WhereAmI gi.require_version("Atspi", "2.0") from gi.repository import Atspi from .scripts import default @gsettings_registry.get_registry().gsettings_enum( "org.gnome.Orca.VerbosityLevel", values={"brief": 0, "verbose": 1}, ) class VerbosityLevel(Enum): """Verbosity level enumeration.""" BRIEF = 0 VERBOSE = 1 @property def string_name(self) -> str: """Returns the lowercase string name for this enum value.""" return self.name.lower() @gsettings_registry.get_registry().gsettings_enum( "org.gnome.Orca.BrailleIndicator", values={"none": 0, "dot7": 64, "dot8": 128, "dots78": 192}, ) class BrailleIndicator(Enum): """Braille indicator enumeration.""" NONE = 0x00 DOT7 = 0x40 DOT8 = 0x80 DOTS78 = 0xC0 @property def string_name(self) -> str: """Returns the lowercase string name for this enum value.""" return self.name.lower() @gsettings_registry.get_registry().gsettings_enum( "org.gnome.Orca.ProgressBarVerbosity", values={"all": 0, "application": 1, "window": 2}, ) class ProgressBarVerbosity(Enum): """Progress bar verbosity level enumeration.""" ALL = 0 APPLICATION = 1 WINDOW = 2 class BrailleVerbosityPreferencesGrid(preferences_grid_base.AutoPreferencesGrid): """GtkGrid containing the Braille Verbosity preferences page.""" def __init__(self, presenter: BraillePresenter) -> None: self._presenter = presenter controls = [ preferences_grid_base.BooleanPreferenceControl( label=guilabels.OBJECT_PRESENTATION_IS_DETAILED, getter=presenter._get_verbosity_is_detailed, setter=presenter._set_verbosity_is_detailed, ), preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_SHOW_CONTEXT, getter=presenter.get_display_ancestors, setter=presenter.set_display_ancestors, prefs_key=BraillePresenter.KEY_DISPLAY_ANCESTORS, ), preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_ABBREVIATED_ROLE_NAMES, getter=presenter._get_use_abbreviated_rolenames, setter=presenter._set_use_abbreviated_rolenames, ), preferences_grid_base.BooleanPreferenceControl( label=guilabels.PRESENT_OBJECT_MNEMONICS, getter=presenter.get_present_mnemonics, setter=presenter.set_present_mnemonics, prefs_key=BraillePresenter.KEY_PRESENT_MNEMONICS, ), ] super().__init__(guilabels.VERBOSITY, controls) def save_settings(self, profile: str = "", app_name: str = "") -> dict[str, Any]: """Save settings, writing enum values for verbosity and rolename style.""" result = super().save_settings(profile, app_name) result[BraillePresenter.KEY_VERBOSITY_LEVEL] = self._presenter.get_verbosity_level() result[BraillePresenter.KEY_ROLENAME_STYLE] = VerbosityLevel[ self._presenter.get_rolename_style().upper() ].value return result class BrailleDisplaySettingsPreferencesGrid(preferences_grid_base.AutoPreferencesGrid): """GtkGrid containing the Braille Display Settings preferences page.""" def __init__(self, presenter: BraillePresenter) -> None: table_dict = presenter.get_contraction_tables_dict() table_names = sorted(table_dict.keys()) if table_dict else [] table_paths = [table_dict[name] for name in table_names] if table_dict else [] self._enable_contracted_control = preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_ENABLE_CONTRACTED_BRAILLE, getter=presenter.get_contracted_braille_is_enabled, setter=presenter.set_contracted_braille_is_enabled, prefs_key=BraillePresenter.KEY_CONTRACTED_BRAILLE, ) controls: list[ preferences_grid_base.BooleanPreferenceControl | preferences_grid_base.EnumPreferenceControl ] = [ preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_ENABLE_END_OF_LINE_SYMBOL, getter=presenter.get_end_of_line_indicator_is_enabled, setter=presenter.set_end_of_line_indicator_is_enabled, prefs_key=BraillePresenter.KEY_END_OF_LINE_INDICATOR, ), preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_ENABLE_WORD_WRAP, getter=presenter.get_word_wrap_is_enabled, setter=presenter.set_word_wrap_is_enabled, prefs_key=BraillePresenter.KEY_WORD_WRAP, ), self._enable_contracted_control, preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_COMPUTER_BRAILLE_AT_CURSOR, getter=presenter.get_computer_braille_at_cursor_is_enabled, setter=presenter.set_computer_braille_at_cursor_is_enabled, prefs_key=BraillePresenter.KEY_COMPUTER_BRAILLE_AT_CURSOR, determine_sensitivity=self._contracted_enabled, ), preferences_grid_base.EnumPreferenceControl( label=guilabels.BRAILLE_CONTRACTION_TABLE, options=table_names, values=table_paths, getter=presenter.get_contraction_table_path, setter=presenter.set_contraction_table_from_path, prefs_key=BraillePresenter.KEY_CONTRACTION_TABLE, determine_sensitivity=self._contracted_enabled, ), preferences_grid_base.EnumPreferenceControl( label=guilabels.BRAILLE_HYPERLINK_INDICATOR, options=[ guilabels.BRAILLE_DOT_NONE, guilabels.BRAILLE_DOT_7, guilabels.BRAILLE_DOT_8, guilabels.BRAILLE_DOT_7_8, ], values=[ BrailleIndicator.NONE.value, BrailleIndicator.DOT7.value, BrailleIndicator.DOT8.value, BrailleIndicator.DOTS78.value, ], getter=presenter._get_link_indicator_as_int, setter=presenter.set_link_indicator_from_int, prefs_key=BraillePresenter.KEY_LINK_INDICATOR, member_of=guilabels.BRAILLE_INDICATORS, ), preferences_grid_base.EnumPreferenceControl( label=guilabels.BRAILLE_SELECTION_INDICATOR, options=[ guilabels.BRAILLE_DOT_NONE, guilabels.BRAILLE_DOT_7, guilabels.BRAILLE_DOT_8, guilabels.BRAILLE_DOT_7_8, ], values=[ BrailleIndicator.NONE.value, BrailleIndicator.DOT7.value, BrailleIndicator.DOT8.value, BrailleIndicator.DOTS78.value, ], getter=presenter._get_selector_indicator_as_int, setter=presenter.set_selector_indicator_from_int, prefs_key=BraillePresenter.KEY_SELECTOR_INDICATOR, member_of=guilabels.BRAILLE_INDICATORS, ), preferences_grid_base.EnumPreferenceControl( label=guilabels.BRAILLE_TEXT_ATTRIBUTES_INDICATOR, options=[ guilabels.BRAILLE_DOT_NONE, guilabels.BRAILLE_DOT_7, guilabels.BRAILLE_DOT_8, guilabels.BRAILLE_DOT_7_8, ], values=[ BrailleIndicator.NONE.value, BrailleIndicator.DOT7.value, BrailleIndicator.DOT8.value, BrailleIndicator.DOTS78.value, ], getter=presenter._get_text_attributes_indicator_as_int, setter=presenter.set_text_attributes_indicator_from_int, prefs_key=BraillePresenter.KEY_TEXT_ATTRIBUTES_INDICATOR, member_of=guilabels.BRAILLE_INDICATORS, ), ] super().__init__(guilabels.BRAILLE_DISPLAY_SETTINGS, controls) def _contracted_enabled(self) -> bool: """Check if contracted braille is enabled in the UI.""" widget = self.get_widget_for_control(self._enable_contracted_control) return widget.get_active() if widget else True class BrailleFlashMessagesPreferencesGrid(preferences_grid_base.AutoPreferencesGrid): """GtkGrid containing the Braille Flash Messages preferences page.""" def __init__(self, presenter: BraillePresenter) -> None: self._presenter = presenter self._flash_persistent_control = preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_MESSAGES_ARE_PERSISTENT, getter=presenter.get_flash_messages_are_persistent, setter=presenter.set_flash_messages_are_persistent, prefs_key=BraillePresenter.KEY_FLASH_MESSAGES_PERSISTENT, ) controls: list[ preferences_grid_base.BooleanPreferenceControl | preferences_grid_base.IntRangePreferenceControl ] = [ preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_ENABLE_FLASH_MESSAGES, getter=presenter.get_flash_messages_are_enabled, setter=presenter.set_flash_messages_are_enabled, prefs_key=BraillePresenter.KEY_FLASH_MESSAGES, ), preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_MESSAGES_ARE_DETAILED, getter=presenter.get_flash_messages_are_detailed, setter=presenter.set_flash_messages_are_detailed, prefs_key=BraillePresenter.KEY_FLASH_MESSAGES_DETAILED, ), self._flash_persistent_control, preferences_grid_base.IntRangePreferenceControl( label=guilabels.BRAILLE_DURATION_SECS, minimum=1, maximum=100, getter=presenter._get_flash_duration_seconds, setter=presenter._set_flash_duration_seconds, determine_sensitivity=self._flash_not_persistent, ), ] super().__init__(guilabels.BRAILLE_FLASH_MESSAGES, controls) def _flash_not_persistent(self) -> bool: """Check if flash messages are not persistent in the UI.""" widget = self.get_widget_for_control(self._flash_persistent_control) return not widget.get_active() if widget else True def save_settings(self, profile: str = "", app_name: str = "") -> dict: """Persist staged values, including flash-message-duration in milliseconds.""" result = super().save_settings(profile, app_name) result[BraillePresenter.KEY_FLASH_MESSAGE_DURATION] = ( self._presenter.get_flash_message_duration() ) return result class BrailleProgressBarsPreferencesGrid(preferences_grid_base.AutoPreferencesGrid): """GtkGrid containing the Braille Progress Bars preferences page.""" def __init__(self, presenter: BraillePresenter) -> None: controls: list[preferences_grid_base.ControlType] = [ preferences_grid_base.BooleanPreferenceControl( label=guilabels.GENERAL_BRAILLE_UPDATES, getter=presenter.get_braille_progress_bar_updates, setter=presenter.set_braille_progress_bar_updates, prefs_key=BraillePresenter.KEY_BRAILLE_PROGRESS_BAR_UPDATES, ), preferences_grid_base.IntRangePreferenceControl( label=guilabels.GENERAL_FREQUENCY_SECS, getter=presenter.get_progress_bar_braille_interval, setter=presenter.set_progress_bar_braille_interval, prefs_key=BraillePresenter.KEY_PROGRESS_BAR_BRAILLE_INTERVAL, minimum=0, maximum=100, ), preferences_grid_base.EnumPreferenceControl( label=guilabels.GENERAL_APPLIES_TO, getter=presenter.get_progress_bar_braille_verbosity, setter=presenter.set_progress_bar_braille_verbosity, prefs_key=BraillePresenter.KEY_PROGRESS_BAR_BRAILLE_VERBOSITY, options=[ guilabels.PROGRESS_BAR_ALL, guilabels.PROGRESS_BAR_APPLICATION, guilabels.PROGRESS_BAR_WINDOW, ], values=[ ProgressBarVerbosity.ALL.value, ProgressBarVerbosity.APPLICATION.value, ProgressBarVerbosity.WINDOW.value, ], ), ] super().__init__(guilabels.PROGRESS_BARS, controls) class BrailleOSDPreferencesGrid(preferences_grid_base.AutoPreferencesGrid): """GtkGrid containing the braille on-screen display preferences page.""" def __init__(self, presenter: BraillePresenter) -> None: controls: list[preferences_grid_base.ControlType] = [ preferences_grid_base.IntRangePreferenceControl( label=guilabels.BRAILLE_MONITOR_CELL_COUNT, getter=presenter.get_monitor_cell_count, setter=presenter.set_monitor_cell_count, prefs_key=BraillePresenter.KEY_MONITOR_CELL_COUNT, minimum=1, maximum=80, ), preferences_grid_base.BooleanPreferenceControl( label=guilabels.BRAILLE_MONITOR_SHOW_DOTS, getter=presenter.get_monitor_show_dots, setter=presenter.set_monitor_show_dots, prefs_key=BraillePresenter.KEY_MONITOR_SHOW_DOTS, ), preferences_grid_base.ColorPreferenceControl( label=guilabels.BRAILLE_MONITOR_FOREGROUND, getter=presenter.get_monitor_foreground, setter=presenter.set_monitor_foreground, prefs_key=BraillePresenter.KEY_MONITOR_FOREGROUND, ), preferences_grid_base.ColorPreferenceControl( label=guilabels.BRAILLE_MONITOR_BACKGROUND, getter=presenter.get_monitor_background, setter=presenter.set_monitor_background, prefs_key=BraillePresenter.KEY_MONITOR_BACKGROUND, ), ] super().__init__( guilabels.ON_SCREEN_DISPLAY, controls, info_message=guilabels.BRAILLE_MONITOR_INFO, ) # pylint: disable-next=too-many-instance-attributes class BraillePreferencesGrid(preferences_grid_base.PreferencesGridBase): """GtkGrid containing the Braille preferences page with nested stack navigation.""" def __init__( self, presenter: BraillePresenter, title_change_callback: preferences_grid_base.Callable[[str], None] | None = None, ) -> None: super().__init__(guilabels.BRAILLE) self._presenter = presenter self._initializing = True self._title_change_callback = title_change_callback self._verbosity_grid = BrailleVerbosityPreferencesGrid(presenter) self._display_settings_grid = BrailleDisplaySettingsPreferencesGrid(presenter) self._flash_messages_grid = BrailleFlashMessagesPreferencesGrid(presenter) self._progress_bars_grid = BrailleProgressBarsPreferencesGrid(presenter) self._osd_grid = BrailleOSDPreferencesGrid(presenter) self._build() self._initializing = False def _build(self) -> None: """Build the nested stack UI.""" row = 0 categories = [ (guilabels.VERBOSITY, "verbosity", self._verbosity_grid), (guilabels.BRAILLE_DISPLAY_SETTINGS, "display_settings", self._display_settings_grid), (guilabels.BRAILLE_FLASH_MESSAGES, "flash_messages", self._flash_messages_grid), (guilabels.PROGRESS_BARS, "progress-bars", self._progress_bars_grid), (guilabels.ON_SCREEN_DISPLAY, "osd", self._osd_grid), ] enable_listbox, stack, _categories_listbox = self._create_multi_page_stack( enable_label=guilabels.BRAILLE_ENABLE_BRAILLE_SUPPORT, enable_getter=self._presenter.get_braille_is_enabled, enable_setter=self._presenter.set_braille_is_enabled, categories=categories, title_change_callback=self._title_change_callback, main_title=guilabels.BRAILLE, ) self.attach(enable_listbox, 0, row, 1, 1) row += 1 self.attach(stack, 0, row, 1, 1) def on_becoming_visible(self) -> None: """Reset to the categories view when this grid becomes visible.""" self.multipage_on_becoming_visible() def reload(self) -> None: """Fetch fresh values and update UI.""" self._initializing = True self._has_unsaved_changes = False self._verbosity_grid.reload() self._display_settings_grid.reload() self._flash_messages_grid.reload() self._progress_bars_grid.reload() self._osd_grid.reload() self._initializing = False def save_settings(self, profile: str = "", app_name: str = "") -> dict: """Persist staged values.""" assert self._multipage_enable_switch is not None result: dict[str, Any] = {} result[BraillePresenter.KEY_ENABLED] = self._multipage_enable_switch.get_active() result.update(self._verbosity_grid.save_settings()) result.update(self._display_settings_grid.save_settings()) result.update(self._flash_messages_grid.save_settings()) result.update(self._progress_bars_grid.save_settings()) result.update(self._osd_grid.save_settings()) if profile: skip = not app_name and profile == "default" gsettings_registry.get_registry().save_schema( "braille", result, profile, app_name, skip, ) return result def refresh(self) -> None: """Update widgets from staged values.""" self._initializing = True self._verbosity_grid.refresh() self._display_settings_grid.refresh() self._flash_messages_grid.refresh() self._progress_bars_grid.refresh() self._osd_grid.refresh() self._initializing = False def has_changes(self) -> bool: """Return True if there are unsaved changes.""" return ( self._has_unsaved_changes or self._verbosity_grid.has_changes() or self._display_settings_grid.has_changes() or self._flash_messages_grid.has_changes() or self._progress_bars_grid.has_changes() or self._osd_grid.has_changes() ) @gsettings_registry.get_registry().gsettings_schema("org.gnome.Orca.Braille", name="braille") class BraillePresenter: """Provides braille presentation support.""" _SCHEMA = "braille" KEY_ENABLED = "enabled" KEY_VERBOSITY_LEVEL = "verbosity-level" KEY_ROLENAME_STYLE = "rolename-style" KEY_PRESENT_MNEMONICS = "present-mnemonics" KEY_DISPLAY_ANCESTORS = "display-ancestors" KEY_BRAILLE_PROGRESS_BAR_UPDATES = "braille-progress-bar-updates" KEY_PROGRESS_BAR_BRAILLE_INTERVAL = "progress-bar-braille-interval" KEY_PROGRESS_BAR_BRAILLE_VERBOSITY = "progress-bar-braille-verbosity" KEY_CONTRACTED_BRAILLE = "contracted-braille" KEY_COMPUTER_BRAILLE_AT_CURSOR = "computer-braille-at-cursor" KEY_CONTRACTION_TABLE = "contraction-table" KEY_END_OF_LINE_INDICATOR = "end-of-line-indicator" KEY_WORD_WRAP = "word-wrap" KEY_FLASH_MESSAGES = "flash-messages" KEY_FLASH_MESSAGE_DURATION = "flash-message-duration" KEY_FLASH_MESSAGES_PERSISTENT = "flash-messages-persistent" KEY_FLASH_MESSAGES_DETAILED = "flash-messages-detailed" KEY_SELECTOR_INDICATOR = "selector-indicator" KEY_LINK_INDICATOR = "link-indicator" KEY_TEXT_ATTRIBUTES_INDICATOR = "text-attributes-indicator" KEY_MONITOR_CELL_COUNT = "monitor-cell-count" KEY_MONITOR_SHOW_DOTS = "monitor-show-dots" KEY_MONITOR_FOREGROUND = "monitor-foreground" KEY_MONITOR_BACKGROUND = "monitor-background" def _get_setting(self, key: str, gtype: str, default: Any) -> Any: """Returns the dconf value for key, or default if not in dconf.""" return gsettings_registry.get_registry().layered_lookup( self._SCHEMA, key, gtype, default=default, ) def __init__(self) -> None: msg = "BRAILLE PRESENTER: Registering D-Bus commands." debug.print_message(debug.LEVEL_INFO, msg, True) controller = dbus_service.get_remote_controller() controller.register_decorated_module("BraillePresenter", self) self._command_names: dict[int, str] | None = None self._table_names: dict[str, str] | None = None self._monitor: braille_monitor.BrailleMonitor | None = None self._monitor_enabled_override: bool | None = None self._initialized = False self._progress_bar_cache: dict = {} def set_up_commands(self) -> None: """Sets up commands with CommandManager.""" if self._initialized: return self._initialized = True manager = command_manager.get_manager() manager.add_command( command_manager.KeyboardCommand( "toggle_braille_monitor", self.toggle_monitor, guilabels.BRAILLE, cmdnames.TOGGLE_BRAILLE_MONITOR, ), ) msg = "BRAILLE PRESENTER: Commands set up." debug.print_message(debug.LEVEL_INFO, msg, True) @dbus_service.command def toggle_monitor( self, script: default.Script | None = None, event: input_event.InputEvent | None = None, notify_user: bool = True, ) -> bool: """Toggles the braille monitor on and off.""" tokens = [ "BRAILLE PRESENTER: toggle_monitor. Script:", script, "Event:", event, "notify_user:", notify_user, ] debug.print_tokens(debug.LEVEL_INFO, tokens, True) from . import presentation_manager # pylint: disable=import-outside-toplevel if self.get_monitor_is_enabled(): self.set_monitor_is_enabled(False) if script is not None and notify_user: presentation_manager.get_manager().present_message( messages.BRAILLE_MONITOR_DISABLED, ) else: self.set_monitor_is_enabled(True) if script is not None and notify_user: presentation_manager.get_manager().present_message(messages.BRAILLE_MONITOR_ENABLED) return True @staticmethod def _build_table_names() -> dict[str, str]: """Returns display names for braille translation tables.""" return { "Cz-Cz-g1": brltablenames.CZ_CZ_G1, "Es-Es-g1": brltablenames.ES_ES_G1, "Fr-Ca-g2": brltablenames.FR_CA_G2, "Fr-Fr-g2": brltablenames.FR_FR_G2, "Lv-Lv-g1": brltablenames.LV_LV_G1, "Nl-Nl-g1": brltablenames.NL_NL_G1, "No-No-g0": brltablenames.NO_NO_G0, "No-No-g1": brltablenames.NO_NO_G1, "No-No-g2": brltablenames.NO_NO_G2, "No-No-g3": brltablenames.NO_NO_G3, "Pl-Pl-g1": brltablenames.PL_PL_G1, "Pt-Pt-g1": brltablenames.PT_PT_G1, "Se-Se-g1": brltablenames.SE_SE_G1, "ar-ar-g1": brltablenames.AR_AR_G1, "cy-cy-g1": brltablenames.CY_CY_G1, "cy-cy-g2": brltablenames.CY_CY_G2, "de-de-g0": brltablenames.DE_DE_G0, "de-de-g1": brltablenames.DE_DE_G1, "de-de-g2": brltablenames.DE_DE_G2, "en-GB-g2": brltablenames.EN_GB_G2, "en-gb-g1": brltablenames.EN_GB_G1, "en-us-g1": brltablenames.EN_US_G1, "en-us-g2": brltablenames.EN_US_G2, "fr-ca-g1": brltablenames.FR_CA_G1, "fr-fr-g1": brltablenames.FR_FR_G1, "gr-gr-g1": brltablenames.GR_GR_G1, "hi-in-g1": brltablenames.HI_IN_G1, "hu-hu-comp8": brltablenames.HU_HU_8DOT, "hu-hu-g1": brltablenames.HU_HU_G1, "hu-hu-g2": brltablenames.HU_HU_G2, "it-it-g1": brltablenames.IT_IT_G1, "nl-be-g1": brltablenames.NL_BE_G1, } def get_table_names(self) -> dict[str, str]: """Returns table aliases mapped to localized display names.""" if self._table_names is None: self._table_names = self._build_table_names() return dict(self._table_names) @staticmethod def _build_command_names() -> dict[int, str]: """Return BrlTTY command names for presentation in the UI.""" command_names: dict[int, str] = {} def add_command(command_id: int | None, label: str) -> None: if command_id is not None: command_names[command_id] = label add_command(braille.BRLAPI_KEY_CMD_HWINLT, cmdnames.BRAILLE_LINE_LEFT) add_command(braille.BRLAPI_KEY_CMD_FWINLT, cmdnames.BRAILLE_LINE_LEFT) add_command(braille.BRLAPI_KEY_CMD_FWINLTSKIP, cmdnames.BRAILLE_LINE_LEFT) add_command(braille.BRLAPI_KEY_CMD_HWINRT, cmdnames.BRAILLE_LINE_RIGHT) add_command(braille.BRLAPI_KEY_CMD_FWINRT, cmdnames.BRAILLE_LINE_RIGHT) add_command(braille.BRLAPI_KEY_CMD_FWINRTSKIP, cmdnames.BRAILLE_LINE_RIGHT) add_command(braille.BRLAPI_KEY_CMD_LNUP, cmdnames.BRAILLE_LINE_UP) add_command(braille.BRLAPI_KEY_CMD_LNDN, cmdnames.BRAILLE_LINE_DOWN) add_command(braille.BRLAPI_KEY_CMD_FREEZE, cmdnames.BRAILLE_FREEZE) add_command(braille.BRLAPI_KEY_CMD_TOP_LEFT, cmdnames.BRAILLE_TOP_LEFT) add_command(braille.BRLAPI_KEY_CMD_BOT_LEFT, cmdnames.BRAILLE_BOTTOM_LEFT) add_command(braille.BRLAPI_KEY_CMD_HOME, cmdnames.BRAILLE_HOME) add_command(braille.BRLAPI_KEY_CMD_SIXDOTS, cmdnames.BRAILLE_SIX_DOTS) add_command(braille.BRLAPI_KEY_CMD_ROUTE, cmdnames.BRAILLE_ROUTE_CURSOR) add_command(braille.BRLAPI_KEY_CMD_CUTBEGIN, cmdnames.BRAILLE_CUT_BEGIN) add_command(braille.BRLAPI_KEY_CMD_CUTLINE, cmdnames.BRAILLE_CUT_LINE) return command_names def get_command_names(self) -> dict[int, str]: """Returns a mapping of BrlTTY command IDs to user-visible labels.""" if self._command_names is None: self._command_names = self._build_command_names() return dict(self._command_names) def create_preferences_grid( self, title_change_callback: preferences_grid_base.Callable[[str], None] | None = None, ) -> BraillePreferencesGrid: """Returns the GtkGrid containing the preferences UI.""" return BraillePreferencesGrid(self, title_change_callback) def _get_verbosity_is_detailed(self) -> bool: """Returns whether braille verbosity is set to detailed/verbose.""" return self.get_verbosity_level() == "verbose" def _set_verbosity_is_detailed(self, value: bool) -> bool: """Sets braille verbosity to detailed/verbose if True, brief if False.""" return self.set_verbosity_level("verbose" if value else "brief") def _get_use_abbreviated_rolenames(self) -> bool: """Returns whether abbreviated role names are used.""" return self.get_rolename_style() == "brief" def _set_use_abbreviated_rolenames(self, value: bool) -> bool: """Sets whether abbreviated role names are used.""" return self.set_rolename_style("brief" if value else "verbose") def _get_flash_duration_seconds(self) -> int: """Returns flash duration in seconds (converted from milliseconds).""" duration_ms = self.get_flash_message_duration() return max(1, duration_ms // 1000) def _set_flash_duration_seconds(self, value: int) -> None: """Sets flash duration in seconds (converts to milliseconds).""" duration_ms = value * 1000 self.set_flash_message_duration(duration_ms) def use_braille(self) -> bool: """Returns whether braille is to be used.""" result = self.get_braille_is_enabled() or self.get_monitor_is_enabled() if not result: msg = "BRAILLE PRESENTER: Braille is disabled." debug.print_message(debug.LEVEL_INFO, msg, True) return result @dbus_service.getter def get_monitor_is_enabled(self) -> bool: """Returns whether the braille monitor is enabled.""" return self._monitor_enabled_override or False @dbus_service.setter def set_monitor_is_enabled(self, value: bool) -> bool: """Sets whether the braille monitor is enabled.""" msg = f"BRAILLE PRESENTER: Setting enable braille monitor to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) self._monitor_enabled_override = value if not value: self.destroy_monitor() return True @gsettings_registry.get_registry().gsetting( key=KEY_MONITOR_CELL_COUNT, schema="braille", gtype="i", default=32, summary="Braille monitor cell count", migration_key="brailleMonitorCellCount", ) @dbus_service.getter def get_monitor_cell_count(self) -> int: """Returns the configured braille monitor cell count.""" return self._get_setting(self.KEY_MONITOR_CELL_COUNT, "i", 32) @dbus_service.setter def set_monitor_cell_count(self, value: int) -> bool: """Sets the braille monitor cell count.""" msg = f"BRAILLE PRESENTER: Setting braille monitor cell count to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_MONITOR_CELL_COUNT, value, ) self.destroy_monitor() return True @gsettings_registry.get_registry().gsetting( key=KEY_MONITOR_SHOW_DOTS, schema="braille", gtype="b", default=False, summary="Show Unicode braille dots in braille monitor", migration_key="brailleMonitorShowDots", ) @dbus_service.getter def get_monitor_show_dots(self) -> bool: """Returns whether the braille monitor shows Unicode braille dots.""" return self._get_setting(self.KEY_MONITOR_SHOW_DOTS, "b", False) @dbus_service.setter def set_monitor_show_dots(self, value: bool) -> bool: """Sets whether the braille monitor shows Unicode braille dots.""" msg = f"BRAILLE PRESENTER: Setting braille monitor show dots to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_MONITOR_SHOW_DOTS, value, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_MONITOR_FOREGROUND, schema="braille", gtype="s", default="#000000", summary="Braille monitor foreground color", migration_key="brailleMonitorForeground", ) @dbus_service.getter def get_monitor_foreground(self) -> str: """Returns the braille monitor foreground color.""" return self._get_setting(self.KEY_MONITOR_FOREGROUND, "s", "#000000") @dbus_service.setter def set_monitor_foreground(self, value: str) -> bool: """Sets the braille monitor foreground color.""" msg = f"BRAILLE PRESENTER: Setting braille monitor foreground to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_MONITOR_FOREGROUND, value, ) if self._monitor is not None: self._monitor.reapply_css(foreground=value) return True @gsettings_registry.get_registry().gsetting( key=KEY_MONITOR_BACKGROUND, schema="braille", gtype="s", default="#ffffff", summary="Braille monitor background color", migration_key="brailleMonitorBackground", ) @dbus_service.getter def get_monitor_background(self) -> str: """Returns the braille monitor background color.""" return self._get_setting(self.KEY_MONITOR_BACKGROUND, "s", "#ffffff") @dbus_service.setter def set_monitor_background(self, value: str) -> bool: """Sets the braille monitor background color.""" msg = f"BRAILLE PRESENTER: Setting braille monitor background to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_MONITOR_BACKGROUND, value, ) if self._monitor is not None: self._monitor.reapply_css(background=value) return True # pylint: disable-next=too-many-arguments def present_regions( self, regions: list[braille.Region], focused_region: braille.Region | None, extra_region: braille.Region | None = None, *, pan_to_cursor: bool = True, indicate_links: bool = True, stop_flash: bool = True, ) -> None: """Build a line from regions and present it as a single braille line.""" if extra_region is not None: regions = list(regions) regions.append(extra_region) focused_region = extra_region line = braille.Line() line.add_regions(regions) braille.display_line( line, focused_region, pan_to_cursor=pan_to_cursor, indicate_links=indicate_links, stop_flash=stop_flash, ) def _build_generator_context( self, where_am_i_type: WhereAmI | None = None, ) -> BrailleGeneratorContext: """Builds the settings context for braille generators.""" mgr = focus_manager.get_manager() active_mode, _obj = mgr.get_active_mode_and_object_of_interest() return BrailleGeneratorContext( enabled=self.use_braille(), verbose=self.use_verbose_braille(), focus=mgr.get_locus_of_focus(), in_say_all=mgr.in_say_all(), in_focus_mode=document_presenter.get_presenter().get_in_focus_mode(), active_mode=active_mode, where_am_i_type=where_am_i_type, full_rolenames=self.use_full_rolenames(), display_ancestors=self.get_display_ancestors(), end_of_line_indicator=self.get_end_of_line_indicator_is_enabled(), present_mnemonics=self.get_present_mnemonics(), ) def display_generated_contents( self, script: default.Script, contents: list[tuple[Atspi.Accessible, int, int, str]], **args: Any, ) -> None: """Generates braille for contents and displays the flattened regions.""" if not self.use_braille(): return context = self._build_generator_context() regions_list, focused_region = script.get_braille_generator().generate_contents( contents, context, **args, ) if not regions_list: return flattened_regions: list = [] for regions in regions_list: flattened_regions.extend(regions) if flattened_regions: flattened_regions[-1].string = flattened_regions[-1].string.rstrip(" ") self.present_regions(flattened_regions, focused_region, indicate_links=False) def present_generated_braille( self, script: default.Script, obj: Atspi.Accessible, **args: Any, ) -> None: """Generates braille for obj using the script's braille generator and displays it.""" if not self.use_braille(): return where_am_i_type = args.pop("where_am_i_type", None) context = self._build_generator_context(where_am_i_type) generator = script.get_braille_generator() result, focused_region = generator.generate_braille(obj, context, **args) if result: self.present_regions( list(result), focused_region, extra_region=args.get("extraRegion"), ) @gsettings_registry.get_registry().gsetting( key=KEY_ENABLED, schema="braille", gtype="b", default=True, summary="Enable braille output", migration_key="enableBraille", ) @dbus_service.getter def get_braille_is_enabled(self) -> bool: """Returns whether braille is enabled.""" return self._get_setting(self.KEY_ENABLED, "b", True) @dbus_service.setter def set_braille_is_enabled(self, value: bool) -> bool: """Sets whether braille is enabled.""" if value == self.get_braille_is_enabled(): return True msg = f"BRAILLE PRESENTER: Setting enable braille to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value(self._SCHEMA, self.KEY_ENABLED, value) braille.set_enable_braille(value) if value: braille.init(input_event_manager.get_manager().process_braille_event) else: braille.shutdown() return True def use_verbose_braille(self) -> bool: """Returns whether the braille verbosity level is set to verbose.""" return self.get_verbosity_level() == "verbose" @gsettings_registry.get_registry().gsetting( key=KEY_VERBOSITY_LEVEL, schema="braille", genum="org.gnome.Orca.VerbosityLevel", default="verbose", summary="Braille verbosity level (brief, verbose)", migration_key="brailleVerbosityLevel", ) @dbus_service.getter def get_verbosity_level(self) -> str: """Returns the current braille verbosity level for object presentation.""" return gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_VERBOSITY_LEVEL, "", genum="org.gnome.Orca.VerbosityLevel", default="verbose", ) @dbus_service.setter def set_verbosity_level(self, value: str) -> bool: """Sets the braille verbosity level for object presentation.""" try: level = VerbosityLevel[value.upper()] except KeyError: msg = f"BRAILLE PRESENTER: Invalid verbosity level: {value}" debug.print_message(debug.LEVEL_WARNING, msg, True) return False msg = f"BRAILLE PRESENTER: Setting verbosity level to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_VERBOSITY_LEVEL, level.string_name, ) return True def use_full_rolenames(self) -> bool: """Returns whether full rolenames should be used.""" return self.get_rolename_style() == "verbose" @gsettings_registry.get_registry().gsetting( key=KEY_ROLENAME_STYLE, schema="braille", genum="org.gnome.Orca.VerbosityLevel", default="verbose", summary="Braille rolename style (brief, verbose)", migration_key="brailleRolenameStyle", ) @dbus_service.getter def get_rolename_style(self) -> str: """Returns the current rolename style for object presentation.""" return gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_ROLENAME_STYLE, "", genum="org.gnome.Orca.VerbosityLevel", default="verbose", ) @dbus_service.setter def set_rolename_style(self, value: str) -> bool: """Sets the current rolename style for object presentation.""" try: level = VerbosityLevel[value.upper()] except KeyError: msg = f"BRAILLE PRESENTER: Invalid rolename style: {value}" debug.print_message(debug.LEVEL_WARNING, msg, True) return False msg = f"BRAILLE PRESENTER: Setting rolename style to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_ROLENAME_STYLE, level.string_name, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_PRESENT_MNEMONICS, schema="braille", gtype="b", default=True, summary="Present mnemonics on braille display", migration_key="displayObjectMnemonic", ) @dbus_service.getter def get_present_mnemonics(self) -> bool: """Returns whether mnemonics are presented on the braille display.""" return self._get_setting(self.KEY_PRESENT_MNEMONICS, "b", True) @dbus_service.setter def set_present_mnemonics(self, value: bool) -> bool: """Sets whether mnemonics are presented on the braille display.""" msg = f"BRAILLE PRESENTER: Setting present mnemonics to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_PRESENT_MNEMONICS, value, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_DISPLAY_ANCESTORS, schema="braille", gtype="b", default=True, summary="Display ancestors of current object", migration_key="enableBrailleContext", ) @dbus_service.getter def get_display_ancestors(self) -> bool: """Returns whether ancestors of the current object will be displayed.""" return self._get_setting(self.KEY_DISPLAY_ANCESTORS, "b", True) @dbus_service.setter def set_display_ancestors(self, value: bool) -> bool: """Sets whether ancestors of the current object will be displayed.""" msg = f"BRAILLE PRESENTER: Setting enable braille context to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_DISPLAY_ANCESTORS, value, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_BRAILLE_PROGRESS_BAR_UPDATES, schema="braille", gtype="b", default=False, summary="Show progress bar updates in braille", migration_key="brailleProgressBarUpdates", ) @dbus_service.getter def get_braille_progress_bar_updates(self) -> bool: """Returns whether braille progress bar updates are enabled.""" return self._get_setting(self.KEY_BRAILLE_PROGRESS_BAR_UPDATES, "b", False) @dbus_service.setter def set_braille_progress_bar_updates(self, value: bool) -> bool: """Sets whether braille progress bar updates are enabled.""" msg = f"BRAILLE PRESENTER: Setting braille progress bar updates to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_BRAILLE_PROGRESS_BAR_UPDATES, value, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_PROGRESS_BAR_BRAILLE_INTERVAL, schema="braille", gtype="i", default=10, summary="Progress bar braille update interval in seconds", migration_key="progressBarBrailleInterval", ) @dbus_service.getter def get_progress_bar_braille_interval(self) -> int: """Returns the braille progress bar update interval in seconds.""" return self._get_setting(self.KEY_PROGRESS_BAR_BRAILLE_INTERVAL, "i", 10) @dbus_service.setter def set_progress_bar_braille_interval(self, value: int) -> bool: """Sets the braille progress bar update interval in seconds.""" msg = f"BRAILLE PRESENTER: Setting progress bar braille interval to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_PROGRESS_BAR_BRAILLE_INTERVAL, value, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_PROGRESS_BAR_BRAILLE_VERBOSITY, schema="braille", genum="org.gnome.Orca.ProgressBarVerbosity", default="application", summary="Progress bar braille verbosity (all, application, window)", migration_key="progressBarBrailleVerbosity", ) @dbus_service.getter def get_progress_bar_braille_verbosity(self) -> int: """Returns the braille progress bar verbosity level.""" nick = gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_PROGRESS_BAR_BRAILLE_VERBOSITY, "", genum="org.gnome.Orca.ProgressBarVerbosity", default="application", ) return ProgressBarVerbosity[nick.upper()].value @dbus_service.setter def set_progress_bar_braille_verbosity(self, value: int) -> bool: """Sets the braille progress bar verbosity level.""" msg = f"BRAILLE PRESENTER: Setting progress bar braille verbosity to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) level = ProgressBarVerbosity(value) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_PROGRESS_BAR_BRAILLE_VERBOSITY, level.name.lower(), ) return True def should_present_progress_bar_update( self, obj: Atspi.Accessible, percent: int | None, is_same_app: bool, is_same_window: bool, ) -> bool: """Returns True if the progress bar update should be brailled.""" if not self.get_braille_progress_bar_updates(): return False last_time, last_value = self._progress_bar_cache.get(id(obj), (0.0, None)) if percent == last_value: return False if percent != 100: interval = int(time.time() - last_time) if interval < self.get_progress_bar_braille_interval(): return False verbosity = self.get_progress_bar_braille_verbosity() if verbosity == ProgressBarVerbosity.ALL.value: present = True elif verbosity == ProgressBarVerbosity.APPLICATION.value: present = is_same_app elif verbosity == ProgressBarVerbosity.WINDOW.value: present = is_same_window else: present = True if present: self._progress_bar_cache[id(obj)] = (time.time(), percent) return present @gsettings_registry.get_registry().gsetting( key=KEY_CONTRACTED_BRAILLE, schema="braille", gtype="b", default=False, summary="Enable contracted braille", migration_key="enableContractedBraille", ) @dbus_service.getter def get_contracted_braille_is_enabled(self) -> bool: """Returns whether contracted braille is enabled.""" return self._get_setting(self.KEY_CONTRACTED_BRAILLE, "b", False) @dbus_service.setter def set_contracted_braille_is_enabled(self, value: bool) -> bool: """Sets whether contracted braille is enabled.""" msg = f"BRAILLE PRESENTER: Setting enable contracted braille to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_CONTRACTED_BRAILLE, value, ) braille.set_enable_contracted_braille(value) return True @gsettings_registry.get_registry().gsetting( key=KEY_COMPUTER_BRAILLE_AT_CURSOR, schema="braille", gtype="b", default=True, summary="Use computer braille at cursor position", migration_key="enableComputerBrailleAtCursor", ) @dbus_service.getter def get_computer_braille_at_cursor_is_enabled(self) -> bool: """Returns whether computer braille is used at the cursor position.""" return self._get_setting(self.KEY_COMPUTER_BRAILLE_AT_CURSOR, "b", True) @dbus_service.setter def set_computer_braille_at_cursor_is_enabled(self, value: bool) -> bool: """Sets whether computer braille is used at the cursor position.""" msg = f"BRAILLE PRESENTER: Setting enable computer braille at cursor to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_COMPUTER_BRAILLE_AT_CURSOR, value, ) braille.set_enable_computer_braille_at_cursor(value) return True def get_contraction_table_path(self) -> str: """Returns the current braille contraction table file path.""" value = gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_CONTRACTION_TABLE, "s", default="", ) return value or braille.get_default_contraction_table() @gsettings_registry.get_registry().gsetting( key=KEY_CONTRACTION_TABLE, schema="braille", gtype="s", default="", summary="Braille contraction table name", migration_key="brailleContractionTable", ) @dbus_service.getter def get_contraction_table(self) -> str: """Returns the current braille contraction table name.""" full_path = gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_CONTRACTION_TABLE, "s", default="", ) if not full_path: return "" return os.path.splitext(os.path.basename(full_path))[0] @dbus_service.getter def get_available_contraction_tables(self) -> list[str]: """Returns a list of available contraction table names.""" table_files = self._get_table_files() return [os.path.splitext(filename)[0] for filename in table_files] def get_contraction_tables_dict(self) -> dict[str, str]: """Returns a dictionary mapping display names to table file paths.""" names = self.get_table_names() tables = {} for fname in self._get_table_files(): alias = fname[:-4] tables[names.get(alias, alias)] = os.path.join(tablesdir, fname) return tables @dbus_service.setter def set_contraction_table(self, value: str) -> bool: """Sets the current braille contraction table.""" table_files = self._get_table_files() base_name = os.path.splitext(value)[0] filename = None for table_file in table_files: if table_file.startswith(base_name + "."): filename = table_file break if not filename: msg = f"BRAILLE PRESENTER: Invalid contraction table: {value}" debug.print_message(debug.LEVEL_WARNING, msg, True) return False full_path = os.path.join(tablesdir, filename) msg = f"BRAILLE PRESENTER: Setting contraction table to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_CONTRACTION_TABLE, full_path, ) return True @staticmethod def _get_table_files() -> list[str]: """Returns a list of braille table filenames in the tables directory.""" try: return [fname for fname in os.listdir(tablesdir) if fname[-4:] in (".utb", ".ctb")] except OSError: return [] def set_contraction_table_from_path(self, file_path: str) -> bool: """Sets the current braille contraction table from a file path.""" msg = f"BRAILLE PRESENTER: Setting contraction table to {file_path}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_CONTRACTION_TABLE, file_path, ) braille.set_contraction_table(file_path) return True @gsettings_registry.get_registry().gsetting( key=KEY_END_OF_LINE_INDICATOR, schema="braille", gtype="b", default=True, summary="Show end-of-line indicator", migration_key="enableBrailleEOL", ) @dbus_service.getter def get_end_of_line_indicator_is_enabled(self) -> bool: """Returns whether the end-of-line indicator is enabled.""" return self._get_setting(self.KEY_END_OF_LINE_INDICATOR, "b", True) @dbus_service.setter def set_end_of_line_indicator_is_enabled(self, value: bool) -> bool: """Sets whether the end-of-line indicator is enabled.""" msg = f"BRAILLE PRESENTER: Setting enable-eol to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_END_OF_LINE_INDICATOR, value, ) braille.set_enable_eol(value) return True @gsettings_registry.get_registry().gsetting( key=KEY_WORD_WRAP, schema="braille", gtype="b", default=False, summary="Enable braille word wrap", migration_key="enableBrailleWordWrap", ) @dbus_service.getter def get_word_wrap_is_enabled(self) -> bool: """Returns whether braille word wrap is enabled.""" return self._get_setting(self.KEY_WORD_WRAP, "b", False) @dbus_service.setter def set_word_wrap_is_enabled(self, value: bool) -> bool: """Sets whether braille word wrap is enabled.""" msg = f"BRAILLE PRESENTER: Setting enable word wrap to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value(self._SCHEMA, self.KEY_WORD_WRAP, value) braille.set_enable_word_wrap(value) return True @gsettings_registry.get_registry().gsetting( key=KEY_FLASH_MESSAGES, schema="braille", gtype="b", default=True, summary="Enable braille flash messages", migration_key="enableFlashMessages", ) @dbus_service.getter def get_flash_messages_are_enabled(self) -> bool: """Returns whether 'flash' messages (i.e. announcements) are enabled.""" return self._get_setting(self.KEY_FLASH_MESSAGES, "b", True) @dbus_service.setter def set_flash_messages_are_enabled(self, value: bool) -> bool: """Sets whether 'flash' messages (i.e. announcements) are enabled.""" msg = f"BRAILLE PRESENTER: Setting enable flash messages to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_FLASH_MESSAGES, value ) return True def get_flashtime_from_settings(self) -> int: """Returns flash message duration in milliseconds based on user settings.""" if self.get_flash_messages_are_persistent(): return -1 return self.get_flash_message_duration() @gsettings_registry.get_registry().gsetting( key=KEY_FLASH_MESSAGE_DURATION, schema="braille", gtype="i", default=5000, summary="Flash message duration in milliseconds", migration_key="brailleFlashTime", ) @dbus_service.getter def get_flash_message_duration(self) -> int: """Returns flash message duration in milliseconds.""" return self._get_setting(self.KEY_FLASH_MESSAGE_DURATION, "i", 5000) @dbus_service.setter def set_flash_message_duration(self, value: int) -> bool: """Sets flash message duration in milliseconds.""" msg = f"BRAILLE PRESENTER: Setting braille flash time to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_FLASH_MESSAGE_DURATION, value, ) return True def set_selector_indicator_from_int(self, value: int) -> bool: """Sets the braille selector indicator from an int value.""" msg = f"BRAILLE PRESENTER: Setting selector indicator to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) indicator = BrailleIndicator(value) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_SELECTOR_INDICATOR, indicator.string_name, ) braille.set_selector_indicator(value) return True def set_link_indicator_from_int(self, value: int) -> bool: """Sets the braille link indicator from an int value.""" msg = f"BRAILLE PRESENTER: Setting link indicator to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) indicator = BrailleIndicator(value) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_LINK_INDICATOR, indicator.string_name, ) braille.set_link_indicator(value) return True def set_text_attributes_indicator_from_int(self, value: int) -> bool: """Sets the braille text attributes indicator from an int value.""" msg = f"BRAILLE PRESENTER: Setting text attributes indicator to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) indicator = BrailleIndicator(value) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_TEXT_ATTRIBUTES_INDICATOR, indicator.string_name, ) braille.set_text_attributes_indicator(value) return True @gsettings_registry.get_registry().gsetting( key=KEY_FLASH_MESSAGES_PERSISTENT, schema="braille", gtype="b", default=False, summary="Make flash messages persistent", migration_key="flashIsPersistent", ) @dbus_service.getter def get_flash_messages_are_persistent(self) -> bool: """Returns whether 'flash' messages are persistent (as opposed to temporary).""" return self._get_setting(self.KEY_FLASH_MESSAGES_PERSISTENT, "b", False) @dbus_service.setter def set_flash_messages_are_persistent(self, value: bool) -> bool: """Sets whether 'flash' messages are persistent (as opposed to temporary).""" msg = f"BRAILLE PRESENTER: Setting flash messages are persistent to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_FLASH_MESSAGES_PERSISTENT, value, ) return True @gsettings_registry.get_registry().gsetting( key=KEY_FLASH_MESSAGES_DETAILED, schema="braille", gtype="b", default=True, summary="Use detailed flash messages", migration_key="flashIsDetailed", ) @dbus_service.getter def get_flash_messages_are_detailed(self) -> bool: """Returns whether 'flash' messages are detailed (as opposed to brief).""" return self._get_setting(self.KEY_FLASH_MESSAGES_DETAILED, "b", True) @dbus_service.setter def set_flash_messages_are_detailed(self, value: bool) -> bool: """Sets whether 'flash' messages are detailed (as opposed to brief).""" msg = f"BRAILLE PRESENTER: Setting flash messages are detailed to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_FLASH_MESSAGES_DETAILED, value, ) return True def _get_selector_indicator_as_int(self) -> int: """Returns the braille selector indicator as an int.""" nick = gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_SELECTOR_INDICATOR, "", genum="org.gnome.Orca.BrailleIndicator", default="dots78", ) value = BrailleIndicator[nick.upper()].value msg = f"BRAILLE PRESENTER: Getting selector indicator: {value}." debug.print_message(debug.LEVEL_INFO, msg, True) return value @gsettings_registry.get_registry().gsetting( key=KEY_SELECTOR_INDICATOR, schema="braille", genum="org.gnome.Orca.BrailleIndicator", default="dots78", summary="Braille selector indicator style (none, dot7, dot8, dots78)", migration_key="brailleSelectorIndicator", ) @dbus_service.getter def get_selector_indicator(self) -> str: """Returns the braille selector indicator style.""" return gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_SELECTOR_INDICATOR, "", genum="org.gnome.Orca.BrailleIndicator", default="dots78", ) @dbus_service.setter def set_selector_indicator(self, value: str) -> bool: """Sets the braille selector indicator style.""" try: indicator = BrailleIndicator[value.upper()] except KeyError: msg = f"BRAILLE PRESENTER: Invalid selector indicator: {value}" debug.print_message(debug.LEVEL_WARNING, msg, True) return False msg = f"BRAILLE PRESENTER: Setting selector indicator to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_SELECTOR_INDICATOR, indicator.string_name, ) braille.set_selector_indicator(indicator.value) return True def _get_link_indicator_as_int(self) -> int: """Returns the braille link indicator as an int.""" nick = gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_LINK_INDICATOR, "", genum="org.gnome.Orca.BrailleIndicator", default="dots78", ) value = BrailleIndicator[nick.upper()].value msg = f"BRAILLE PRESENTER: Getting link indicator: {value}." debug.print_message(debug.LEVEL_INFO, msg, True) return value @gsettings_registry.get_registry().gsetting( key=KEY_LINK_INDICATOR, schema="braille", genum="org.gnome.Orca.BrailleIndicator", default="dots78", summary="Braille link indicator style (none, dot7, dot8, dots78)", migration_key="brailleLinkIndicator", ) @dbus_service.getter def get_link_indicator(self) -> str: """Returns the braille link indicator style.""" return gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_LINK_INDICATOR, "", genum="org.gnome.Orca.BrailleIndicator", default="dots78", ) @dbus_service.setter def set_link_indicator(self, value: str) -> bool: """Sets the braille link indicator style.""" try: indicator = BrailleIndicator[value.upper()] except KeyError: msg = f"BRAILLE PRESENTER: Invalid link indicator: {value}" debug.print_message(debug.LEVEL_WARNING, msg, True) return False msg = f"BRAILLE PRESENTER: Setting link indicator to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_LINK_INDICATOR, indicator.string_name, ) braille.set_link_indicator(indicator.value) return True def _get_text_attributes_indicator_as_int(self) -> int: """Returns the braille text attributes indicator as an int.""" nick = gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_TEXT_ATTRIBUTES_INDICATOR, "", genum="org.gnome.Orca.BrailleIndicator", default="none", ) value = BrailleIndicator[nick.upper()].value msg = f"BRAILLE PRESENTER: Getting text attributes indicator: {value}." debug.print_message(debug.LEVEL_INFO, msg, True) return value @gsettings_registry.get_registry().gsetting( key=KEY_TEXT_ATTRIBUTES_INDICATOR, schema="braille", genum="org.gnome.Orca.BrailleIndicator", default="none", summary="Braille text attributes indicator style (none, dot7, dot8, dots78)", migration_key="textAttributesBrailleIndicator", ) @dbus_service.getter def get_text_attributes_indicator(self) -> str: """Returns the braille text attributes indicator style.""" return gsettings_registry.get_registry().layered_lookup( self._SCHEMA, self.KEY_TEXT_ATTRIBUTES_INDICATOR, "", genum="org.gnome.Orca.BrailleIndicator", default="none", ) @dbus_service.setter def set_text_attributes_indicator(self, value: str) -> bool: """Sets the braille text attributes indicator style.""" try: indicator = BrailleIndicator[value.upper()] except KeyError: msg = f"BRAILLE PRESENTER: Invalid text attributes indicator: {value}" debug.print_message(debug.LEVEL_WARNING, msg, True) return False msg = f"BRAILLE PRESENTER: Setting text attributes indicator to {value}." debug.print_message(debug.LEVEL_INFO, msg, True) gsettings_registry.get_registry().set_runtime_value( self._SCHEMA, self.KEY_TEXT_ATTRIBUTES_INDICATOR, indicator.string_name, ) braille.set_text_attributes_indicator(indicator.value) return True def is_flash_active(self) -> bool: """Returns True if a flash message is currently being displayed.""" return braille.is_flash_active() def kill_flash(self, restore_saved: bool = True) -> None: """Kills any flashed message currently being displayed.""" braille.kill_flash(restore_saved) def present_message(self, message: str, restore_previous: bool = True) -> None: """Displays a single line message in braille.""" if not self.use_braille(): return flash_time = self.get_flashtime_from_settings() if not restore_previous and flash_time: braille.kill_flash(restore_saved=False) braille.display_message(message, flash_time=flash_time) def pan_left(self) -> bool: """Pans the braille display left, returning True if the display moved.""" if not self.use_braille(): return False moved = braille.pan_left() if moved: braille.refresh(pan_to_cursor=False, stop_flash=False) return moved def pan_right(self) -> bool: """Pans the braille display right, returning True if the display moved.""" if not self.use_braille(): return False moved = braille.pan_right() if moved: braille.refresh(pan_to_cursor=False, stop_flash=False) return moved def refresh_braille(self, pan_to_cursor: bool = True) -> None: """Refreshes the braille display without rebuilding the line.""" if not self.use_braille(): return braille.refresh(pan_to_cursor=pan_to_cursor) def pan_to_beginning(self) -> None: """Pans the braille display all the way to the beginning of the line.""" if not self.use_braille(): return while braille.pan_left(): pass braille.refresh(pan_to_cursor=False, stop_flash=True) def pan_to_end(self) -> None: """Pans the braille display all the way to the end of the line.""" if not self.use_braille(): return while braille.pan_right(): pass braille.refresh(pan_to_cursor=False, stop_flash=True) def check_braille_setting(self) -> None: """Checks the braille setting and disables braille if necessary.""" braille.check_braille_setting() def disable_braille(self) -> None: """Idles or shuts down braille output if enabled.""" braille.disable_braille() def set_brlapi_priority(self, high: bool = False) -> None: """Sets the BrlAPI priority level. Args: high: If True, use high priority (for flat review). Otherwise use default. """ if high: braille.set_brlapi_priority(braille.BRLAPI_PRIORITY_HIGH) else: braille.set_brlapi_priority() def update_monitor( self, cursor_cell: int, substring: str, mask: str | None, display_size: int, ) -> None: """Updates the braille monitor display, creating it on demand if enabled.""" if not self.get_monitor_is_enabled(): return cell_count = self.get_monitor_cell_count() or display_size if self._monitor is None: self._monitor = braille_monitor.BrailleMonitor( cell_count, on_close=lambda: self.set_monitor_is_enabled(False), foreground=self.get_monitor_foreground(), background=self.get_monitor_background(), ) self._monitor.show_all() # pylint: disable=no-member if self.get_monitor_show_dots(): substring = self._to_unicode_braille(substring) self._monitor.write_text(cursor_cell, substring, mask) def _to_unicode_braille(self, text: str) -> str: """Convert text to Unicode braille dot pattern characters. Uses louis.charToDots() to map each character to its braille dot pattern, then converts to Unicode braille characters (U+2800 block). """ try: import louis # pylint: disable=import-outside-toplevel table = ( self.get_contraction_table_path() if self.get_contracted_braille_is_enabled() else "" ) if not table: table = "en-us-comp8.ctb" dots_str = louis.charToDots([table], text) return "".join(chr(0x2800 | (ord(c) & 0xFF)) for c in dots_str) except Exception: # pylint: disable=broad-except return text def destroy_monitor(self) -> None: """Destroys the braille monitor widget if it exists.""" if self._monitor is not None: self._monitor.destroy() self._monitor = None def init_braille(self) -> None: """Initializes braille if enabled.""" braille.set_monitor_callback(self.update_monitor) if not self.get_braille_is_enabled(): return braille.init(input_event_manager.get_manager().process_braille_event) self._sync_state_to_braille() def _sync_state_to_braille(self) -> None: """Pushes persisted dconf settings into the braille module's runtime state.""" braille.set_enable_contracted_braille(self.get_contracted_braille_is_enabled()) braille.set_contraction_table(self.get_contraction_table_path()) braille.set_enable_computer_braille_at_cursor( self.get_computer_braille_at_cursor_is_enabled(), ) braille.set_enable_eol(self.get_end_of_line_indicator_is_enabled()) braille.set_enable_word_wrap(self.get_word_wrap_is_enabled()) braille.set_selector_indicator(self._get_selector_indicator_as_int()) braille.set_link_indicator(self._get_link_indicator_as_int()) braille.set_text_attributes_indicator(self._get_text_attributes_indicator_as_int()) def shutdown_braille(self) -> None: """Shuts down braille.""" braille.shutdown() _presenter: BraillePresenter = BraillePresenter() def get_presenter() -> BraillePresenter: """Returns the Braille Presenter""" return _presenter