Skip to content

Commit 3736342

Browse files
gh-157676: Fix KeyError in tkinter.simpledialog when a foreign window holds the grab (GH-157677)
_temp_grab_focus() used grab_current() and focus_get(), which raise KeyError for windows which were not created by tkinter, such as a native message box. Use Tcl window names instead of widgets. Regression from GH-151848, not present in released versions.
1 parent 4a5a7b9 commit 3736342

2 files changed

Lines changed: 42 additions & 16 deletions

File tree

‎Lib/test/test_tkinter/test_simpledialog.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
import unittest
23
import tkinter
34
from tkinter import messagebox, ttk
@@ -245,6 +246,26 @@ def test_go(self):
245246
d.root.after(1, lambda: d._buttons[0].invoke())
246247
self.assertEqual(d.go(), 0)
247248

249+
def test_go_foreign_grab(self):
250+
# gh-157676: the grab and the focus can be in a window which was not
251+
# created by tkinter, such as a native message box; they must be
252+
# restored after the dialog.
253+
tk = self.root.tk
254+
tk.call('toplevel', '.foreign')
255+
self.addCleanup(tk.call, 'destroy', '.foreign')
256+
tk.call('wm', 'deiconify', '.foreign')
257+
tk.call('update')
258+
tk.call('grab', 'set', '.foreign')
259+
tk.call('focus', '-force', '.foreign')
260+
d = self.create()
261+
d.root.after(1, lambda: d._buttons[0].invoke())
262+
self.assertEqual(d.go(), 0)
263+
self.assertEqual(tk.call('grab', 'current', self.root._w), '.foreign')
264+
# On Windows the application can lose the focus when the dialog is
265+
# destroyed, and then "focus" returns an empty string.
266+
if sys.platform != 'win32':
267+
self.assertEqual(tk.call('focus'), '.foreign')
268+
248269

249270
class DialogTest(AbstractDialogTest, unittest.TestCase):
250271
# Dialog's button box is modelled on tk::MessageBox.

‎Lib/tkinter/simpledialog.py‎

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -722,12 +722,13 @@ def askstring(title, prompt, **kw):
722722

723723
@contextlib.contextmanager
724724
def _temp_grab_focus(grab, focus=None, destroy=True):
725-
old_focus = grab.focus_get()
726-
old_grab = grab.grab_current()
727-
if old_grab is not None and old_grab.winfo_exists():
728-
old_status = old_grab.grab_status()
729-
else:
730-
old_status = None
725+
# Use Tcl window names rather than widgets, because the focus and the
726+
# grab can be in a window which was not created by tkinter, such as
727+
# a native message box.
728+
tk = grab.tk
729+
old_focus = tk.call('focus')
730+
old_grab = tk.call('grab', 'current', grab._w)
731+
old_status = tk.call('grab', 'status', old_grab) if old_grab else None
731732
# The "grab" command will fail if another application
732733
# already holds the grab. So catch it.
733734
try:
@@ -741,9 +742,11 @@ def _temp_grab_focus(grab, focus=None, destroy=True):
741742
yield
742743

743744
finally:
744-
if old_focus is not None:
745+
# The old windows and even the whole application can be destroyed
746+
# by now, so catch errors.
747+
if old_focus:
745748
try:
746-
old_focus.focus_set()
749+
tk.call('focus', old_focus)
747750
except TclError:
748751
pass
749752
try:
@@ -755,15 +758,17 @@ def _temp_grab_focus(grab, focus=None, destroy=True):
755758
grab.destroy()
756759
except TclError:
757760
pass
758-
if (old_grab is not None and old_grab.winfo_exists()
759-
and old_grab.winfo_ismapped()):
760-
# The "grab" command will fail if another application
761-
# already holds the grab. So catch it.
761+
if old_grab:
762+
# Do not restore the grab if the window is no longer mapped:
763+
# a local grab on an unmapped window would block the application.
764+
# The "grab" command will fail if another application already
765+
# holds the grab on a window with the same name. So catch it.
762766
try:
763-
if old_status == 'global':
764-
old_grab.grab_set_global()
765-
else:
766-
old_grab.grab_set()
767+
if grab.getboolean(tk.call('winfo', 'ismapped', old_grab)):
768+
if old_status == 'global':
769+
tk.call('grab', 'set', '-global', old_grab)
770+
else:
771+
tk.call('grab', 'set', old_grab)
767772
except TclError:
768773
pass
769774

0 commit comments

Comments
 (0)