{"id":15720301,"url":"https://github.com/gnome/orca","last_synced_at":"2025-04-13T00:46:05.374Z","repository":{"id":3471941,"uuid":"4526816","full_name":"GNOME/orca","owner":"GNOME","description":"Read-only mirror of https://gitlab.gnome.org/GNOME/orca","archived":false,"fork":false,"pushed_at":"2025-04-09T11:13:27.000Z","size":96786,"stargazers_count":119,"open_issues_count":0,"forks_count":49,"subscribers_count":30,"default_branch":"main","last_synced_at":"2025-04-13T00:45:54.628Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://gitlab.gnome.org/GNOME/orca","language":"Python","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-2.1","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/GNOME.png","metadata":{"files":{"readme":"README-APPLICATION-DEVELOPERS.md","changelog":"ChangeLog","contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-06-02T04:29:44.000Z","updated_at":"2025-04-09T11:13:31.000Z","dependencies_parsed_at":"2024-02-09T02:42:17.940Z","dependency_job_id":"26143421-fe0f-416f-a5a4-da2445fb84c4","html_url":"https://github.com/GNOME/orca","commit_stats":null,"previous_names":[],"tags_count":339,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GNOME%2Forca","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GNOME%2Forca/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GNOME%2Forca/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GNOME%2Forca/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GNOME","download_url":"https://codeload.github.com/GNOME/orca/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248650420,"owners_count":21139672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-03T21:58:14.992Z","updated_at":"2025-04-13T00:46:05.352Z","avatar_url":"https://github.com/GNOME.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tips for Application Developers\n\n[TOC]\n\n## Introduction\n\nPlease note: This document is a work in progress and will be expanded over time to include details\nabout what Orca expects from applications. In the meantime, it contains tips based on frequently-asked\nquestions. We hope you find them useful.\n\n## A Request\n\nIf Orca is not presenting your application in the way you think it should, and the techniques in\nthis document do not apply, please file a bug against Orca so we can determine where the fix belongs.\nIt might be an Orca bug which we can fix for you. Otherwise, we can help you address it in your\napplication and update this document accordingly.\n\n## Accessible Events\n\nOrca has many commands and modes which make it possible for users to interact with your application.\nHowever, the most basic need for applications is presentation of changes in location, such as focus\nchanges and navigation within text. In order for Orca to present such changes, it must be notified\nvia one or more accessible events. Unless you have created a custom widget, you should expect that\naccessible events will be fired by your application's toolkit.\n\nExamples of accessible events include:\n\n* `window:activate` tells Orca that the user is in a different window. When the active window\n  changes, Orca will announce the new window. Orca also keeps track of the active window in order\n  to filter many accessibility events which get fired by apps other than the one currently\n  being used.\n* `object:state-changed:focused` tells Orca that the user has moved to a different widget. Orca\n  presents newly-focused widgets. Orca also keeps track of the focused widget in order to filter\n  out some accessibility events. As an example, the `object:state-changed:checked` event tells Orca\n  that the `checked` state of a checkbox-like widget has changed. That change is likely not of\n  interest to the user if that widget is not focused. Therefore Orca will present that change only\n  if the widget emitting it is focused.\n* `object:text-caret-moved` tells Orca that the user has moved to a different location in a text\n  widget or document. Assuming the event is fired from the active object (focused text field or\n  current document), Orca will present the new character, word, or line in response, using some\n  heuristics to determine what unit of text should be presented.\n\nIf you are interested in seeing what accessible events your application is firing, you can use\n[Accerciser](https://gitlab.gnome.org/GNOME/accerciser)'s event monitor.\n\n## What Orca Presents In Response To Location Changes\n\n### As a General Rule\n\nWhen an accessible event informs Orca that the object of interest, or the user's location therein,\nhas changed, Orca will generally present two things:\n\n1. The new ancestors of the object of interest, if any\n2. The object of interest, or the new location, and any role-specific relevant details\n\nFor instance if the user tabs into a group of checkboxes labelled \"Permissions\" and lands on the\nunchecked \"Allow access to microphone\" checkbox, Orca will say:\n\n* \"Permissions panel\"\n* \"Allow access to microphone checkbox. Not checked.\"\n\nIf the user then tabs to another widget in the \"Permissions\" group, Orca will only present that\nwidget and will not repeat \"Permissions.\"\n\nWhat Orca presents for any given object depends on the role of that object. However, for UI\ncomponents, Orca will always present the accessible name and, by default, the accessible\ndescription. (The latter can be disabled by the user either globally or on a per-app basis,\nthough it is always available on demand via Orca's \"Where Am I?\" command.)\n\n### Presentation of \"Selected\"/\"Not selected\" in UI\n\nIn most groups of selectable items, selection follows focus. In other words, arrowing to an item\ncauses it to become both focused and selected. In these instances, Orca deliberately does not say\n\"selected\" because that is the expected state, and most users dislike \"chattiness.\" However, Orca\nshould announce \"not selected\" when the user navigates to a selectable item that did not become\nselected as a result of navigation. This presentation can be seen by using Ctrl+Arrow in Caja to\nmove among items without selecting them.\n\nThe way Orca determines that \"not selected\" should be announced is by finding the \"selectable\"\naccessible state present and the \"selected\" state absent on the item which just claimed focus.\nIf the \"selectable\" state is absent, Orca will not say \"not selected\" because doing so makes no sense\non an object which is not selectable.\n\nOrca should also announce when the selected state of the focused item subsequently changes. For\ninstance, after using Ctrl+Arrow to move to an item without selecting it, one can use Ctrl+Space to\ntoggle that item's selected state. When it becomes selected, Orca will say \"selected\". When it\nbecomes unselected, Orca will say \"unselected\".\n\nWhat causes Orca to make this announcement is an `object:state-changed:selected` event being fired\nby the item whose state changed. If the event is not fired, Orca will be unaware that the state\nchanged and remain silent in response. For standard toolkit widgets, using the toolkit's API to\ntoggle the selection should cause the accessible state to be updated and the accessible event to\nbe fired. If that is not the case, there may be a toolkit bug. For custom widgets, it is likely that\nyou will need to make these updates within your application.\n\n### Status Bars and Focusable List Items\n\nThere are a couple of instances in which Orca will also include the descendants of a UI\ncomponent in the presentation of that component:\n\n1. Status bars. Because status bars are normally not focusable, Orca provides a\n[\"Where Am I?\" command](https://gnome.pages.gitlab.gnome.org/orca/help/commands_where_am_i.html)\nto speak the contents of the status bar.\n2. Focusable list items. GTK `ListItem`s containing multiple descendants are becoming increasingly\ncommon in applications. Some application developers have stated that Orca should automatically present\nall of the displayed information inside a `ListItem` when it becomes focused. This change was made\nin Orca v47.\n\nBecause some application developers use the accessible name as a means to make Orca present the\nfull contents of the list item, Orca has filtering which attempts to eliminate presentation of\ndescendants whose contents are in reflected in the name. Since the release of Orca v47, it was\ndiscovered that this filtering is insufficient for some applications. It is being increased in\nOrca v47.2. However, we recommend that application developers do not expose the full contents of\na focusable list item in that item's accessible name, and to file a bug if Orca fails to present\nthe list item correctly.\n\n### Tables\n\nWhen a user navigates among rows in a table/grid, sometimes the full row should be presented;\nother times just the current cell/item. It can be difficult to programmatically determine which\nmakes sense for any given application. In addition, user needs and preferences vary. For these\nreasons, the amount Orca reads when arrowing up or down in a container with the accessible `table`\nrole is a [user-configurable option](https://gnome.pages.gitlab.gnome.org/orca/help/preferences_speech.html)\nwhich can be set on a [per-application basis](https://gnome.pages.gitlab.gnome.org/orca/help/preferences_introduction.html)\nand customized by the type of table (GUI, spreadsheet, and document).\n\n## Speaking Your Application's Non-Focusable Static Text\n\n### Technique: Use An Accessible Description\n\nIf your application has static, on-screen text of an explanatory nature and you do not want to make\nthat text focusable, it is still possible to have Orca present it automatically using the accessible\n`description` property or the `described-by` relationship.\n\nExamples:\n\n* If the focused object is a search input, and the text to be presented is \"*n* matches found\",\n  set \"*n* matches found\" as the accessible description of the search input and update that\n  description each time the count changes. In response, the toolkit should fire an\n  `object:property-change:accessible-description` event which Orca will handle by presenting the\n  new description.\n\n* If there is an on-screen message associated with a group of widgets, set the accessible description\n  of that group to the on-screen message. As stated in the previous section, Orca will present the\n  name and description of new ancestors prior to presenting the focused object.\n\nWhen using this technique, keep in mind that the description is the last thing presented for any\nobject. Taking the \"Permissions\" group example from the previous section, that means Orca will say:\n\n* \"Permissions panel\", followed by the description of that group\n* \"Allow access to microphone checkbox. Not checked.\", followed by the description of the checkbox\n\nIf the static text should not be presented last, a different technique might be advisable.\n\n**Note:** Orca v47.alpha or later is required to have Orca present the accessible description, and\nany changes to that description, on *ancestors* of the object of interest. In Orca v46 and earlier,\nOrca only presents the name of ancestors and description changes on the object of interest.\n\n### Can I Use Details/Details-For And Error-Message/Errors-For Relations?\n\nThe `details`/`details-for` and `error-message`/`error-for` relation types were created for ARIA,\nand there was no indication that they might be of interest to developers of native applications.\nAs a result, support in Orca for these new relation types was implemented only for web apps.\n[There are plans to support these relation types globally](https://gitlab.gnome.org/GNOME/orca/-/issues/514),\nhopefully during the v48 release cycle.\n\n### Why Is Orca Speaking My Labels As Static Text?\n\nUsing the accessible `description` property to get screen readers to automatically announce text in\na newly-shown dialog originated as a web-browser practice, e.g. for alerts. Historically, application\ndevelopers simply used toolkit labels, e.g. `GtkLabel`, to add static text. And those developers,\nand Orca users, expected Orca to read that text automatically.\n\nIn order to distinguish static text labels from widget labels, Orca checks the accessible relations\nof the label. If it finds any relation, Orca assumes the label is NOT static text. Otherwise Orca\napplies some additional heuristics to filter out false positives. But in the end, Orca may conclude\nincorrectly that the unrelated label is indeed static text which should be read automatically.\n\nWhile less than ideal, keeping this functionality in place is important, because there are *many*\napplications that do not use the new `description` approach and likely will not be updated to do so.\nBreaking the user experience in all of those apps would be bad. However, **it's easy to ensure Orca\ndoesn't mistakenly treat your labels as static text to be read automatically:**\n\n* Orca v47.alpha and later prefers the `description` as the source of static text. If your app\n  uses that technique, Orca will not search for unrelated labels to present.\n* Any label that is not static text should have the accessible `label-for` relationship pointing to\n  the widget it labels. That will prevent all versions of Orca from concluding it is static text\n  that should be automatically presented.\n\n## Speaking Your Application's Custom Message/Announcement\n\nAT-SPI2/ATK v2.46 added an `announcement` signal which can be used with Orca v45.2 and later.\nSimple examples are provided below.\n\n### Announcement Example: GTK 3\n\n```python\n#!/usr/bin/python\n\nimport gi\ngi.require_version(\"Gtk\", \"3.0\")\nfrom gi.repository import Gtk\n\ndef on_button_clicked(button):\n    button.get_accessible().emit(\"announcement\", \"Hello world. I am an announcement.\")\n\ndef on_activate(application):\n    window = Gtk.ApplicationWindow(application=application)\n    button = Gtk.Button(label=\"Make an announcement\")\n    button.connect(\"clicked\", on_button_clicked)\n    window.add(button)\n    window.show_all()\n\napp = Gtk.Application()\napp.connect(\"activate\", on_activate)\napp.run(None)\n```\n\nIf you are running Orca v45.2 or later, launch the sample application above and press the\n\"Make an announcement\" button. You should hear Orca say \"Hello world. I am an announcement.\"\n\nBeginning with ATK v2.50, the `announcement` signal was deprecated in favor of a new\n`notification` signal to provide native applications similar functionality to ARIA's live\nregions which allow web applications to specify that a notification is urgent/\"assertive.\"\n\nHere is an example of using the `notification` signal:\n\n```python\n#!/usr/bin/python\n\nimport gi\ngi.require_version(\"Atk\", \"1.0\")\ngi.require_version(\"Gtk\", \"3.0\")\n\nfrom gi.repository import Atk, Gtk\n\ndef on_button_clicked(button):\n    button.get_accessible().emit(\"notification\", \"Hello world. I am a notification.\", Atk.Live.POLITE)\n\ndef on_activate(application):\n    window = Gtk.ApplicationWindow(application=application)\n    button = Gtk.Button(label=\"Make a notification\")\n    button.connect(\"clicked\", on_button_clicked)\n    window.add(button)\n    window.show_all()\n\napp = Gtk.Application()\napp.connect(\"activate\", on_activate)\napp.run(None)\n```\n\n### Announcement Example: GTK 4 (Minimum Version: 4.14)\n\n```python\n#!/usr/bin/python\n\nimport gi\ngi.require_version(\"Gtk\", \"4.0\")\n\nfrom gi.repository import Gtk\n\ndef on_button_clicked(button):\n    button.announce(\"Hello world. I am a notification.\", Gtk.AccessibleAnnouncementPriority.MEDIUM)\n\ndef on_activate(application):\n    window = Gtk.ApplicationWindow(application=application)\n    button = Gtk.Button(label=\"Make a notification\")\n    button.connect(\"clicked\", on_button_clicked)\n    window.set_child(button)\n    window.present()\n\napp = Gtk.Application()\napp.connect(\"activate\", on_activate)\napp.run(None)\n```\n\nNote that in older GTK 4 releases there is no way how to do this, as you can't emit raw AT-SPI2\nevents, or do similar platform-specific things.\n\n### Announcement Example: Qt 6 (Minimum Version: 6.8)\n\n```python\n#!/usr/bin/python\n\nimport sys\nfrom PySide6.QtCore import Slot\nfrom PySide6.QtGui import QAccessible, QAccessibleAnnouncementEvent\nfrom PySide6.QtWidgets import QApplication, QMainWindow, QPushButton\n\n@Slot()\ndef on_button_clicked(checked):\n    announcement_event = QAccessibleAnnouncementEvent(button, \"Hello world. I am a notification.\")\n    # prio could be set like this (Polite is the default anyway)\n    announcement_event.setPoliteness(QAccessible.AnnouncementPoliteness.Polite)\n    QAccessible.updateAccessibility(announcement_event)\n\napp = QApplication(sys.argv)\nmain_window = QMainWindow()\nbutton = QPushButton(\"Make a notification\", main_window)\nbutton.resize(200, 50)\nbutton.clicked.connect(on_button_clicked)\n\nmain_window.show()\napp.exec()\n```\n\n### Announcement Example: Headless Application using Dasbus for DBus Communication\n\nIn an application without a GUI, you can pretend to be enough of an accessible object to fire this\nevent as well. To hear the result, Orca must be running first, though.\n\n```python\n#!/usr/bin/python3\nfrom dasbus.connection import SessionMessageBus, AddressedMessageBus\nfrom dasbus.loop import EventLoop\nfrom dasbus.server.interface import dbus_class, dbus_interface, dbus_signal\nfrom dasbus.typing import Int32, UInt32, Variant, Structure, Dict, List, Tuple, ObjPath, get_variant\nimport threading\nimport time\n\nANNOUNCER_PATH = \"/com/example/Announcer\"\n\n@dbus_interface(\"org.a11y.atspi.Application\")\nclass Application:\n    \"\"\"A minimal accessible application to fulfill AT-SPI2's expectations that events come from a\n    valid accessible application.\"\"\"\n\n    @property\n    def ToolkitName(self) -\u003e str:\n        return \"Announcer\"\n\n@dbus_interface(\"org.a11y.atspi.Event.Object\")\nclass ObjectEvents:\n    \"\"\"Just a holder for the one needed signal.\"\"\"\n\n    @dbus_signal\n    def Announcement(self, subtype: str, detail1: int, detail2: int, value: Variant, props: Structure):\n        pass\n\n@dbus_interface(\"org.a11y.atspi.Accessible\")\nclass Accessible:\n    \"\"\"A minimal accessible object to fulfill AT-SPI2's expectations that events come from a valid\n    accessible object.\"\"\"\n\n    def GetState(self) -\u003e list[UInt32]:\n        return [1\u003c\u003c25, 0] # ATSPI_STATE_SHOWING\n\n    @property\n    def Name(self) -\u003e str:\n        return \"Announcer\"\n\n    @property\n    def Parent(self) -\u003e Tuple[str, ObjPath]:\n        return \"\", ObjPath(\"/org/a11y/atspi/null\")\n\n    def GetRole(self) -\u003e UInt32:\n        return 75 # ATSPI_ROLE_APPLICATION\n\n    def GetAttributes(self) -\u003e Dict[str, str]:\n        return {}\n\n@dbus_class\nclass Announcer(Accessible, Application, ObjectEvents):\n    pass\n\nCacheEntry = Tuple[Tuple[str, ObjPath], Tuple[str, ObjPath], Tuple[str, ObjPath], Int32, Int32, List[str], str, UInt32, str, List[UInt32]]\n\n@dbus_interface(\"org.a11y.atspi.Cache\")\nclass Cache:\n    \"\"\"A minimal accessible objects cache to fulfill AT-SPI2's expectations that events come from an\n    accessible application which has a valid accessible objects cache.\"\"\"\n\n    def GetItems(self) -\u003e List[CacheEntry]:\n        return []\n\nsession_bus = SessionMessageBus()\na11y_bus_info_provider = session_bus.get_proxy(\"org.a11y.Bus\", \"/org/a11y/bus\")\naddress = a11y_bus_info_provider.GetAddress()\na11y_bus = AddressedMessageBus(address)\nannouncer = Announcer()\na11y_bus.publish_object(ANNOUNCER_PATH, announcer)\na11y_bus.publish_object(\"/org/a11y/atspi/cache\", Cache())\nloop = EventLoop()\nthreading.Thread(target=loop.run, daemon=True).start()\nprint(\"About to announce Hello, world!\")\nannouncer.Announcement(\"\", 1, 0, get_variant(str, \"Hello, world!\"), [])\n# Give the announcement time to be processed\ntime.sleep(0.5)\nprint(\"Done announcing Hello, world!\")\n```\n\n**Please note:** Because \"assertive\" messages can be disruptive if presented at the wrong\ntime, Orca *currently* treats an \"assertive\" notification from non-web applications the\nsame as a regular/\"polite\" notification. Adding support for \"assertive\" notifications from non-web\napplications is planned and depends on Orca's\n[live-region support being made global](https://gitlab.gnome.org/GNOME/orca/-/issues/431)\nso that users have full control over when and how notifications are presented to them.\n\n## Providing Context-Sensitive Help Messages\n\nAT-SPI2/ATK v2.52 added support for setting and retrieving \"help text\" on accessible objects.\nHelp text makes it possible to provide context-sensitive information that might not be\nimmediately obvious to the user. For instance in a slide presentation editor, when the user tabs to a\nplaceholder on a slide, an appropriate message might be \"You are on a placeholder. Use the arrow\nkeys to reposition it on the slide. Press Return to edit its contents.\" (As the user moves the\nplaceholder on the slide, the Announcement feature described above could be used to inform the user\nof the new location.)\n\nPlease note: Help text should *not* be used to announce mnemonics. Mnemonics are expected to be\nexposed to Orca via the accessible Action interface via the toolkit. Orca has a setting, disabled\nby default, to present mnemonics to the user.\n\nHelp text is supported in Orca v46 and later. Prior to Orca v47.alpha, this feature was disabled by\ndefault. Starting with Orca v47.alpha, help text is presented by default when focus changes, but\nthat presentation can be disabled by the user either globally or on per-app basis. However, even\nwhen disabled for focus changes, users can always obtain the help text on demand by using Orca's\n\"Where Am I?\" command.\n\n### Help Message Example: GTK 3\n\n```python\n#!/usr/bin/python\n\nimport gi\ngi.require_version(\"Gtk\", \"3.0\")\n\nfrom gi.repository import Gtk\n\ndef on_activate(application):\n    window = Gtk.ApplicationWindow(application=application)\n    box = Gtk.HBox()\n    window.add(box)\n    label = Gtk.Label(label=\"Type something here:\")\n    box.add(label)\n    entry = Gtk.Entry()\n    box.add(entry)\n\n    # Setting the mnemonic widget will cause the accessible labelled-by relation to be\n    # set. Orca uses that to say \"Type something here:\" when the entry gains focus.\n    label.set_mnemonic_widget(entry)\n\n    # This text is presented by Orca as a \"tutorial message.\"\n    entry.get_accessible().set_help_text(\"Enter 10 characters.\")\n    window.show_all()\n\napp = Gtk.Application()\napp.connect(\"activate\", on_activate)\napp.run(None)\n```\n\n### Help Message Example: GTK 4 (Minimum Version: 4.16)\n\n```python\n#!/usr/bin/python\n\nimport gi\ngi.require_version(\"Gtk\", \"4.0\")\n\nfrom gi.repository import Gtk\n\ndef on_activate(application):\n    window = Gtk.ApplicationWindow(application=application)\n    box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)\n    window.set_child(box)\n    label = Gtk.Label(label=\"Type something here:\")\n    box.append(label)\n    entry = Gtk.Entry()\n    box.append(entry)\n\n    # Setting the mnemonic widget will cause the accessible labelled-by relation to be\n    # set. Orca uses that to say \"Type something here:\" when the entry gains focus.\n    label.set_mnemonic_widget(entry)\n\n    # This text is presented by Orca as a \"tutorial message.\"\n    entry.update_property([Gtk.AccessibleProperty.HELP_TEXT], [\"Enter 10 characters.\"])\n    window.present()\n\napp = Gtk.Application()\napp.connect(\"activate\", on_activate)\napp.run(None)\n```\n\n### Help Message Example: Qt 6 (Minimum Version: 6.8)\n\n```python\n#!/usr/bin/python\nimport sys\nfrom PySide6.QtWidgets import QMainWindow, QLabel, QLineEdit, QHBoxLayout, QApplication, QWidget\n\napp = QApplication(sys.argv)\nwindow = QMainWindow()\ncentral_widget = QWidget()\nbox = QHBoxLayout(central_widget)\nwindow.setCentralWidget(central_widget)\nlabel = QLabel(\"Type something here:\")\nbox.addWidget(label)\nentry = QLineEdit()\nbox.addWidget(entry)\n\n# Setting the label's buddy will cause the accessible label-for relation to be\n# set. Orca uses that to say \"Type something here:\" when the entry gains focus.\nlabel.setBuddy(entry)\n\n# This text is presented by Orca as a \"tutorial message.\"\nentry.setWhatsThis(\"Enter 10 characters.\")\nwindow.show()\n\napp.exec()\n```\n\n## Stand-Alone Tools For Debugging\n\n### Dump The Focused Object And Its Ancestors\n\nThis tool listens for `object:state-changed:focused` events. When an object emits this event,\nthe tool will print the event along with the accessibility tree from the application down to\nthe object which just claimed focus. Note that the information dumped is limited to the basics:\nrole, name/label, description, and help text.\n\n```python\n#!/usr/bin/python\n\nimport gi\ngi.require_version(\"Atspi\", \"2.0\")\nfrom gi.repository import Atspi\n\ndef as_string(obj):\n    try:\n        help_text = Atspi.Accessible.get_help_text(obj)\n    except Exception as error:\n        help_text = f\"({error})\"\n    return (f\"{Atspi.Accessible.get_role(obj).value_name} \"\n            f\"name:'{get_name(obj)}' \"\n            f\"description:'{get_description(obj)}' \"\n            f\"help text: '{help_text}'\")\n\ndef get_name(obj):\n    name = Atspi.Accessible.get_name(obj)\n    if name:\n        return name\n\n    relations = Atspi.Accessible.get_relation_set(obj)\n    for relation in relations:\n        if relation.get_relation_type() != Atspi.RelationType.LABELLED_BY:\n            continue\n        targets = [relation.get_target(i) for i in range(relation.get_n_targets())]\n        return \" \".join([target.name for target in targets])\n\n    return \"\"\n\ndef get_description(obj):\n    description = Atspi.Accessible.get_description(obj)\n    if description:\n        return description\n\n    relations = Atspi.Accessible.get_relation_set(obj)\n    for relation in relations:\n        if relation.get_relation_type() != Atspi.RelationType.DESCRIBED_BY:\n            continue\n        targets = [relation.get_target(i) for i in range(relation.get_n_targets())]\n        return \" \".join([target.name for target in targets])\n\n    return \"\"\n\ndef on_event(e):\n    if not e.detail1:\n        return\n\n    print(f\"\\n{as_string(e.source)} is now focused\")\n    ancestors = []\n    parent = e.source\n    while parent:\n      grandparent = Atspi.Accessible.get_parent(parent)\n      ancestors.append(as_string(parent))\n      if Atspi.Accessible.get_role(parent) == Atspi.Role.APPLICATION:\n        break\n      parent = grandparent\n\n    ancestors.reverse()\n    indent = 0\n    for ancestor in ancestors:\n        print(f\"{' ' * indent}--\u003e {ancestor}\")\n        indent += 2\n\n    if Atspi.Accessible.get_role(e.source) == Atspi.Role.TERMINAL:\n      print(\"Exiting.\")\n      Atspi.event_quit()\n\nlistener = Atspi.EventListener.new(on_event)\nlistener.register(\"object:state-changed:focused\")\nprint(\"Return focus to your terminal to exit\")\nAtspi.event_main()\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnome%2Forca","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgnome%2Forca","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnome%2Forca/lists"}