This blog post is a friendly note to application maintainers out there to think carefully about how accessible your software is, especially when using non-standard widgets or canvases. Accessibility is important and it can also be hard. So think about it early and think about it often. Below are some notes from in the trenches, and if you think about a11y from the beginning things won’t end up this way for you.
One issue to rise out of the current module proposal discussion is Conduit and its accessibility. In order to be a good GNOME application, Conduit needs to be accessible. The use of a canvas to render shiny goodness manually means that we sacrifice lots of ready made accessibility.
Conduit has two major issues on the accessibility front. Can I control it without using a mouse? And can I tell what is happening when i’m using a screen reader? Sadly, the current answer is no and no. Having decided to change this, I’ve spent the weekend looking at how to pass more useful information to the accessibility infrastructure.
We use GooCanvas, which is actually already accessible. Each item that is added to the canvas is available to accessibility tools, be it a raw canvas item or a gtk widget. Basic information such as its size and position is exposed without any effort on the part of the application author. Whilst experimenting, I have hit the following limitations:
1. Without implementing more ATK interfaces, the only Conduit relevant information I can expose is a name and a description for each object. Its not easy to implement new interfaces without throwing away the existing interface. This is really quite limiting, and I don’t think it will make for a good enough user experience. (I don’t consider this a GooCanvas bug, but instead something that Conduit needs to fix on its own).
2. GooCanvas doesn’t seem to implement the children-changed signal. In testing this has meant that accerciser is unaware of changes on the canvas, and i’ve needed to keep restarting accerciser.
So GooCanvas needs a patch to fix children-changed. Lets defer that to later, i’ll look at it early in the next cycle. For now I need to work on fixing Conduit. Hopefully I can derive from the existing Goocanvas ATK objects and extend, as opposed to rewriting it all. Sadly not, the GooCanvas accessibility code isn’t available from python, so I need to re-implement everything. Again, potential patch to pygoocanvas deferred to early next cycle.
From reading the Devhelp for ATK, it seems that I just need to implement an atk.Object (and assorted interfaces) and an atk.ObjectFactory for each accessible GObject I want to have. So how do I implement an interface in python? My initial thought was that I would just have to derive from them:
>>> import gobject
>>> import atk
>>> class Foo(atk.Object, atk.Component):
... def some_method(self, param):
... pass
...
Is it enough?
>>> gobject.type_interfaces(Foo)
[]
GObject doesn’t seem to know that I want to implement an interface. So my code is not enough. After poking this for some time it turns out there are two fundamental pieces of fail with my first attempt.
At some point, pygtk claimed to automatically register things for me. In this case, maybe more generally, this is not the case. I need to register my class with the GObject type system:
>>> gobject.type_register(Foo)
>>> gobject.type_interfaces(Foo)
[<GType AtkComponent (136469760)>]
Victory! At this point I was hoping my code would spring to life, but it didn’t. Poke, poke, poke. When overriding a GObject method from python or implementing an interface you need a do_ prefix. Presumably this is to make the introspection foo a little less scary. So I needed:
>>> class Foo(atk.Object, atk.Component):
>>> def do_some_method(self, param):
>>> pass
Once you have implemented your accessible object, you need an accessible object factory, and you need to register it with the default ATK registry. I’m leaving some bits out here, but basically:
>>> class FooFactory(atk.ObjectFactory):
... def do_create_accessible(self, obj):
... return Foo(obj)
...
>>> gobject.type_register(FooFactory)
>>> atk.get_default_registry().set_factory_type(GooFoo, FooFactory)
Now when ATK encounters a GooFoo object it should ask FooFactory for an accessible version of it. It will get a new Foo object.
Unfortunately this is not what happens. !”£$. A new FooFactory is instanced, and then a GCritical fires, because create_accessible isn’t happening and something is then trying to ref a null. Sigh. Epic Fail. After examining pygtk, it turns out that the create_accessible method of atk is blacklisted when bindings are generated. I unblacklisted it and encountered a nice compile error. If you poke around (or ask Rob), you’ll see that create_accessible is defined without a self, its essentially a @staticmethod. This is an edge case that the code generator doesn’t currently handle, and the wrapper ends up trying to pass too many arguments. So now I need to figure out a suitable patch for pyatk (or the code generator) before I can continue…
Big thanks to Rob for helping me with this so far, your big chunk of clue has been most welcome. You can has beer.