Skip to content

Commit 64f444c

Browse files
gh-145578: Add tkinter support for the Tk 9.1 accessibility API
Add the tk_set_acc_* and tk_get_acc_* families of methods which set and return the accessible attributes of a widget, the tk_add_acc_object(), tk_emit_selection_change() and tk_emit_focus_change() methods which register custom widgets with the platform accessibility API, and the tk_check_screenreader() method which reports whether a screen reader is running. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2092192 commit 64f444c

5 files changed

Lines changed: 205 additions & 0 deletions

File tree

Doc/library/tkinter.rst

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,6 +1894,75 @@ Base and mixin classes
18941894

18951895
.. versionadded:: next
18961896

1897+
The following methods support assistive technologies such as screen
1898+
readers.
1899+
They require Tk 9.1 or newer.
1900+
Tk automatically exposes the standard widgets to the platform
1901+
accessibility API;
1902+
these methods allow overriding the exposed attributes and registering
1903+
custom widgets.
1904+
If no assistive technology is active when the application starts,
1905+
all these calls (except :meth:`!tk_check_screenreader`)
1906+
have no effect and return ``0``.
1907+
1908+
.. method:: tk_check_screenreader()
1909+
1910+
Return whether a screen reader is currently running.
1911+
1912+
.. versionadded:: next
1913+
1914+
.. method:: tk_set_acc_role(value)
1915+
tk_set_acc_name(value)
1916+
tk_set_acc_description(value)
1917+
tk_set_acc_value(value)
1918+
tk_set_acc_state(value)
1919+
tk_set_acc_action(value)
1920+
tk_set_acc_help(value)
1921+
1922+
Set an accessible attribute of the widget.
1923+
The supported roles are ``Button``, ``Canvas``, ``Checkbutton``,
1924+
``Combobox``, ``Entry``, ``Label``, ``Listbox``, ``Menu``,
1925+
``Notebook``, ``Progressbar``, ``Scale``, ``Scrollbar``,
1926+
``Spinbox``, ``Table``, ``Text`` and ``Tree``.
1927+
1928+
.. versionadded:: next
1929+
1930+
.. method:: tk_get_acc_role()
1931+
tk_get_acc_name()
1932+
tk_get_acc_description()
1933+
tk_get_acc_value()
1934+
tk_get_acc_state()
1935+
tk_get_acc_action()
1936+
tk_get_acc_help()
1937+
1938+
Return an accessible attribute of the widget.
1939+
Raise :exc:`~tkinter.TclError` if the attribute has not been set.
1940+
1941+
.. versionadded:: next
1942+
1943+
.. method:: tk_add_acc_object()
1944+
1945+
Register the widget with the platform accessibility API.
1946+
This is only needed for custom widgets;
1947+
the standard widgets are registered automatically.
1948+
1949+
.. versionadded:: next
1950+
1951+
.. method:: tk_emit_selection_change()
1952+
1953+
Notify the platform accessibility API that the selection in the
1954+
widget has changed.
1955+
1956+
.. versionadded:: next
1957+
1958+
.. method:: tk_emit_focus_change()
1959+
1960+
Notify the platform accessibility API that the widget has received
1961+
focus.
1962+
Not available on macOS.
1963+
1964+
.. versionadded:: next
1965+
18971966
The methods with the ``busy_`` prefix manage the busy state of a window,
18981967
which shows a busy cursor and ignores user input.
18991968

Doc/whatsnew/3.16.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,18 @@ tkinter
358358
report the user idle time.
359359
(Contributed by Serhiy Storchaka in :gh:`151881`.)
360360

361+
* Added :mod:`tkinter` support for the Tk 9.1 accessibility API: the
362+
:meth:`~tkinter.Misc.tk_set_acc_role` and
363+
:meth:`~tkinter.Misc.tk_get_acc_role` families of methods which set and
364+
return the accessible attributes of a widget, the
365+
:meth:`~tkinter.Misc.tk_add_acc_object`,
366+
:meth:`~tkinter.Misc.tk_emit_selection_change` and
367+
:meth:`~tkinter.Misc.tk_emit_focus_change` methods which register custom
368+
widgets with the platform accessibility API, and the
369+
:meth:`~tkinter.Misc.tk_check_screenreader` method which reports whether
370+
a screen reader is running.
371+
(Contributed by Serhiy Storchaka in :gh:`145578`.)
372+
361373
* Added new window-management methods :meth:`~tkinter.Misc.winfo_isdark`
362374
(dark mode detection), :meth:`~tkinter.Wm.wm_iconbadge` (application icon
363375
badge) and :meth:`~tkinter.Wm.wm_stackorder` (toplevel stacking order).

Lib/test/test_tkinter/test_misc.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,35 @@ def test_tk_inactive(self):
599599
# Resetting the timer returns None and does not raise.
600600
self.assertIsNone(self.root.tk_inactive(reset=True))
601601

