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 24a767c56e8397..8abc4d0af8d19f 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -163,6 +163,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.