Accessibility for developers
- 1 Automatic .ui testing
- 1.1 Adding a relation between a label and a widget
- 1.2 Fixing existing issues
- 1.3 Coping with false positives
- 1.4 Explanations of gla11y warnings
- 1.4.1 'GtkLabel' 'labelfoo' does not specify what it labels
- 1.4.2 'GtkFoo' 'foobar' has no accessibility label
- 1.4.3 'Foo' 'foobar' has no accessibility label
- 1.4.4 'GtkLabel' 'labelfoo' has label-for, but is not labelled-by by 'GtkFoo' 'foobar' line 123
- 1.4.5 'GtkFoo' 'foobar' has labelled-by, but is not label-for by 'GtkLabel' 'labelfoo' line 123
- 1.4.6 'GtkButton' 'foobutton' does not have its own label
- 1.4.7 'GtkEntry' 'fooentry' is referenced by multiple mnemonic_widget 'GtkLabel' 'labelfoo1' line 457 , 'GtkLabel' 'labelfoo2' line 782
- 1.4.8 'GtkTextView' 'fooentry' has both a mnemonic 'GtkLabel' 'descriptionlabel' line 152 and labelled-by relation
- 1.4.9 'GtkFoo' 'foofoo' has the same id as other elements 'GtkFoo' 'foobar' line 1234
- 1.4.10 'GtkFoo' 'foo' visibility conflicts with paired 'GtkBar' 'bar' line 1234
- 2 Enable accessibility support
- 3 Script to read a11y events
- 4 Accerciser
- 5 Debugging a11y
- 6 Reporting an a11y bug
Automatic .ui testing
Some 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 for now). 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 widgets indeed do not make sense to users without some context. The relation is often obvious visually, but screen readers can not guess that, so the .ui file needs to specify the widgets that labels are intended for, otherwise the labels and the widgets 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. That means that fixing one relation between a label and a widget 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. Conversely, however, warnings about orphan labels are always printed, even if there are no orphan widgets.
In case of doubt, advice 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 the following bit 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.
The widgets you want to relate may not have ids yet, but you can freely add some.
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.
Fixing existing issues
For people who plan to work on fixing the existing issues (and thus emptying the suppression files, which is the eventual goal), here is a process:
- Pick up from the output of solenv/sanitizers/ui/*.suppr a .ui file to fix, make sure to know how to reach it in the LibreOffice interface to understand its semantics.
- Move the corresponding lines away from the .suppr file (but keep them at hand in case some false positives should be suppressed)
- Run "make UIConfig" to get the uncovered list of warnings (touch the .ui file if make does not do anything).
- Look over the first warnings, very often there is both a warning for an orphan widget and for an orphan label, and one just needs to add a relation to fix both at the same time.
- Fix the issue in the .ui file (see below for the details), optionally check the result with a screen reader by installing the modified version (either the whole libreoffice, or simpler, just the single .ui file). In case of false positives, move the corresponding lines from the .suppr file to the .false file (create it if it doesn't exist yet).
- Run "make UIConfig" again to check that the warning went away.
- Repeat from step 4 until gla11y does not emit any warning any more.
- Submit these changes.
- Restart from step 1 with another .ui file :)
Note: if you are unsure about how to fix some warning, better leave the suppression rule in the .suppr file and let somebody else to fix the issue. It is way better to leave a suppressed warning (which we will then know still needs fixing) than wiping it out wrongly, which would mean hiding an accessibility issue.
To get all warnings for all files at a time, one can disable adding the -s option in solenv/gbuild/UIConfig.mk's gb_UIConfig_gla11y_PARAMETERS and run "make UIConfig" They will also be dumped to workdir/UIConfig/*.a11yerrors. One can also regenerate suppression files by enabling the line which adds the -g option. One can also run the checks only on a given LibreOffice 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 existing issues, the suppression rules can be taken from the .suppr file. Otherwise, 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 .
Important: do not put a suppression rule in the .false file unless you are sure that there is nothing to fix here. If you just happen not to know how to fix it, keep the suppression rule in the .suppr file.
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.
Such orphan labels are reported even if there is no such orphan widget (but that is not made fatal), because labels being orphan is most of the time doubtful: labels are usually there for a reason, and not associating them with a widget means that some meaning is rendered visually but not through screen readers.
- In some cases labels are used to convey independent information, e.g. in the find&replace dialog to report the number of replacements. These should not be using the LABEL role, but a STATIC role, so that screen readers know how to report them appropriately. This can be done by adding
<accessibility> <role type="static"/> </accessibility
in the <object class="GtkLabel">.
- In some cases there are two labels for a widget: one before the widget, and one after the widget. For instance, in the Options dialog, LibreOffice->General panel, "Interpret as years between" and "and 2029" surround the editable field. For such a case we will need another "label-for-after" accessibility relation, in addition to the standard label-for. For now, the suppression rule should be kept in the .suppr file, so we remember to fix them once the new relation is available. Here is the list of such case, to be extended as noticed (this is a quite rare case):
cui/uiconfig/ui/optgeneralpage.ui://GtkLabel[@id='toyear'] orphan-label vcl/uiconfig/ui/printdialog.ui://GtkLabel[@id='totalnumpages'] orphan-label vcl/uiconfig/ui/printdialog.ui://GtkLabel[@id='pagemargintxt2'] orphan-label vcl/uiconfig/ui/printdialog.ui://GtkLabel[@id='sheetmargintxt2'] orphan-label vcl/uiconfig/ui/printdialog.ui://GtkLabel[@id='cbPrintOrder'] orphan-label svx/uiconfig/ui/compressgraphicdialog.ui://GtkLabel[@id='label13'] orphan-label svx/uiconfig/ui/compressgraphicdialog.ui://GtkLabel[@id='label14'] orphan-label svx/uiconfig/ui/compressgraphicdialog.ui://GtkLabel[@id='label16'] orphan-label
- In some cases the label is only used to describe a group of options, and is
thus not for one precise widget but a group of them. The label is typically the first child of a vertical GtkBox. In this case, a GtkFrame should be inserted between the upper container and the GtkBox, and the GtkLabel set as
<child type="label"> ... GtkLabel definition ... </child>
of the GtkFrame.
- In some cases labels are really there for quite trivial reasons (e.g. in writer's table sort dialog, "Setting" is there only for visual coherence but does not provide any meaning). In such cases a suppression rule should be added to the .false file.
'GtkFoo' 'foobar' has no accessibility label
This widget does not have labelling. If there are orphan labels alongside, they may be potential candidates 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.
'Foo' 'foobar' has no accessibility label
The warning mentioned above is only for Gtk widgets, for which gla11y knows quite well which types should have labelling, and which types do not need labelling. For libreoffice-specific widgets (vcl*, cui*, etc.), gla11y does not have this knowledge, but by default it is assumed that they need labelling.
If it is known that a Libreoffice-specific widget types never needs labelling, this knowledge can be added to solenv/gbuild/UIConfig.mk, see for instance the option used for foruilo-RefButton.
If it is known that a Libreoffice-specific widget types always needs labelling (just like a GtkEntry does), this can also be added there, see for instance the option used for foruilo-RefEdit.
Otherwise, the precise widgets which need labelling should be fixed as explained in the previous section, and the precise widgets which do not need labelling should be mentioned in a suppression line in the .false file.
'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 needs 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 needs 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. Quite often in that case, there is a visual label alongside, and one can just add the relation. If there is really no label because the context is obvious visually, 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 to the .false file. 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.
'GtkFoo' 'foo' visibility conflicts with paired 'GtkBar' 'bar' line 1234
'foo' is labelled by 'bar', and one of them has visibility enabled while the other has visibility disabled. Visibility should be coherent for labelling to work.
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.