For Developers and Testers: This page provides information on the GNOME Accessibility architecture, developing accessible applications, and testing for accessibility. |
Contents
A11Y-Love: Need help bringing many of these documents up to date.
A11Y-Love: Need to break these into categories, such as 'AT-SPI Infrastructure Overview', 'Designing for Accessibility', 'Developing for Accessibility', 'Testing For Accessibility', 'AT-SPI Implementation Best Practices'
Guidelines for Writing Accessible GNOME Applications Using GTK+ and the Accessibility Toolkit (ATK)
Testing GNOME Applications for Accessibility document, written by Wipro Technologies. See also the Open A11y group's work on testing/validation.
A11Y-Love: Porting your assistive technology from the Windows platform (brand new document to be written).
Quick Notes on the Infrastructure Mechanics
Here's some quick notes on how the infrastructure starts and how applications connect to it.
Starting the Registry (gnome-session)
The AT-SPI registry is the core of the AT-SPI infrastructure. It is the means by which applications and assistive technologies can find and talk to each other.
gnome-session starts the at-spi-registryd, which lives in libexecdir (/usr/lib on Solaris) and also sets the GTK_MODULES environment variable to include gail and atk-bridge. Here's a quick layout of the relevant code in the gnome-session module:
gnome-session/main.c:main
main looks for the GNOME_ACCESSIBILITY environment variable (see ACCESSIBILITY_ENV in gnome-session/headers.h). If set, this environment variable overrides any gconf setting, and is an integer value that indicates whether or not to enable accessibility. "1" means to enable it, "0" means do not.
main looks for the gconf ACCESSIBILITY_KEY, which is defined as "/desktop/gnome/interface/accessibility" in gnome-session/headers.h. This is the key that is set when one enables accessibility via the assistive technologies preferences dialog (the gnome-at-properties application).
If it determines accessibility should be used via the above decisions, main calls gnome-session/gsm-at-startup.c:gsm_assistive_registry_start and then gsm_at_set_gtk_modules.
gnome-session/gsm-at-startup.c:gsm_assistive_registry_start
First adds a listener for property change events on the root window property AT_SPI_IOR, which is set by the at-spi-registryd (see below).
Asynchronously execs the at-spi-registryd via gsm_exec_command_line_async (command, NULL); and sets up a 5 second timer that waits for the AT_SPI_IOR property to be set. The at-spi-registryd is the thing that sets the AT_SPI_IOR property. If the timer times out, we get a "Assistive technology support has been requested for this session, but the accessibility registry was not found. Please ensure that the AT-SPI package is installed. Your session has been started without assistive technology support." dialog and then gnome-session quits. [WDW: I hit the 5 second timeout somewhat frequently on my slow machine, so I think this might need to be extended or perhaps even customizable].
gnome-session/gsm-at-startup.c:gsm_at_set_gtk_modules
Makes sure GTK_MODULES is set (via g_setenv ("GTK_MODULES", g_strjoinv (":", modules), TRUE);) and that gail and atk-bridge are part of it.
The AT_SPI_IOR property is the way any client that has access to the X Windows display can find the AT-SPI registry. The AT-SPI registry is the way all assistive technologies can discover and interact with applications running on the desktop. The application that runs the AT-SPI registry is at-spi_registryd. Its source code lives in the registryd of the at-spi module. The relevant code that sets the AT_SPI_IOR property on the root window lives in registryd/registry-main.c:registry_set_ior.
See bug 163132 for complete details on the current implementation.
Autostarting Assistive Technologies
See the notes on how this is done via gnome-session. The nice thing is that gnome-session has been modified to stop using a special exec_ats property. Instead, it starts up assistive technologies via the same autostart mechanism used for all of GNOME (i.e., a *.desktop file under ~/.config/autostart), and the assistive technology setup (gnome-at-properties and gnome-default-applications-properties) ends up putting the correct *.desktop files in place. gnome-session-properties also provides some startup applications configuration. [WDW: I'm not quite sure how all these work together.]
How Applications Connect to the Registry
The GTK_MODULES environment variable specifies modules that GTK+ applications should load on startup. This is the hook for getting accessibility support into GTK+ applications -- when a GTK+ application runs, it will load the modules listed in GTK_MODULES. The most common setting for GTK_MODULES in an accessibility environment is:
GTK_MODULES=gail:atk-bridge
The gail module sets up the mapping of GTK+ widgets to atk widgets, and the atk-bridge module communicates with the at-spi-registryd that was started by gnome-session. The code that attempts to connect to the at-spi-registryd lives in the at-spi module under atk-bridge/bridge.c. It looks for the AT_SPI_IOR property on the root window to find the at-spi-registryd process and then attempts to connect to it.
At one time prior to the AT_SPI_IOR work, the at-spi-registryd would automagically (e.g., Bonobo activation) start if one attempted to tickle it and it wasn't running. Now, it needs to be explicitly started by gnome-session in order for the AT-SPI infrastructure to work.
How to issue events to reflect "focused" change
It is quite simple for us to notice what is going on when operating on the computer, but it may be a little challenging for a blind person to do so. As a result, we should provide feedback to these users by issuing events to Orca (a flexible accessibility application for people with visual impairments) to tell it what is going on. Specifically, to tell users which object is focused on the desktop, we need to issue "object:state-changed:focused" and "focus:" events to reflect the new object with focus.
The "object:state-changed:focused" Event
When to Emit "object:state-changed:focused" Event
When focus moves from one object to another, this event should be issued to reflect that the former object lost "focused" state and the latter one got it. Consequently, if there is a "focused" change, "object:state-changed:focused" event with regard to the above two objects will be emitted to specify the state change for both of them.
For example, if we press Arrow Right to move focus from one object (named "bin") to another ("boot"), Orca will receive the following events:
object:state-changed:focused(0, 0, None)
source: [icon | bin]
application: [application | gtk-demo]
object:state-changed:focused(1, 0, None)
source: [icon | boot]
application: [application | gtk-demo]Where focused(0, 0, None) represents that "focused" state has been removed from "bin", while focused(1, 0, None) denotes that "focused" state has been added to "boot".
How to Emit "object:state-changed:focused" Event
To illustrate the process of emitting events, we continue to analyse the above example, pressing Arrow Right to move focus from one object ("bin") to another ("boot").
- Get the GTK widget with focus change:
When pressing Arrow Right, gtk_main (gtkmain.c) will connect to a function gtk_widget_real_key_press_event (gtkwidget.c) to cope with this action leading to "focused" change.
Then gtk_widget_grab_focus (gtkwidget.c) will be invoked, which is in charge of finding the widget with "focused" state change by iteratively running gtk_widget_child_focus (gtkwidget.c).
- GTK widget emits "FOCUS_IN_EVENT" or "FOCUS_OUT_EVENT" event:
Getting the widget with "focused" change (e.g., widget "bin" losing "focused" state), gtk_window_real_set_focus (gtkwindow.c) will bind an event (event->focus_change.type = GDK_FOCUS_CHANGE, event->focus_change.in = 0) to this GTK Widget via the function do_focus_change (gtkwindown.c). Here "event->focus_change.in" is a boolean variable (0 represents losing "focused" state, 1 denots obtaining "focused" state). So for Widget "bin", "event->focus_change.in" is set to be 0 because it lost "focused" state in this process, while as to Widget "boot", event->focus_change.in=1.
Then gtk_widget_event_internal (gtkwidget.c) emits "FOCUS_IN_EVENT" event (event->focus_change.in=1) or "FOCUS_OUT_EVENT" event (event->focus_change.in=0) to Gail.
- ATK object emits "focus_event" event and "ATK_STATE_FOCUSED" event:
Receiving the above event, gail_widget_focus_gtk (gailwidget.c) will establish the corresponding ATK Object as to this GTK Widget (e.g., "bin") and emit "focus_event" signal via the function g_signal_emit_by_name (accessible, "focus_event", event->in, &return_val). Here event->in is a boolean variable which has the same meaning with "event->focus_change.in".
The "focus_event" signal will connect to gail_widget_focus_event (gailwidget.c), which emits "ATK_STATE_FOCUSED" event via atk_object_notify_state_change (focus_obj, ATK_STATE_FOCUSED, focus_in), where "focus_obj" refers to the ATK Object with "focused" change and the value of "focus_in" equals to "event->focus_change.in".
- ATK object emits "object:state-changed:focused" event:
The "ATK_STATE_FOCUSED" event will connect to spi_atk_emit_eventv (bridge.c), where "object:state-changed:focused" will be emitted to Orca.
The "focus:" Event
When to Emit "focus:" Event
Let's take the above case as an example again, pressing Arrow Right to move focus from one object(named "bin") to another ("boot"). As the focus on the desktop has changed, it is reasonable to report this alteration to Orca. Consequently, "focus:" event as follows is needed to tell Orca which object is focused currently.
focus:(0, 0, None)
source: [icon | boot]
application: [application | gtk-demo]The above event denotes that the icon named "boot" is focused at present.
How to Emit "focus:" Event
There are several key functions to complete this process:
gail_focus_watcher (gail.c) looks for "event-after" signal, and checks whether the signal is "FOCUS_IN_EVENT" or not. If yes, gail_focus_notify_when_idle (gail.c) will run.
gail_focus_notify_when_idle (gail.c) is used to invoke the function gail_focus_idle_handler (gail.c), in order to connect to gail_focus_notify (gail.c).
gail_focus_notify (gail.c) mainly undertakes two tasks:
- make sure "focus_widget = widget" (the current widget is set to be focus_widget)
- connect to atk_focus_tracker_notify (atkutil.c) if "focus_widget = widget".
atk_focus_tracker_notify (atkutil.c) connects to spi_atk_bridge_focus_tracker (bridge.c) to emit "focus:" event to Orca.
How to add children to GtkContainer
Quick Introduction to GtkContainer
A GTK+ user interface is constructed by nesting widgets inside widget. Container widgets are the inner nodes in the resulting tree of widgets: they contain other widgets. For example, you might have a GtkWindow containing a GtkButton containing a GtkLabel.
Abstractly, GtkContainer is a base class for widgets which contain other widgets. GtkBin, GtkBox, GtkTable, GtkList, GtkMenuShell... are all subclasses of the abstract GtkContainer base class. Generally, all the subclasses could be divided into two major kinds of container widgets in GTK+. The first type of container widget has a single child widget and derives from GtkBin, such as GtkButton, GtkFrame. The second type of container widget has more than one child, such as, GtkBox, GtkTable.
Anyway, all the container widgets are aimed to contain other widget. Consequently, the following will discuss how to add widgets to containers.
How to add a widget to containers
To demonstrate the process of adding a child to containers, we firstly take GtkBox as an example.
Example: GtkBox
GtkBox is a typical subclass of GtkContainer, which could contain more than one child. If we want to add a menubar to a GtkBox, the process is described as follows.
- gtk_container_add will be used to emit a signal container_signals [ADD]
- This "ADD" signal can be connected to gtk_box_add to implement the adding task for Gtk
- Meanwhile, "ADD" signal will link gail_container_add_gtk, which is in charge of emitting a signal "children_changed::add"
- Then, spi_atk_bridge_signal_listener will receive "children_changed::add" signal and report it to Orca through spi_atk_emit_event

