Accessibility for Developers
- 1 Automatic .ui testing
- 1.1 Adding a relation between a label and a widget
- 1.2 Explanations of gla11y warnings
- 1.2.1 'GtkLabel' 'labelfoo' does not specify what it labels
- 1.2.2 'GtkFoo' 'foobar' has no accessibility label
- 1.2.3 'GtkLabel' 'labelfoo' has label-for, but is not labelled-by by 'GtkFoo' 'foobar' line 123
- 1.2.4 'GtkFoo' 'foobar' has labelled-by, but is not label-for by 'GtkLabel' 'labelfoo' line 123
- 1.2.5 'GtkButton' 'foobutton' does not have its own label
- 1.2.6 'GtkEntry' 'fooentry' is referenced by multiple mnemonic_widget 'GtkLabel' 'labelfoo1' line 457 , 'GtkLabel' 'labelfoo2' line 782
- 1.2.7 'GtkTextView' 'fooentry' has both a mnemonic 'GtkLabel' 'descriptionlabel' line 152 and labelled-by relation
- 1.2.8 'GtkFoo' 'foofoo' has the same id as other elements 'GtkFoo' 'foobar' line 1234
- 1.3 Fixing existing issues
- 1.4 Coping with false positives
- 2 Enable accessibility support
- 3 Script to read a11y events
- 4 Accerciser
- 5 Debugging a11y
- 6 Reporting an a11y bug
Automatic .ui testing
Some automatic accessibility checks are automatically performed by the gla11y tool on the .ui files during build time. That will fail the build if new issues are introduced (existing issues are suppressed). Here are explanations about warnings produced by the tool.
The tool mostly checks for labelling: GtkLabel widgets are usually used to label another widget, and some widget indeed do not make sense without some context. The relation is often obvious visually, but screen readers can not guess that, so the .ui file needs to specify widget labels are intended for, otherwise the label and the widget are orphan. The tool looks both for orphan labels and for orphan widgets. Some widgets always need labelling (e.g. GtkEntry), so warnings will always be printed for them. Others may not always need one, and so warnings will only be printed if there are orphan labels alongside. Conversely, warnings about orphan labels are currently only printed if there are orphan widgets. That means that fixing one relation between a label and a widget may make disappear all other warnings about remaining orphan labels because there are no orphan widgets to be labelled any more, and conversely that may make disappear a lot of warnings about remaining orphan widgets because there are no orphan labels that they could be labelled by any more.
In case of doubt, advise can be sought for on the libreoffice-design mailing list
Adding a relation between a label and a widget
Once it is determined that a relation is missing between a label (or anything that can be used as a label because it contains text, such as a GtkRadioButton, a GtkCheckButton, or an image with an accessible-name) and a widget, there are two ways to express the relation:
- The GtkLabel can probably be used to define a keyboard shortcut for the widget (this is by large the most common case). In that case an underscore character should be added in the label to specify the shortcut, and the following xml bits should be added to specify the relation with the widget triggered by the mnemonic, and the accessibility relation will be added automatically:
<property name="use_underline">True</property> <property name="mnemonic_widget">the_widget_id</property>
- If it does not make sense to set a keyboard shortcut from the label (for instance because the label is actually for several widgets, or because it is a GtkRadioButton or GtkCheckButton, whose keyboard shortcut is already used to operate it), the accessibility relation should be set by hand by adding the following xml bits to the label:
<accessibility> <relation type="label-for" target="the_widget_id"/> </accessibility>
and to the widget:
<accessibility> <relation type="labelled-by" target="the_label_id"/> </accessibility>
Yes, both label-for and labelled-by relations need to be set. An error is emitted by the tool if they do not match.
After having added a relation between a widget and a label in a .ui file, you can check that the warning has disappeared by running by hand
(but that will also check other .ui files).
Also, you should ideally check that labelling actually works. A simple way is to run
orca -e braille-monitor
and restart libreoffice with the new .ui file. On clicking on the widget which has just received a label, the braille monitor should now show both the content of the widget and the content of the label. Orca might also be speaking a lot, you can use insert-s to stop the speech synthesis.
Explanations of gla11y warnings
'GtkLabel' 'labelfoo' does not specify what it labels
This label (or another within the same scope) may have to be associated with a widget which does not have a label yet. One should thus look for the widgets which do not have a label, and add appropriate accessibility relations.
'GtkFoo' 'foobar' has no accessibility label
This widget does not have labelling. If there are orphan labels alongside, they may be potential candidate for labelling this widget, as described above about the does not specify what is labels warning. If there aren't orphan labels, perhaps another widget can be used as a label, for instance a GtkRadioButton or a GtkCheckButton. If there aren't candidates (because the context is obvious visually), depending on the widget type one can set a tooltip_text property, or a placeholder_text property, to provide a textual content that the screen reader will be able to show to the user.
'GtkLabel' 'labelfoo' has label-for, but is not labelled-by by 'GtkFoo' 'foobar' line 123
label-for / labelled-by relations have to be set by pair: if we have a label-for relation, we also need the corresponding labelled-by relation. Here, one probably just need to add to the 'foobar' widget a 'labelled-by' relation targetting 'labelfoo'.
'GtkFoo' 'foobar' has labelled-by, but is not label-for by 'GtkLabel' 'labelfoo' line 123
label-for / labelled-by relations have to be set by pair: if we have a labelled-by relation, we also need the corresponding label-for relation. Here, one probably just need to add to the 'labelfoo' widget a 'label-for' relation targetting 'foobar'.
Some buttons are designed to only embed an image (e.g. an arrow). For sight-impaired users, a label is needed to convey the information in a textual form (e.g. notably the direction of the arrow). If a visual label actually already exists, the label-for/labelled-by relation just needs to be added. If no visual label already exists, one can provide extra labelling by including a tooltip_text property in the GtkButton, for instance:
<property name="tooltip_text" translatable="yes" context="mydialog|forward">Next page</property>
This will actually be also useful to sighted users who want to make sure of the consequence of clicking the button.
A tooltip_text is not enough for GtkCheckButton that lacks a label property, because that type of widget is supposed to be using its own label, and the tooltip is only supposed to provide more lengthy explanations. In such a case, there is usually a visual label to add a relation with. If there is really none, the accessibility labelling can be provided explicitly with
<child internal-child="accessible"> <object class="AtkObject" id="checkbutton_legend-atkobject"> <property name="AtkObject::accessible-name" translatable="yes">The label for the checkbutton</property> </object> </child>
'GtkEntry' 'fooentry' is referenced by multiple mnemonic_widget 'GtkLabel' 'labelfoo1' line 457 , 'GtkLabel' 'labelfoo2' line 782
Several labels with mnemonics have fooentry as target. This may happen when e.g. only one of these labels is visible at a time (but in that case one should make sure that either the two labels are exactly the same, or the screen reader indeed gets the proper label at the proper time) and then a suppression rule can be added. But otherwise it is often an indication that one of these labels was actually mistargetted and there is an orphan widget that it was supposed to target instead. One then just need to fix the target of labels.
'GtkTextView' 'fooentry' has both a mnemonic 'GtkLabel' 'descriptionlabel' line 152 and labelled-by relation
fooentry has both a labelling relation and a label with mnemonic targetting it. This is very doubtful, and is usually an indication that one of these labels was actually mistargetted and there is an orphan widget that it was supposed to target instead. One then just need to fix the target of labels.
'GtkFoo' 'foofoo' has the same id as other elements 'GtkFoo' 'foobar' line 1234
foofoo and foobar have the same 'id' property, which is not allowed. The id of one of them needs to be renamed.
Fixing existing issues
For people who plan to work on fixing the existing issues (and thus emptying the suppression files), you can disable adding the -s option in solenv/gbuild/UIConfig.mk's gb_UIConfig_gla11y_PARAMETERS, to see the warnings during the build. They will also be dumped to workdir/UIConfig/*.a11yerrors. Once the issues are fixed in the .ui files, the suppression files can be regenerated by enabling the line which adds the -g option. One can run only the check only on a given LO module by running make UIConfig_themodule (or make UIConfig_modules/themodule if it is a submodule).
Coping with false positives
In some cases, gla11y emits warnings where there is actually no accessibility issue (e.g. because the context is obvious, or the label does not actually bring meaning, thus making the output heavier than useful, etc.). In that case, one can suppress the warnings completely by putting suppression rules to solenv/sanitizers/ui/themodulename.false . For instance, one can use
./bin/gla11y -g suppr.false path/to/file.ui
to generate a suppression file for the whole .ui file, and from that file copy/paste the lines corresponding to the warning to be suppressed into solenv/sanitizers/ui/themodulename.false .
Enable accessibility support
First of all, don't forget to compile the accessibility (from now on, a11y) support in LibreOffice:
Before running LibreOffice run this command to enable a11y in the GNOME toolkit so the program knows that it should start sending events:
gsettings set org.gnome.desktop.interface toolkit-accessibility true
Script to read a11y events
With the following small python script you will be able to log all the a11y events happening in your system:
#!/usr/bin/python import pyatspi def onFocused(e): try: if e.host_application.name != "soffice": return except: return print(e) def onKey(e): if e.type == pyatspi.KEY_PRESSED_EVENT: eType = "PRESSED" else: eType = "RELEASED" print("%s: %s" % (eType, e.event_string)) pyatspi.Registry.registerKeystrokeListener( onKey, mask=range(128), kind=(pyatspi.KEY_PRESSED_EVENT, pyatspi.KEY_RELEASED_EVENT)) pyatspi.Registry.registerEventListener(onFocused, "object:") pyatspi.Registry.start()
Accerciser is a software to check if an application is providing correct information to assistive technologies and automated testing frameworks. It's available in most distros, just:
yum install accerciser
apt-get install accerciser
Accerciser is good to browse the accessibility tree or to do some quick queries with its python console, but the event monitor is a bit buggy; to debug events it's better to use the script attached in the previous section.
Two basic points to make applications accessible for visually impaired:
- Keyboard navigation
There is one trivial way to detect the first kind of a11y bugs: just try to use the application without your mouse, only with keyboard commands (arrows, tabs). If you can't reach every place in the application there's definitely something wrong.
It may be possible that you can reach some UI element with the keyboard but the corresponding a11y event is not being sent. You can use accerciser to read these events and check if the transitions are being properly informed to a11y-enabling technologies, like a screen reader. Use the "Event monitor" tab, enable "Events monitoring" and "focus" type. Now navigate through LibO window with your keyboard and check that the events are being reported when you change the focus.
Reporting an a11y bug
If it's related to events, it's a good idea that you attach a tuned version of the listener script that prints only the relevant events and attributes for that bug.