Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Doc/library/tkinter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1894,6 +1894,31 @@ Base and mixin classes

.. versionadded:: next

The following members support assistive technologies such as screen
readers.
They require Tk 9.1 or newer.
Tk automatically exposes the standard widgets to the platform
accessibility API;
the :attr:`!accessible` attributes allow overriding what is exposed,
and the :class:`Accessible` methods allow registering custom widgets.
If no assistive technology is active when the application starts,
setting the attributes has no effect,
reading them returns ``0``,
and the methods do nothing (except :meth:`!tk_check_screenreader`).

.. method:: tk_check_screenreader()

Return whether a screen reader is currently running.

.. versionadded:: next

.. attribute:: accessible

The accessible attributes of the widget,
as an :class:`Accessible` object.

.. versionadded:: next

The methods with the ``busy_`` prefix manage the busy state of a window,
which shows a busy cursor and ignores user input.

Expand Down Expand Up @@ -2323,6 +2348,51 @@ Base and mixin classes
.. versionadded:: 3.11


.. class:: Accessible()

The accessible attributes of a widget,
returned by the :attr:`~Misc.accessible` property of the widget.
The attributes are exposed to assistive technologies
such as screen readers.
Requires Tk 9.1 or newer.

Setting an attribute overrides what Tk exposes for the widget;
reading an attribute that has not been set raises
:exc:`~tkinter.TclError`.
The supported roles are ``Button``, ``Canvas``, ``Checkbutton``,
``Combobox``, ``Entry``, ``Label``, ``Listbox``, ``Menu``,
``Notebook``, ``Progressbar``, ``Scale``, ``Scrollbar``,
``Spinbox``, ``Table``, ``Text`` and ``Tree``.

.. versionadded:: next

.. attribute:: role
name
description
value
state
action
help

The accessible attributes of the widget.

.. method:: add_object()

Register the widget with the platform accessibility API.
This is only needed for custom widgets;
the standard widgets are registered automatically.

.. method:: emit_selection_change()

Notify the platform accessibility API that the selection in the
widget has changed.

.. method:: emit_focus_change()

Notify the platform accessibility API that the widget has received
focus.
Not available on macOS.


.. class:: Wm()

Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,15 @@ tkinter
report the user idle time.
(Contributed by Serhiy Storchaka in :gh:`151881`.)

* Added :mod:`tkinter` support for the Tk 9.1 accessibility API: the
:attr:`~tkinter.Misc.accessible` property of widgets returns an
:class:`~tkinter.Accessible` object whose attributes are exposed to
assistive technologies such as screen readers and whose methods register
custom widgets with the platform accessibility API, and the
:meth:`~tkinter.Misc.tk_check_screenreader` method reports whether a
screen reader is running.
(Contributed by Serhiy Storchaka in :gh:`145578`.)

* Added new window-management methods :meth:`~tkinter.Misc.winfo_isdark`
(dark mode detection), :meth:`~tkinter.Wm.wm_iconbadge` (application icon
badge) and :meth:`~tkinter.Wm.wm_stackorder` (toplevel stacking order).
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,42 @@ def test_tk_inactive(self):
# Resetting the timer returns None and does not raise.
self.assertIsNone(self.root.tk_inactive(reset=True))

@requires_tk(9, 1)
def test_accessible(self):
# Without an active screen reader every "tk accessible" call is
# a no-op returning 0, so only test that the calls do not raise
# and check the values when a screen reader is active.
try:
sr = self.root.tk_check_screenreader()
except tkinter.TclError:
# Tk 9.1a0 or Tk compiled without accessibility support
# (e.g. without ATK on X11).
self.skipTest('the "tk accessible" command is not supported')
self.assertIsInstance(sr, bool)
f = tkinter.Frame(self.root)
acc = f.accessible
self.assertIsInstance(acc, tkinter.Accessible)
self.assertIs(f.accessible, acc)
if sr:
# Reading an attribute that has not been set raises TclError.
with self.assertRaises(tkinter.TclError):
acc.help
for attr in ('name', 'description', 'value', 'state',
'action', 'help'):
with self.subTest(attr=attr):
setattr(acc, attr, 'spam')
result = getattr(acc, attr)
if sr:
self.assertEqual(result, 'spam')
acc.role = 'Label'
result = acc.role
if sr:
self.assertEqual(result, 'Label')
acc.add_object()
acc.emit_selection_change()
if self.root._windowingsystem != 'aqua':
acc.emit_focus_change()

def test_wait_variable(self):
var = tkinter.StringVar(self.root)
self.assertEqual(self.root.waitvar, self.root.wait_variable)
Expand Down
80 changes: 80 additions & 0 deletions Lib/tkinter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,25 @@ def tk_inactive(self, reset=False, *, displayof=0):
else:
return self.tk.getint(self.tk.call(args))

def tk_check_screenreader(self):
"""Return whether a screen reader is currently running.

Requires Tk 9.1 or newer."""
return self.tk.getboolean(self.tk.call(
'tk', 'accessible', 'check_screenreader'))

@property
def accessible(self):
"""The accessible attributes of the widget, as an Accessible
object.

Requires Tk 9.1 or newer."""
try:
return self._accessible_object
except AttributeError:
self._accessible_object = Accessible(self)
return self._accessible_object

def wait_variable(self, name):
"""Wait until the variable is modified.

Expand Down Expand Up @@ -2307,6 +2326,67 @@ def yview_scroll(self, number, what):
self.tk.call(self._w, 'yview', 'scroll', number, what)


def _accessible_attribute(name):
"""Internal function. Create an Accessible attribute property."""
def getter(self):
widget = self._widget
return widget.tk.call('tk', 'accessible', 'get_acc_' + name,
widget._w)
def setter(self, value):
widget = self._widget
widget.tk.call('tk', 'accessible', 'set_acc_' + name,
widget._w, value)
return property(getter, setter,
doc=f'The accessible {name} of the widget.')


class Accessible:
"""The accessible attributes of a widget.

Returned by the accessible property of a widget. The attributes
are exposed to assistive technologies such as screen readers.
Requires Tk 9.1 or newer.

If no assistive technology is active when the application starts,
setting an attribute has no effect and reading it returns 0.
Reading an attribute that has not been set raises TclError.
"""

def __init__(self, widget):
self._widget = widget

def __repr__(self):
return f'<Accessible object of {self._widget!r}>'

role = _accessible_attribute('role')
name = _accessible_attribute('name')
description = _accessible_attribute('description')
value = _accessible_attribute('value')
state = _accessible_attribute('state')
action = _accessible_attribute('action')
help = _accessible_attribute('help')

def add_object(self):
"""Register the widget with the platform accessibility API."""
widget = self._widget
widget.tk.call('tk', 'accessible', 'add_acc_object', widget._w)

def emit_selection_change(self):
"""Notify the platform accessibility API that the selection
in the widget has changed."""
widget = self._widget
widget.tk.call('tk', 'accessible', 'emit_selection_change',
widget._w)

def emit_focus_change(self):
"""Notify the platform accessibility API that the widget has
received focus.

Not available on macOS."""
widget = self._widget
widget.tk.call('tk', 'accessible', 'emit_focus_change', widget._w)


class Wm:
"""Provides functions for the communication with the window manager."""

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added :mod:`tkinter` support for the Tk 9.1 accessibility API: the
:attr:`~tkinter.Misc.accessible` property of widgets returns an
:class:`~tkinter.Accessible` object whose attributes are exposed to
assistive technologies such as screen readers and whose methods register
custom widgets with the platform accessibility API, and the
:meth:`~tkinter.Misc.tk_check_screenreader` method reports whether a
screen reader is running.
Loading