%PDF- %PDF-
Direktori : /lib/python3/dist-packages/orca/ |
Current File : //lib/python3/dist-packages/orca/ax_utilities.py |
# Utilities for performing tasks related to accessibility inspection. # # Copyright 2023 Igalia, S.L. # Author: Joanmarie Diggs <jdiggs@igalia.com> # # 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. """ Utilities for performing tasks related to accessibility inspection. These utilities are app-type- and toolkit-agnostic. Utilities that might have different implementations or results depending on the type of app (e.g. terminal, chat, web) or toolkit (e.g. Qt, Gtk) should be in script_utilities.py file(s). N.B. There are currently utilities that should never have custom implementations that live in script_utilities.py files. These will be moved over time. """ __id__ = "$Id$" __version__ = "$Revision$" __date__ = "$Date$" __copyright__ = "Copyright (c) 2023 Igalia, S.L." __license__ = "LGPL" import inspect import gi gi.require_version("Atspi", "2.0") from gi.repository import Atspi from . import debug from .ax_object import AXObject from .ax_utilities_collection import AXUtilitiesCollection from .ax_utilities_role import AXUtilitiesRole from .ax_utilities_state import AXUtilitiesState class AXUtilities: """Utilities for performing tasks related to accessibility inspection.""" COMPARE_COLLECTION_PERFORMANCE = False @staticmethod def get_desktop(): """Returns the accessible desktop""" try: desktop = Atspi.get_desktop(0) except Exception as error: tokens = ["ERROR: Exception getting desktop from Atspi:", error] debug.printTokens(debug.LEVEL_INFO, tokens, True) return None return desktop @staticmethod def get_all_applications(must_have_window=False): """Returns a list of running applications known to Atspi, filtering out those which have no child windows if must_have_window is True.""" desktop = AXUtilities.get_desktop() if desktop is None: return [] def pred(obj): if must_have_window: return AXObject.get_child_count(obj) > 0 return True return list(AXObject.iter_children(desktop, pred)) @staticmethod def is_application_in_desktop(app): """Returns true if app is known to Atspi""" desktop = AXUtilities.get_desktop() if desktop is None: return False for child in AXObject.iter_children(desktop): if child == app: return True tokens = ["WARNING:", app, "is not in", desktop] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False @staticmethod def get_application_with_pid(pid): """Returns the accessible application with the specified pid""" desktop = AXUtilities.get_desktop() if desktop is None: return None for app in AXObject.iter_children(desktop): if AXObject.get_process_id(app) == pid: return app tokens = ["WARNING: app with pid", pid, "is not in", desktop] debug.printTokens(debug.LEVEL_INFO, tokens, True) return None @staticmethod def get_all_static_text_leaf_nodes(obj): """Returns all the descendants of obj that are static text leaf nodes""" roles = [Atspi.Role.STATIC, Atspi.Role.TEXT] def is_not_element(acc): return AXObject.get_attribute(acc, "tag") in (None, "", "br") result = None if AXObject.supports_collection(obj): result = AXUtilitiesCollection.find_all_with_role(obj, roles, is_not_element) if not AXUtilities.COMPARE_COLLECTION_PERFORMANCE: return result def is_match(acc): return AXObject.get_role(acc) in roles and is_not_element(acc) return AXObject.find_all_descendants(obj, is_match) @staticmethod def get_all_widgets(obj, must_be_showing_and_visible=True, exclude_push_button=False): """Returns all the descendants of obj with a widget role""" roles = AXUtilitiesRole.get_widget_roles() if exclude_push_button and Atspi.Role.PUSH_BUTTON in roles: roles.remove(Atspi.Role.PUSH_BUTTON) result = None if AXObject.supports_collection(obj): if not must_be_showing_and_visible: result = AXUtilitiesCollection.find_all_with_role(obj, roles) else: states = [Atspi.StateType.SHOWING, Atspi.StateType.VISIBLE] result = AXUtilitiesCollection.find_all_with_role_and_all_states( obj, roles, states) if not AXUtilities.COMPARE_COLLECTION_PERFORMANCE: return result def is_match(acc): if AXObject.get_role(acc) not in roles: return False if must_be_showing_and_visible: return AXUtilitiesState.is_showing(acc) and AXUtilitiesState.is_visible(acc) return True return AXObject.find_all_descendants(obj, is_match) @staticmethod def get_default_button(obj): """Returns the default button descendant of obj""" result = None if AXObject.supports_collection(obj): result = AXUtilitiesCollection.find_default_button(obj) if not AXUtilities.COMPARE_COLLECTION_PERFORMANCE: return result return AXObject.find_descendant(obj, AXUtilitiesRole.is_default_button) @staticmethod def get_focused_object(obj): """Returns the focused descendant of obj""" result = None if AXObject.supports_collection(obj): result = AXUtilitiesCollection.find_focused_object(obj) if not AXUtilities.COMPARE_COLLECTION_PERFORMANCE: return result return AXObject.find_descendant(obj, AXUtilitiesState.is_focused) @staticmethod def get_status_bar(obj): """Returns the status bar descendant of obj""" result = None if AXObject.supports_collection(obj): result = AXUtilitiesCollection.find_status_bar(obj) if not AXUtilities.COMPARE_COLLECTION_PERFORMANCE: return result return AXObject.find_descendant(obj, AXUtilitiesRole.is_status_bar) @staticmethod def is_message_dialog(obj): """Returns True if obj is a dialog that should be treated as a message dialog""" if not AXUtilitiesRole.is_dialog_or_alert(obj): return False if not AXObject.supports_collection(obj): widgets = AXUtilities.get_all_widgets(obj, exclude_push_button=True) return not widgets if AXUtilitiesCollection.has_scroll_pane(obj): tokens = ["AXUtilities:", obj, "is not a message dialog: has scroll pane"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False if AXUtilitiesCollection.has_split_pane(obj): tokens = ["AXUtilities:", obj, "is not a message dialog: has split pane"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False if AXUtilitiesCollection.has_tree_or_tree_table(obj): tokens = ["AXUtilities:", obj, "is not a message dialog: has tree or tree table"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False if AXUtilitiesCollection.has_combo_box_or_list_box(obj): tokens = ["AXUtilities:", obj, "is not a message dialog: has combo box or list box"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False if AXUtilitiesCollection.has_editable_object(obj): tokens = ["AXUtilities:", obj, "is not a message dialog: has editable object"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return False tokens = ["AXUtilities:", obj, "is believed to be a message dialog"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return True for name, method in inspect.getmembers(AXUtilitiesRole, predicate=inspect.isfunction): setattr(AXUtilities, name, method) for name, method in inspect.getmembers(AXUtilitiesState, predicate=inspect.isfunction): setattr(AXUtilities, name, method) for name, method in inspect.getmembers(AXUtilitiesCollection, predicate=inspect.isfunction): if name.startswith("find"): setattr(AXUtilities, name, method)