diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index ca034fcdc09b43..6cea5705f3b691 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -1894,6 +1894,75 @@ Base and mixin classes .. versionadded:: next + The following methods 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; + these methods allow overriding the exposed attributes and registering + custom widgets. + If no assistive technology is active when the application starts, + all these calls (except :meth:`!tk_check_screenreader`) + have no effect and return ``0``. + + .. method:: tk_check_screenreader() + + Return whether a screen reader is currently running. + + .. versionadded:: next + + .. method:: tk_set_acc_role(value) + tk_set_acc_name(value) + tk_set_acc_description(value) + tk_set_acc_value(value) + tk_set_acc_state(value) + tk_set_acc_action(value) + tk_set_acc_help(value) + + Set an accessible attribute of the widget. + The supported roles are ``Button``, ``Canvas``, ``Checkbutton``, + ``Combobox``, ``Entry``, ``Label``, ``Listbox``, ``Menu``, + ``Notebook``, ``Progressbar``, ``Scale``, ``Scrollbar``, + ``Spinbox``, ``Table``, ``Text`` and ``Tree``. + + .. versionadded:: next + + .. method:: tk_get_acc_role() + tk_get_acc_name() + tk_get_acc_description() + tk_get_acc_value() + tk_get_acc_state() + tk_get_acc_action() + tk_get_acc_help() + + Return an accessible attribute of the widget. + Raise :exc:`~tkinter.TclError` if the attribute has not been set. + + .. versionadded:: next + + .. method:: tk_add_acc_object() + + Register the widget with the platform accessibility API. + This is only needed for custom widgets; + the standard widgets are registered automatically. + + .. versionadded:: next + + .. method:: tk_emit_selection_change() + + Notify the platform accessibility API that the selection in the + widget has changed. + + .. versionadded:: next + + .. method:: tk_emit_focus_change() + + Notify the platform accessibility API that the widget has received + focus. + Not available on macOS. + + .. versionadded:: next + The methods with the ``busy_`` prefix manage the busy state of a window, which shows a busy cursor and ignores user input. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index e8c75d2f571061..dd7af01958cbcc 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -358,6 +358,18 @@ 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 + :meth:`~tkinter.Misc.tk_set_acc_role` and + :meth:`~tkinter.Misc.tk_get_acc_role` families of methods which set and + return the accessible attributes of a widget, the + :meth:`~tkinter.Misc.tk_add_acc_object`, + :meth:`~tkinter.Misc.tk_emit_selection_change` and + :meth:`~tkinter.Misc.tk_emit_focus_change` methods which register custom + widgets with the platform accessibility API, and the + :meth:`~tkinter.Misc.tk_check_screenreader` method which 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). diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index 5f75b8d245b3e9..0898cba72b50a8 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -599,6 +599,35 @@ 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_accessibility(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) + for attr in ('name', 'description', 'value', 'state', + 'action', 'help'): + with self.subTest(attr=attr): + getattr(f, f'tk_set_acc_{attr}')('spam') + result = getattr(f, f'tk_get_acc_{attr}')() + if sr: + self.assertEqual(result, 'spam') + f.tk_set_acc_role('Label') + result = f.tk_get_acc_role() + if sr: + self.assertEqual(result, 'Label') + f.tk_add_acc_object() + f.tk_emit_selection_change() + if self.root._windowingsystem != 'aqua': + f.tk_emit_focus_change() + def test_wait_variable(self): var = tkinter.StringVar(self.root) self.assertEqual(self.root.waitvar, self.root.wait_variable) diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 83d11a7695ffbe..2e97d667a54ed4 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -818,6 +818,92 @@ def tk_inactive(self, reset=False, *, displayof=0): else: return self.tk.getint(self.tk.call(args)) + # Accessibility support for assistive technologies such as screen + # readers. Requires Tk 9.1 or newer. If no assistive technology + # is active when the application starts, all these calls (except + # tk_check_screenreader()) have no effect and return 0. + + 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')) + + def tk_set_acc_role(self, value): + """Set the accessible role of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_role', self._w, value) + + def tk_get_acc_role(self): + """Return the accessible role of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_role', self._w) + + def tk_set_acc_name(self, value): + """Set the accessible name of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_name', self._w, value) + + def tk_get_acc_name(self): + """Return the accessible name of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_name', self._w) + + def tk_set_acc_description(self, value): + """Set the accessible description of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_description', + self._w, value) + + def tk_get_acc_description(self): + """Return the accessible description of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_description', + self._w) + + def tk_set_acc_value(self, value): + """Set the accessible value of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_value', self._w, value) + + def tk_get_acc_value(self): + """Return the accessible value of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_value', self._w) + + def tk_set_acc_state(self, value): + """Set the accessible state of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_state', self._w, value) + + def tk_get_acc_state(self): + """Return the accessible state of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_state', self._w) + + def tk_set_acc_action(self, value): + """Set the accessible action of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_action', self._w, value) + + def tk_get_acc_action(self): + """Return the accessible action of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_action', self._w) + + def tk_set_acc_help(self, value): + """Set the accessible help text of the widget.""" + self.tk.call('tk', 'accessible', 'set_acc_help', self._w, value) + + def tk_get_acc_help(self): + """Return the accessible help text of the widget.""" + return self.tk.call('tk', 'accessible', 'get_acc_help', self._w) + + def tk_add_acc_object(self): + """Register the widget with the platform accessibility API.""" + self.tk.call('tk', 'accessible', 'add_acc_object', self._w) + + def tk_emit_selection_change(self): + """Notify the platform accessibility API that the selection + in the widget has changed.""" + self.tk.call('tk', 'accessible', 'emit_selection_change', self._w) + + def tk_emit_focus_change(self): + """Notify the platform accessibility API that the widget has + received focus. + + Not available on macOS.""" + self.tk.call('tk', 'accessible', 'emit_focus_change', self._w) + def wait_variable(self, name): """Wait until the variable is modified. diff --git a/Misc/NEWS.d/next/Library/2026-07-06-14-38-02.gh-issue-145578.aX11yQ.rst b/Misc/NEWS.d/next/Library/2026-07-06-14-38-02.gh-issue-145578.aX11yQ.rst new file mode 100644 index 00000000000000..8b2eda1d71eb31 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-06-14-38-02.gh-issue-145578.aX11yQ.rst @@ -0,0 +1,9 @@ +Added :mod:`tkinter` support for the Tk 9.1 accessibility API: the +``tk_set_acc_*`` and ``tk_get_acc_*`` families of methods which set and +return the accessible attributes of a widget, the +:meth:`~tkinter.Misc.tk_add_acc_object`, +:meth:`~tkinter.Misc.tk_emit_selection_change` and +:meth:`~tkinter.Misc.tk_emit_focus_change` methods which register custom +widgets with the platform accessibility API, and the +:meth:`~tkinter.Misc.tk_check_screenreader` method which reports whether a +screen reader is running.