From c9d71ca554352c0b5bba8e6f8785e3f4b503bf50 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 22 Jun 2026 00:48:44 +0300 Subject: [PATCH] gh-151874: Add tkinter wm_stackorder, wm_iconbadge and winfo_isdark Wrap the Tk commands "wm stackorder" (toplevel stacking order, since Tk 8.4), "wm iconbadge" (application icon badge, new in Tk 9.0) and "winfo isdark" (dark mode detection, new in Tk 9.0) as the methods Wm.wm_stackorder(), Wm.wm_iconbadge() and Misc.winfo_isdark(), with the usual stackorder and iconbadge short aliases. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XWevzas4XVpjzedzR9gKVo --- Doc/library/tkinter.rst | 44 +++++++++++++++++++ Doc/whatsnew/3.16.rst | 5 +++ Lib/test/test_tkinter/test_misc.py | 37 ++++++++++++++++ Lib/tkinter/__init__.py | 44 +++++++++++++++++++ ...-06-22-00-46-57.gh-issue-151874.8VtTrq.rst | 3 ++ 5 files changed, 133 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-22-00-46-57.gh-issue-151874.8VtTrq.rst diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index 222a4d0128bd8d..fcace71329b345 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -2134,6 +2134,16 @@ Base and mixin classes window; otherwise it refers to the display of the application's main window. + .. method:: winfo_isdark() + + On macOS and Windows, return ``True`` if the widget is in "dark mode", + and ``False`` otherwise. + Always return ``False`` on X11. + + .. versionadded:: next + + Requires Tk 9.1 or newer. + .. method:: winfo_ismapped() Return ``1`` if the widget is currently mapped, ``0`` otherwise. @@ -2605,6 +2615,24 @@ Base and mixin classes empty string. :meth:`wm_group` is an alias of :meth:`!group`. + .. method:: wm_iconbadge(badge) + :no-typesetting: + + .. method:: iconbadge(badge) + + Set a badge for the window's icon, intended for display in the Dock + (macOS), taskbar (Windows) or app panel (X11). + *badge* may be a positive integer (for example a count of unread + messages) or an exclamation point to denote that attention is needed; + an empty string removes the badge. + On X11 the variable ``::tk::icons::base_icon(window)`` must be set to the + window's icon image for the badge to appear. + :meth:`wm_iconbadge` is an alias of :meth:`!iconbadge`. + + .. versionadded:: next + + Requires Tk 9.0 or newer. + .. method:: wm_iconbitmap(bitmap=None, default=None) :no-typesetting: @@ -2814,6 +2842,22 @@ Base and mixin classes has been set. :meth:`wm_sizefrom` is an alias of :meth:`!sizefrom`. + .. method:: wm_stackorder(relation=None, window=None) + :no-typesetting: + + .. method:: stackorder(relation=None, window=None) + + Query the stacking order of top-level windows. + With no arguments, return a list of the mapped top-level widgets in + stacking order, from lowest to highest, recursively including this + window's top-level children. + If *relation* is ``'isabove'`` or ``'isbelow'`` and *window* is another + top-level, return ``True`` if this window is respectively above or below + *window* in the stacking order, and ``False`` otherwise. + :meth:`wm_stackorder` is an alias of :meth:`!stackorder`. + + .. versionadded:: next + .. method:: wm_state(newstate=None) :no-typesetting: diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index ec8e367d938ddb..0152265eafd0d8 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -157,6 +157,11 @@ tkinter synchronization of the displayed view with the underlying text. (Contributed by Serhiy Storchaka in :gh:`151675`.) +* 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). + (Contributed by Serhiy Storchaka in :gh:`151874`.) + xml --- diff --git a/Lib/test/test_tkinter/test_misc.py b/Lib/test/test_tkinter/test_misc.py index b4cb5aaae1b1e5..77bf84304e78ef 100644 --- a/Lib/test/test_tkinter/test_misc.py +++ b/Lib/test/test_tkinter/test_misc.py @@ -751,6 +751,12 @@ def test_winfo_viewable(self): self.root.update() self.assertTrue(f.winfo_viewable()) + @requires_tk(9, 1) + def test_winfo_isdark(self): + self.assertIsInstance(self.root.winfo_isdark(), bool) + if self.root._windowingsystem == 'x11': + self.assertFalse(self.root.winfo_isdark()) + def test_winfo_atom(self): atom = self.root.winfo_atom('PRIMARY') self.assertIsInstance(atom, int) @@ -1057,6 +1063,37 @@ def test_wm_transient(self): t.transient(self.root) self.assertEqual(str(t.transient()), str(self.root)) + def test_wm_stackorder(self): + t1 = tkinter.Toplevel(self.root) + t2 = tkinter.Toplevel(self.root) + t1.deiconify() + t2.deiconify() + self.root.update() + t1.lift(t2) # Raise t1 above t2. + self.root.update() + order = self.root.wm_stackorder() + self.assertIsInstance(order, list) + self.assertTrue(all(isinstance(w, tkinter.Misc) for w in order)) + names = [str(w) for w in order] + self.assertIn(str(t1), names) + self.assertIn(str(t2), names) + # The list is ordered from lowest to highest, consistently with the + # isabove/isbelow queries. + self.assertGreater(names.index(str(t1)), names.index(str(t2))) + self.assertIs(t1.wm_stackorder('isabove', t2), True) + self.assertIs(t1.wm_stackorder('isbelow', t2), False) + self.assertIs(t2.wm_stackorder('isbelow', t1), True) + + @requires_tk(9, 0) + def test_wm_iconbadge(self): + if self.root._windowingsystem == 'x11': + # On X11 the badge requires ::tk::icons::base_icon to be set. + self.skipTest('iconbadge needs a base icon on X11') + # The badge is not queryable, so just check the call does not fail. + self.root.wm_iconbadge('3') + self.root.wm_iconbadge('!') + self.root.wm_iconbadge('') + class EventTest(AbstractTkTest, unittest.TestCase): diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 8bdf7cc1e2d96b..24d229ed74d948 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -1275,6 +1275,15 @@ def winfo_interps(self, displayof=0): args = ('winfo', 'interps') + self._displayof(displayof) return self.tk.splitlist(self.tk.call(args)) + def winfo_isdark(self): # new in Tk 9.1 + """Return whether this widget is in "dark mode". + + On macOS and Windows return True if the widget is in "dark mode", + and False otherwise. Always return False on X11. + """ + return self.tk.getboolean( + self.tk.call('winfo', 'isdark', self._w)) + def winfo_ismapped(self): """Return true if this widget is mapped.""" return self.tk.getint( @@ -2330,6 +2339,22 @@ def wm_group(self, pathName=None): group = wm_group + def wm_iconbadge(self, badge): # new in Tk 9.0 + """Set a badge for the icon of this widget. + + BADGE can be a positive integer number, for instance the number of + new or unread messages, or an exclamation point denoting attention + needed. If BADGE is an empty string, the badge image is removed from + the application icon. + + The badge is intended for display in the Dock (macOS), taskbar + (Windows) or app panel (X11). On X11, the variable + ::tk::icons::base_icon(WINDOW) must be set to the image used for the + window icon for this command to work.""" + return self.tk.call('wm', 'iconbadge', self._w, badge) + + iconbadge = wm_iconbadge + def wm_iconbitmap(self, bitmap=None, default=None): """Set bitmap for the iconified widget to BITMAP. Return the bitmap if None is given. @@ -2479,6 +2504,25 @@ def wm_sizefrom(self, who=None): sizefrom = wm_sizefrom + def wm_stackorder(self, relation=None, window=None): + """Query the stacking order of toplevel windows. + + If called with no arguments, return a list of toplevel widgets in + stacking order, from lowest to highest. The list recursively + includes all of this window's children that are toplevels; only + toplevels currently mapped to the screen are included. + + If RELATION is "isabove" or "isbelow" and WINDOW is another toplevel, + return True if this window is respectively above or below WINDOW in + the stacking order, and False otherwise.""" + if relation is None: + return [self._nametowidget(x) for x in self.tk.splitlist( + self.tk.call('wm', 'stackorder', self._w))] + return self.tk.getboolean( + self.tk.call('wm', 'stackorder', self._w, relation, window)) + + stackorder = wm_stackorder + def wm_state(self, newstate=None): """Query or set the state of this widget as one of normal, icon, iconic (see wm_iconwindow), withdrawn, or zoomed (Windows only).""" diff --git a/Misc/NEWS.d/next/Library/2026-06-22-00-46-57.gh-issue-151874.8VtTrq.rst b/Misc/NEWS.d/next/Library/2026-06-22-00-46-57.gh-issue-151874.8VtTrq.rst new file mode 100644 index 00000000000000..50a33f5be80987 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-22-00-46-57.gh-issue-151874.8VtTrq.rst @@ -0,0 +1,3 @@ +Add the :mod:`tkinter` methods :meth:`!Misc.winfo_isdark`, +:meth:`!Wm.wm_iconbadge` and :meth:`!Wm.wm_stackorder`, wrapping the +``winfo isdark``, ``wm iconbadge`` and ``wm stackorder`` Tk commands.