602+
@requires_tk(9, 1)
603+
def test_accessibility(self):
604+
# Without an active screen reader every "tk accessible" call is
605+
# a no-op returning 0, so only test that the calls do not raise
606+
# and check the values when a screen reader is active.
607+
try:
608+
sr = self.root.tk_check_screenreader()
609+
except tkinter.TclError:
610+
# Tk 9.1a0 or Tk compiled without accessibility support
611+
# (e.g. without ATK on X11).
612+
self.skipTest('the "tk accessible" command is not supported')
613+
self.assertIsInstance(sr, bool)
614+
f = tkinter.Frame(self.root)
615+
for attr in ('name', 'description', 'value', 'state',
616+
'action', 'help'):
617+
with self.subTest(attr=attr):
618+
getattr(f, f'tk_set_acc_{attr}')('spam')
619+
result = getattr(f, f'tk_get_acc_{attr}')()
620+
if sr:
621+
self.assertEqual(result, 'spam')
622+
f.tk_set_acc_role('Label')
623+
result = f.tk_get_acc_role()
624+
if sr:
625+
self.assertEqual(result, 'Label')
626+
f.tk_add_acc_object()
627+
f.tk_emit_selection_change()
628+
if self.root._windowingsystem != 'aqua':
629+
f.tk_emit_focus_change()
630+
602631
def test_wait_variable(self):
603632
var = tkinter.StringVar(self.root)
604633
self.assertEqual(self.root.waitvar, self.root.wait_variable)

Lib/tkinter/__init__.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,92 @@ def tk_inactive(self, reset=False, *, displayof=0):
818818
else:
819819
return self.tk.getint(self.tk.call(args))
820820

821+
# Accessibility support for assistive technologies such as screen
822+
# readers. Requires Tk 9.1 or newer. If no assistive technology
823+
# is active when the application starts, all these calls (except
824+
# tk_check_screenreader()) have no effect and return 0.
825+
826+
def tk_check_screenreader(self):
827+
"""Return whether a screen reader is currently running.
828+
829+
Requires Tk 9.1 or newer."""
830+
return self.tk.getboolean(self.tk.call(
831+
'tk', 'accessible', 'check_screenreader'))
832+
833+
def tk_set_acc_role(self, value):
834+
"""Set the accessible role of the widget."""
835+
self.tk.call('tk', 'accessible', 'set_acc_role', self._w, value)
836+
837+
def tk_get_acc_role(self):
838+
"""Return the accessible role of the widget."""
839+
return self.tk.call('tk', 'accessible', 'get_acc_role', self._w)
840+
841+
def tk_set_acc_name(self, value):
842+
"""Set the accessible name of the widget."""
843+
self.tk.call('tk', 'accessible', 'set_acc_name', self._w, value)
844+
845+
def tk_get_acc_name(self):
846+
"""Return the accessible name of the widget."""
847+
return self.tk.call('tk', 'accessible', 'get_acc_name', self._w)
848+
849+
def tk_set_acc_description(self, value):
850+
"""Set the accessible description of the widget."""
851+
self.tk.call('tk', 'accessible', 'set_acc_description',
852+
self._w, value)
853+
854+
def tk_get_acc_description(self):
855+
"""Return the accessible description of the widget."""
856+
return self.tk.call('tk', 'accessible', 'get_acc_description',
857+
self._w)
858+
859+
def tk_set_acc_value(self, value):
860+
"""Set the accessible value of the widget."""
861+
self.tk.call('tk', 'accessible', 'set_acc_value', self._w, value)
862+
863+
def tk_get_acc_value(self):
864+
"""Return the accessible value of the widget."""
865+
return self.tk.call('tk', 'accessible', 'get_acc_value', self._w)
866+
867+
def tk_set_acc_state(self, value):
868+
"""Set the accessible state of the widget."""
869+
self.tk.call('tk', 'accessible', 'set_acc_state', self._w, value)
870+
871+
def tk_get_acc_state(self):
872+
"""Return the accessible state of the widget."""
873+
return self.tk.call('tk', 'accessible', 'get_acc_state', self._w)
874+
875+
def tk_set_acc_action(self, value):
876+
"""Set the accessible action of the widget."""
877+
self.tk.call('tk', 'accessible', 'set_acc_action', self._w, value)
878+
879+
def tk_get_acc_action(self):
880+
"""Return the accessible action of the widget."""
881+
return self.tk.call('tk', 'accessible', 'get_acc_action', self._w)
882+
883+
def tk_set_acc_help(self, value):
884+
"""Set the accessible help text of the widget."""
885+
self.tk.call('tk', 'accessible', 'set_acc_help', self._w, value)
886+
887+
def tk_get_acc_help(self):
888+
"""Return the accessible help text of the widget."""
889+
return self.tk.call('tk', 'accessible', 'get_acc_help', self._w)
890+
891+
def tk_add_acc_object(self):
892+
"""Register the widget with the platform accessibility API."""
893+
self.tk.call('tk', 'accessible', 'add_acc_object', self._w)
894+
895+
def tk_emit_selection_change(self):
896+
"""Notify the platform accessibility API that the selection
897+
in the widget has changed."""
898+
self.tk.call('tk', 'accessible', 'emit_selection_change', self._w)
899+
900+
def tk_emit_focus_change(self):
901+
"""Notify the platform accessibility API that the widget has
902+
received focus.
903+
904+
Not available on macOS."""
905+
self.tk.call('tk', 'accessible', 'emit_focus_change', self._w)
906+
821907
def wait_variable(self, name):
822908
"""Wait until the variable is modified.
823909
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Added :mod:`tkinter` support for the Tk 9.1 accessibility API: the
2+
``tk_set_acc_*`` and ``tk_get_acc_*`` families of methods which set and
3+
return the accessible attributes of a widget, the
4+
:meth:`~tkinter.Misc.tk_add_acc_object`,
5+
:meth:`~tkinter.Misc.tk_emit_selection_change` and
6+
:meth:`~tkinter.Misc.tk_emit_focus_change` methods which register custom
7+
widgets with the platform accessibility API, and the
8+
:meth:`~tkinter.Misc.tk_check_screenreader` method which reports whether a
9+
screen reader is running.

0 commit comments

Comments
 (0)