Skip to content

Commit 7560111

Browse files
gh-157676: Fix KeyError in tkinter.simpledialog when a foreign window holds the grab
_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.
1 parent 9ab004d commit 7560111

2 files changed

Lines changed: 30 additions & 12 deletions

File tree

‎Lib/test/test_tkinter/test_simpledialog.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,23 @@ def test_go(self):
245245
d.root.after(1, lambda: d._buttons[0].invoke())
246246
self.assertEqual(d.go(), 0)
247247

248+
def test_go_foreign_grab(self):
249+
# gh-157676: the grab and the focus can be in a window which was not
250+
# created by tkinter, such as a native message box; they must be
251+
# restored after the dialog.
252+
tk = self.root.tk
253+
tk.call('toplevel', '.foreign')
254+
self.addCleanup(tk.call, 'destroy', '.foreign')
255+
tk.call('wm', 'deiconify', '.foreign')
256+
tk.call('update')
257+
tk.call('grab', 'set', '.foreign')
258+
tk.call('focus', '-force', '.foreign')
259+
d = self.create()
260+
d.root.after(1, lambda: d._buttons[0].invoke())
261+
self.assertEqual(d.go(), 0)
262+
self.assertEqual(tk.call('grab', 'current', self.root._w), '.foreign')
263+
self.assertEqual(tk.call('focus'), '.foreign')
264+
248265

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

‎Lib/tkinter/simpledialog.py‎

Lines changed: 13 additions & 12 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,9 @@ def _temp_grab_focus(grab, focus=None, destroy=True):
741742
yield
742743

743744
finally:
744-
if old_focus is not None:
745+
if old_focus and grab.getboolean(tk.call('winfo', 'exists', old_focus)):
745746
try:
746-
old_focus.focus_set()
747+
tk.call('focus', old_focus)
747748
except TclError:
748749
pass
749750
try:
@@ -755,15 +756,15 @@ def _temp_grab_focus(grab, focus=None, destroy=True):
755756
grab.destroy()
756757
except TclError:
757758
pass
758-
if (old_grab is not None and old_grab.winfo_exists()
759-
and old_grab.winfo_ismapped()):
759+
if (old_grab and grab.getboolean(tk.call('winfo', 'exists', old_grab))
760+
and grab.getboolean(tk.call('winfo', 'ismapped', old_grab))):
760761
# The "grab" command will fail if another application
761762
# already holds the grab. So catch it.
762763
try:
763764
if old_status == 'global':
764-
old_grab.grab_set_global()
765+
tk.call('grab', 'set', '-global', old_grab)
765766
else:
766-
old_grab.grab_set()
767+
tk.call('grab', 'set', old_grab)
767768
except TclError:
768769
pass
769770

0 commit comments

Comments
 (0)