Support Accesskit
Maintainer merge checklist
- [ ] Title is descriptive/clear for inclusion in release notes.
- [ ] Applied a
Component: xxxlabel. - [ ] Applied the
api-deprecationorapi-breaklabel. - [ ] Applied the
release-highlightlabel to be highlighted in release notes. - [ ] Added to the milestone version it was merged into.
- [ ] Unittests are included in PR.
- [ ] Properly documented, including
versionadded,versionchangedas needed.
Running the accessibility.py example, I'm stuck on this panic:
/home/sanotehu/.local/share/virtualenv/lisien3.13/bin/python /home/sanotehu/src/kivy/examples/widgets/accessibility.py -d
[INFO ] [Kivy ] v3.0.0.dev0
[INFO ] [Kivy ] Installed at "/home/sanotehu/src/kivy/kivy/__init__.py"
[INFO ] [Python ] v3.13.5 (main, Jul 18 2025, 13:57:58) [GCC 13.3.0]
[INFO ] [Python ] Interpreter at "/home/sanotehu/.local/share/virtualenv/lisien3.13/bin/python"
[INFO ] [Logger ] Purge log fired. Processing...
[INFO ] [Logger ] Purge finished!
[INFO ] [Factory ] 195 symbols loaded
[DEBUG ] [Cache ] register <kv.resourcefind> with limit=None, timeout=60
[DEBUG ] [Cache ] register <kv.lang> with limit=None, timeout=None
[DEBUG ] [Cache ] register <kv.image> with limit=None, timeout=60
[DEBUG ] [Cache ] register <kv.atlas> with limit=None, timeout=None
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl3, img_pil (img_ffpyplayer ignored)
[DEBUG ] [Cache ] register <kv.texture> with limit=1000, timeout=60
[DEBUG ] [Cache ] register <kv.shader> with limit=1000, timeout=3600
[DEBUG ] [Cache ] register <kv.graphics.texture> with limit=None, timeout=None
[DEBUG ] [Text ] Provider <pango> ignored by config
[INFO ] [Text ] Provider: sdl3(['text_pango'] ignored)
[DEBUG ] [App ] Loading kv </home/sanotehu/src/kivy/examples/widgets/accessible.kv>
[DEBUG ] [App ] kv </home/sanotehu/src/kivy/examples/widgets/accessible.kv> not found
[INFO ] [Window ] Provider: sdl3
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] Backend used <sdl3>
[INFO ] [GL ] OpenGL version <b'4.6 (Compatibility Profile) Mesa 25.0.7-0ubuntu0.24.04.2'>
[INFO ] [GL ] OpenGL vendor <b'Intel'>
[INFO ] [GL ] OpenGL renderer <b'Mesa Intel(R) UHD Graphics 620 (WHL GT2)'>
[INFO ] [GL ] OpenGL parsed version: 4, 6
[INFO ] [GL ] Shading version <b'4.60'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[DEBUG ] [Shader ] Fragment compiled successfully
[DEBUG ] [Shader ] Vertex compiled successfully
[DEBUG ] [ImageSDL3 ] Load </home/sanotehu/src/kivy/kivy/data/glsl/default.png>
[INFO ] [Window ] auto add sdl3 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[DEBUG ] [Resource ] add </usr/share/fonts> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype/unifont> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype/urw-base35> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype/porson> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype/malayalam> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype/font-awesome> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/opentype/noto> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/X11> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/X11/encodings> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/X11/encodings/large> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/X11/util> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/X11/misc> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/X11/Type1> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-devanagari> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-kannada> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-gujr-extra> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/Sarai> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/Gubbi> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-deva-extra> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/samyak> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/abyssinica> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/dejavu> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-kalapi> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-guru-extra> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/tibetan-machine> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lyx> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/unifont> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/samyak-fonts> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-beng-extra> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/Navilu> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/ubuntu> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/glyphicons> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-malayalam> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/ttf-bitstream-vera> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/annapurna> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/quicksand> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-orya-extra> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/droid> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/alegreya> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/poppins> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/baloo> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/roboto> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/henny-penny> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/happy-monkey> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/lato> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/oswald> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/montserrat> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/gfonts/nunito-sans> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/ttf-khmeros-core> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-telugu> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-gujarati> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-yrsa-rasa> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/malayalam> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/libreoffice> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/sinhala> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/Gargi> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/liberation> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/tlwg> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/fonts-telu-extra> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-tamil-classical> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/font-awesome> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-assamese> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-tamil> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/padauk> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/kacst> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lato> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-punjabi> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/liberation2> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/baskerville> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/pagul> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/Sahadeva> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/noto> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/Nakula> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lao> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/teluguvijayam> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-bengali> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/lohit-oriya> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/freefont> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/truetype/kacst-one> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/cMap> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/cmap> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/type1> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/type1/texlive-fonts-recommended> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/type1/urw-base35> in path list
[DEBUG ] [Resource ] add </usr/share/fonts/type1/gsfonts> in path list
[DEBUG ] [Resource ] add </usr/local/share/fonts> in path list
[DEBUG ] [Resource ] add </home/sanotehu/src/kivy/kivy/data/fonts> in path list
[DEBUG ] [Base ] Create provider from mouse
[DEBUG ] [Base ] Create provider from probesysfs
[DEBUG ] [ProbeSysfs ] using probesysfs!
[DEBUG ] [ProbeSysfs ] found device: MSFT0001:02 04F3:3052 Touchpad at /dev/input/event5
[INFO ] [ProbeSysfs ] device match: /dev/input/event5
[INFO ] [MTD ] Read event from </dev/input/event5>
[INFO ] [Base ] Start application main loop
[INFO ] [GL ] NPOT texture support is available
thread '<unnamed>' panicked at /home/sanotehu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/accesskit_consumer-0.26.0/src/tree.rs:305:54:
called `Option::unwrap()` on a `None` value
stack backtrace:
0: 0x7bf3557a132d - <std::sys::backtrace::BacktraceLock::print::DisplayBacktrace as core::fmt::Display>::fmt::hdcfcb6d4c8489523
1: 0x7bf35577e7c3 - core::fmt::write::h8a494366950f23bb
2: 0x7bf3557a0bff - std::io::Write::write_fmt::h6556609fca33d0b1
3: 0x7bf3557a1093 - std::sys::backtrace::BacktraceLock::print::hb2a626a81e06b2dc
4: 0x7bf3557a06de - std::panicking::rust_panic_with_hook::h33ac55f64bbd807d
5: 0x7bf3557cbab5 - std::panicking::begin_panic_handler::{{closure}}::h30e7cb89678a57fe
6: 0x7bf3557cba49 - std::sys::backtrace::__rust_end_short_backtrace::hed60f27456c16ced
7: 0x7bf3557cc3fc - __rustc[de2ca18b4c54d5b8]::rust_begin_unwind
8: 0x7bf35577dc5f - core::panicking::panic_fmt::h62f63d096dd276af
9: 0x7bf35577e49b - core::panicking::panic::h89a5f2df32b0508a
10: 0x7bf355780388 - core::option::unwrap_failed::he1a8284b5a1e2496
11: 0x7bf3556e34c3 - accesskit_consumer::tree::Tree::process_changes::hd683d74f2b54d383
12: 0x7bf3556d3856 - accesskit::unix::_::<impl accesskit::unix::Adapter>::__pymethod_update_if_active__::hf351e11eb5b5bc71
13: 0x7bf3556b60af - pyo3::impl_::trampoline::cfunction_with_keywords::h3ac519c1c0961031
14: 0x7bf3556d489a - accesskit::unix::_::_::__INVENTORY::trampoline::hf7ab11bb25ba721d
15: 0x5be37401021e - method_vectorcall_VARARGS_KEYWORDS
at /home/sanotehu/src/Python-3.13.5/Objects/descrobject.c:358:14
16: 0x5be37400b704 - _PyObject_VectorcallTstate
at /home/sanotehu/src/Python-3.13.5/./Include/internal/pycore_call.h:168:11
17: 0x5be37400b704 - PyObject_Vectorcall
at /home/sanotehu/src/Python-3.13.5/Objects/call.c:327:12
18: 0x5be373f7cecc - _PyEval_EvalFrameDefault
at /home/sanotehu/src/Python-3.13.5/Python/generated_cases.c.h:813:23
19: 0x7bf356cf5815 - __pyx_f_4kivy_6_clock_10ClockEvent_tick
at /home/sanotehu/src/kivy/kivy/_clock.c:7103:17
20: 0x7bf356cf9752 - __pyx_f_4kivy_6_clock_11CyClockBase__process_events
at /home/sanotehu/src/kivy/kivy/_clock.c:15557:22
21: 0x7bf356cfaca3 - __pyx_pf_4kivy_6_clock_11CyClockBase_30_process_events
at /home/sanotehu/src/kivy/kivy/_clock.c:15903:15
22: 0x7bf356cfaca3 - __pyx_pw_4kivy_6_clock_11CyClockBase_31_process_events
at /home/sanotehu/src/kivy/kivy/_clock.c:15887:13
23: 0x5be37400b704 - _PyObject_VectorcallTstate
at /home/sanotehu/src/Python-3.13.5/./Include/internal/pycore_call.h:168:11
24: 0x5be37400b704 - PyObject_Vectorcall
at /home/sanotehu/src/Python-3.13.5/Objects/call.c:327:12
25: 0x5be373f7cecc - _PyEval_EvalFrameDefault
at /home/sanotehu/src/Python-3.13.5/Python/generated_cases.c.h:813:23
26: 0x5be3740f6e87 - PyEval_EvalCode
at /home/sanotehu/src/Python-3.13.5/Python/ceval.c:604:21
27: 0x5be374114e70 - run_eval_code_obj
at /home/sanotehu/src/Python-3.13.5/Python/pythonrun.c:1381:9
28: 0x5be3741148fe - run_mod
at /home/sanotehu/src/Python-3.13.5/Python/pythonrun.c:1466:19
29: 0x5be374114835 - pyrun_file
at /home/sanotehu/src/Python-3.13.5/Python/pythonrun.c:1295:15
30: 0x5be37411469c - _PyRun_SimpleFileObject
at /home/sanotehu/src/Python-3.13.5/Python/pythonrun.c:517:13
31: 0x5be3741144b8 - _PyRun_AnyFileObject
at /home/sanotehu/src/Python-3.13.5/Python/pythonrun.c:77:15
32: 0x5be37411dba7 - pymain_run_file_obj
at /home/sanotehu/src/Python-3.13.5/Modules/main.c:410:15
33: 0x5be37411dba7 - pymain_run_file
at /home/sanotehu/src/Python-3.13.5/Modules/main.c:429:15
34: 0x5be37411dba7 - pymain_run_python
at /home/sanotehu/src/Python-3.13.5/Modules/main.c:696:21
35: 0x5be37411dba7 - Py_RunMain
at /home/sanotehu/src/Python-3.13.5/Modules/main.c:775:5
36: 0x5be37411d71b - Py_BytesMain
at /home/sanotehu/src/Python-3.13.5/Modules/main.c:829:12
37: 0x7bf37ba2a1ca - __libc_start_call_main
at ./csu/../sysdeps/nptl/libc_start_call_main.h:58:16
38: 0x7bf37ba2a28b - __libc_start_main_impl
at ./csu/../csu/libc-start.c:360:3
39: 0x5be3740958a5 - _start
40: 0x0 - <unknown>
[INFO ] [Base ] Leaving application in progress...
Traceback (most recent call last):
File "/home/sanotehu/src/kivy/examples/widgets/accessibility.py", line 157, in <module>
AccessibleApp().run()
~~~~~~~~~~~~~~~~~~~^^
File "/home/sanotehu/src/kivy/kivy/app.py", line 962, in run
runTouchApp()
~~~~~~~~~~~^^
File "/home/sanotehu/src/kivy/kivy/base.py", line 574, in runTouchApp
EventLoop.mainloop()
~~~~~~~~~~~~~~~~~~^^
File "/home/sanotehu/src/kivy/kivy/base.py", line 339, in mainloop
self.idle()
~~~~~~~~~^^
File "/home/sanotehu/src/kivy/kivy/base.py", line 379, in idle
Clock.tick()
~~~~~~~~~~^^
File "/home/sanotehu/src/kivy/kivy/clock.py", line 733, in tick
self.post_idle(ts, self.idle())
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/home/sanotehu/src/kivy/kivy/clock.py", line 776, in post_idle
self._process_events()
~~~~~~~~~~~~~~~~~~~~^^
File "kivy/_clock.pyx", line 620, in kivy._clock.CyClockBase._process_events
File "kivy/_clock.pyx", line 653, in kivy._clock.CyClockBase._process_events
File "kivy/_clock.pyx", line 649, in kivy._clock.CyClockBase._process_events
File "kivy/_clock.pyx", line 218, in kivy._clock.ClockEvent.tick
File "/home/sanotehu/src/kivy/kivy/core/accessibility/__init__.py", line 56, in check_for_updates
if not self.window.accessibility.update(self.root_window_changed):
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/sanotehu/src/kivy/kivy/core/accessibility/accessibility_accesskit.py", line 162, in update
events = self.adapter.update_if_active(lambda: self._build_tree_update(root_window_changed))
pyo3_runtime.PanicException: called `Option::unwrap()` on a `None` value
Process finished with exit code 1
This would appear to be the relevant Rust code:
#[pymethods]
impl Adapter {
...
pub fn update_if_active(&mut self, py: Python<'_>, update_factory: Py<PyAny>) {
self.0.update_if_active(|| {
let update = update_factory.call0(py).unwrap();
update.extract::<TreeUpdate>(py).unwrap().into()
});
}
}
in unix.rs
The TreeUpdate I passed in is not None, though. So I think one of the other calls there ends up with None...?
I guess that was an AccessKit bug. It doesn't happen that way with 0.7.0.
Hm, it seems like I've pushed an update to the window, but Mint's default screen reader doesn't read it...
If your comment at the top of _update_root_window_focus is true, then this might explain why.
This is not called the first time the window gets focused, but it really should be.
Creating a window that doesn't have the focus won't raise the appropriate events for a screen reader to notice it on Linux.
I wasn't really the one who wrote that, and it's not correct.
Although... line 30 there just... returns before sending anything to the adapter...
And when I delete that, I hear things.
Thanks!
I'm making some good progress, including a way of checking for updated widgets that works for all widgets, with no need for changes to any Kivy app.
Orca has an option, "Speak object under mouse," which I'd assume would make it read a label whenever I mouse over it, but it's not doing that. I thought this might be because I'd gotten a widget's rectangle wrong, but I haven't gotten any widgets in Kivy to say anything without being clicked on, no matter where I move the mouse; and none said anything without being focused, meaning that AccessibleLabel is always silent.
Since I happen to be implementing a new form of focus anyhow, I may as well make it possible to "focus" widgets that do not inherit FocusBehavior just by colliding with the mouse... but this feels like guesswork. I have no idea if it would work well for any purpose; if, indeed, I'm not missing something really obvious in Orca's operation that would let me, the user of Orca, decide how I want it to read the labels node tree that the app has. And I'm pretty sure the node tree is what it should be, for at least this tiny example app.
If anyone with...basic literacy in actual usage of screen readers would like to give me tips on how I should be testing, please do.
I flipped the coordinate space around two or three times, and have yet to get Orca's Mouse Review to say anything when I mouse over a Kivy app. @DataTriny can you help?
If I remember correctly, I was not able to get Orca's mouse review working with the SDL example for our C bindings either. At the time I suspected a bug in how Orca links AT-SPI apps with their process. You could try using Accerciser or KDE's Accessibility Inspector to check whether the bounds seem fine.
I'll try your branch at least on Windows when I have a bit of time. I really appreciate the efforts you put into this!
@clayote kudos to the great efforts. I have a few questions and suggestions:
- Why are we introducing new properties like accesible x, y , etc., can't we use the x, y, etc., properties of the widget? Any examples why and how they might differ?
- I believe in a few cases usage of the kivy event dispatch would be better than direct calling, as this would follow current kivy paradigm.
- Why do we need to expose or have some focus related properties in widget itself? Can't it be made user dependent on whether they want to add the focus behavior or not? By default we can either skip having any accessibility properties or behaviour and can introduce mixins.
- I see a update widget list global is being used. I want to understand first of all why and then is there any way to have alternative implementation with a global, I say this in light of free-threaded python build and to keep in mind globals are bad for concurrency as a start.
Why are we introducing new properties like accesible x, y , etc., can't we use the x, y, etc., properties of the widget? Any examples why and how they might differ?
Consider a checkbox with a label next to it. From the sighted user's perspective, this is two widgets, with two sizes. Perhaps clicking the label checks the checkbox, but it's not surprising if not.
To a screenreader user, these should instead be presented like one widget, equivalent to a toggle button the size and shape of the box containing both the checkbox and the label.
Why do we need to expose or have some focus related properties in widget itself? Can't it be made user dependent on whether they want to add the focus behavior or not? By default we can either skip having any accessibility properties or behaviour and can introduce mixins.
Some users physically cannot use pointing devices. They should be able to interact with a Kivy app, even if the app's developer didn't make it very ergonomic. To do that, UI automation needs to be able to focus on some widget in particular. Hence: "focus". It's a good idea to add FocusBehavior to your whole app, too, but a lot of people haven't.
I see a update widget list global is being used. I want to understand first of all why and then is there any way to have alternative implementation with a global, I say this in light of free-threaded python build and to keep in mind globals are bad for concurrency as a start.
UI automation (such as screen readers) needs its own whole widget tree, basically, and it needs to be synchronized with the visual one that Kivy apps already have. It may have some differences with the visual widgets, like the checkbox-and-label mentioned above, and has extra metadata for the semantics of particular widgets--what automated actions might be done to it, how a screen reader should describe it, and when, and so on.
I suppose the other way to keep them synchronized would be for all widget property changes to go into a global queue. I feel like that would have most of the same problems, though.
Consider a checkbox with a label next to it. From the sighted user's perspective, this is two widgets, with two sizes. Perhaps clicking the label checks the checkbox, but it's not surprising if not.
To a screenreader user, these should instead be presented like one widget, equivalent to a toggle button the size and shape of the box containing both the checkbox and the label.
Pardon me if my understanding is not correct, but do we really need to present them as one widget as that would be misinformation. True goal of accessibility is to assist people to get the same information without respect to any disabilities. So translation of the information should be same across all modes of access. Taking example of web aria, labels have a for attribute which is used to denote the input they relate to and are separate entities then the inputs in the accessibility tree or you can nest the input inside the label. Internally, in both these ways the label and input are separate entities even to the accessibility tree and focus on label (text node of label) is passed on to the associated input. I think we can explore this as most tools are conforming aria standards and this makes much more intuitive sense to me. Open for your thoughts.
Some users physically cannot use pointing devices. They should be able to interact with a Kivy app, even if the app's developer didn't make it very ergonomic. To do that, UI automation needs to be able to focus on some widget in particular. Hence: "focus". It's a good idea to add
FocusBehaviorto your whole app, too, but a lot of people haven't.
Hence, I mentioned about mixins, rather than adding individual attributes to widget class, we should add the focus behavior mixin to classes that require it explicitly and selectively, like button, inputs, etc. This will limit exposure of extra attributes to only required places and also provide default ergonomic behaviour as you desire, without making the widget class more bulky. Also, not all widgets require this by default. Following web standards and aria again, by default only a subset of elements have such assisstive capabilities and not all. I think we should do that as a good default and let users use this mixin for enhancing any other widgets which are not in the default set. This way by default the accessibility tree can be as minimal as possible by skipping non accessible widgets too at default mode, its like opting participation rather than forced for all.
UI automation (such as screen readers) needs its own whole widget tree, basically, and it needs to be synchronized with the visual one that Kivy apps already have. It may have some differences with the visual widgets, like the checkbox-and-label mentioned above, and has extra metadata for the semantics of particular widgets--what automated actions might be done to it, how a screen reader should describe it, and when, and so on.
I suppose the other way to keep them synchronized would be for all widget property changes to go into a global queue. I feel like that would have most of the same problems, though.
I may need to understand this a bit more in depth. But as of now, I think this can be easily done taking advantages of the property update event system of Kivy and a singleton for the accessibility tree rather than a global. Like for each property change/event of a widget( pos, size, parent, add_widget, remove_widget, etc), that is interesting to the accessibility tree, you can bind a handler to these on_ events and the handler can be a event handler defined in window singleton, e.g., widget.bind(pos=lambda *args: Window.on_accessible_tree_change(*args, other args as necessary). And you can add special logic for non event/property changes like add or remove widget.
Note this naming above of the function is just for example. As each accessibility tree should be contained by one window only, this can easily be integrated into multi window settings without having concurrency issue, as no other thread of different window would need to modify this tree directly, while access can be through designated read only points.
Also do we need eager updation of accessibility tree or we can have lazy updation when required to minimize updation frequency.
All these you can encapsulate into a mixin class rather than in widget class directly.
Pardon me if my understanding is not correct, but do we really need to present them as one widget as that would be misinformation. True goal of accessibility is to assist people to get the same information without respect to any disabilities. So translation of the information should be same across all modes of access.
This does not follow. A checkbox needs to be turned on and off to set a particular value. The user needs to know the meaning of that value, so they can decide to check it or not. If they can see the label, that's that; but if they can't, they need their screen reader to tell them both what the label says, and that it is a label of that particular checkbox. One way to do that is to just make them one widget for access purposes. It's not necessarily the best way, and AccessKit does provide the option to link the label to the checkbox and let the screen reader decide what to do with that, so it wasn't a very good choice of example on my part.
I think the principle, though, is that AccessKit and other UI automation needs to know the bounding box of each widget for collision purposes, which aren't necessarily the same as the size of the widget's graphics. I'm new at this; @DataTriny is that accurate?
This way by default the accessibility tree can be as minimal as possible by skipping non accessible widgets too at default mode, its like opting participation rather than forced for all.
Why do you consider that minimalism a good thing? I think everyone should be able to use every app. We have to make tradeoffs about that, for practical reasons, but I have the opportunity to make a bunch of existing apps accessible without their authors having to lift a finger, so I'm doing it. I'll follow the relevant standards as they are useful to that end.
Why do you consider that minimalism a good thing? I think everyone should be able to use every app. We have to make tradeoffs about that, for practical reasons, but I have the opportunity to make a bunch of existing apps accessible without their authors having to lift a finger, so I'm doing it. I'll follow the relevant standards as they are useful to that end.
I appreciate your reasoning, but may be I wasn't clear enough with my words. I too align with your view of making it usable for all. What I meant was whichever widget would need to be made accessible for this to be achieved, only make those widgets accessible. I don't think every random widget needs to be accessible, if somebody wishes to do this, they can simply add the AcessibleBehavior mixin and get those properties. While designing and implementing, there's always a lot of widgets which are may be for layouts, presentation, etc., and don't perform user relevant functions, in those cases why to make them accessible. My suggestion is to not introduce these properties, which only some widgets might use, in the Widget class but create a separate mixin class having these properties and add these class as the sub class of widgets which would absolutely require accessibility, like button, inputs, labels, etc.
Kivy is not only used in desktops but in mobile and also in resource constrained environments, both the later have specific concerns about size and memory consumption. If there's an option of making it efficient, then why not do it while achieving the same result.
If you can express relationships such as "labelled by" between a radio button and a label, assistive technologies are expected to combine their bounding boxes if that is necessary. Now if you have a label inside a button and you are sure that the button will never contain other children than this label, you could hide the label from the accessibility tree and use the label property on the button, but that's an optimization which you should probably ignore for a first integration.
I won't give advice on how to better handle custom widgets or focus, but I remember that when I looked at kivy, it was clear to me that most apps seemed to not handle keyboard focus at all. The fact that the concept of focus seemed very flexible is nice for supporting all kinds of devices and experiences, but it also make it harder to implement support for assistive technologies. In the accessibility tree there must only be one focused widget at all time, not zero, not 2 because you have 2 plugged keyboards. Some of this is probably due to the fact that accessibility stacks haven't evolved much in the last 20 years, but we have to deal with it unfortunately.
My suggestion is to not introduce these properties, which only some widgets might use
No, all of the widgets are going to use them. I intend to make every widget in the standard uix module accessible.
I don't find, "some apps might perform worse because of accesskit," to be a terribly compelling argument, with the library as lightweight as it is.
I think @Samael-TLB may be mistaken about what AccessKit does: it provides information to the OS about what the app's displaying, and how the user might interact with it. It doesn't provide voice synthesis, for instance, which could legitimately overburden some embedded applications. The host OS has to take care of that, if the user wants screen reading.
I think @Samael-TLB may be mistaken about what AccessKit does: it provides information to the OS about what the app's displaying, and how the user might interact with it. It doesn't provide voice synthesis, for instance, which could legitimately overburden some embedded applications. The host OS has to take care of that, if the user wants screen reading.
Just to clear, no I didn't mistaken about the library. I was coming from an easy optimization point of view without a down side ( at least in my opinion). Cause these properties are not free to create and have memory burden ( think about hundreds of widgets having them but not using) and from my point of view, I neither see all widgets definitely needing them or using them even from accessibility standpoint, like for example, the camera and video widgets, will they need them, no right, it's the actionable widgets like buttons, input, etc., and detailing widgets like the label, who need them. But if you are determined in your approach, happy to see the final thing and test it out on a positive note. Remaining I really appreciate your work on this @clayote.
Since it was a concern, I've made Kivy tolerate the absence of an accessibility provider. I'd still like to include accesskit in the dependencies, though.
Oh, to be clear, when there's no accessibility provider, Widget will not bind its properties any more than before.
Hi @clayote,
So I wanted to test this but couldn't find out how. I built the entire project and ran the widgets/accessibility example. However I can see "No usable accessibility provider" on the standard output yet I can import accesskit in my environment. Quickly looking through the code, I don't see why the provider wouldn't get registered. Is there something special I should do?
Thanks.
oh. because when I made it "switchable," I then neglected to import it anywhere.
Try it again now, @DataTriny
It works now, thanks!
Action.DEFAULT was renamed to Action.CLICK, I got a panic due to that.
I can't get mouse review to work on Windows either, even though it's much more reliable on this platform. The bounds seem OK to me on Accessibility Insights, I need to find what is going wrong. Are there any interesting example apps to try?
From a checkout of this repo, you can run python examples/demo/kivycatalog/main.py to get an interactive demonstration of all the widgets. At the moment, only buttons and checkboxes are really accessible, though it's possible that some of the stuff I did to the Label class will trickle down to other widgets.