Skip to content

Commit 4532b36

Browse files
gh-157672: Keep Python's signal handlers when Tk is initialized on macOS (GH-157673)
Tk on macOS replaces the SIGINT, SIGHUP and SIGTERM handlers with its own, which exits the process, so Ctrl-C terminated a tkinter program instead of raising KeyboardInterrupt. Save the handlers installed by Python before Tk_Init() and restore them after it. This affects Tk 8.6.11 to 8.6.18 and 9.0 to 9.0.4. Tk itself is fixed in 8.6.19 and 9.0.5.
1 parent 55ecc0a commit 4532b36

3 files changed

Lines changed: 39 additions & 1 deletion

File tree

‎Lib/test/test_tkinter/test_misc.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import gc
55
import platform
6+
import signal
67
import sys
78
import textwrap
89
import time
@@ -13,6 +14,7 @@
1314
import enum
1415
from test import support
1516
from test.support import os_helper
17+
from test.support.isolation import runInSubprocess
1618
from test.support.script_helper import assert_python_ok
1719
from test.test_tkinter.support import setUpModule # noqa: F401
1820
from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest,
@@ -2149,6 +2151,18 @@ def _info_commands(widget, pattern=None):
21492151
return widget.tk.splitlist(widget.tk.call('info', 'commands', pattern))
21502152

21512153

2154+
class SignalTest(unittest.TestCase):
2155+
2156+
@runInSubprocess()
2157+
def test_sigint_handler(self):
2158+
# gh-157672: Tk on macOS replaced the SIGINT handler with its own,
2159+
# which exits the process.
2160+
root = tkinter.Tk()
2161+
self.addCleanup(root.destroy)
2162+
with self.assertRaises(KeyboardInterrupt):
2163+
signal.raise_signal(signal.SIGINT)
2164+
2165+
21522166
class TclObjTypeTest(AbstractTkTest, unittest.TestCase):
21532167
# See FromObj() in Modules/_tkinter.c: Tcl object types are converted to
21542168
# appropriate Python types. These conversions only happen in the object
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :mod:`tkinter` on macOS: creating a :class:`~tkinter.Tk` instance no
2+
longer replaces the Python handlers of SIGINT, SIGHUP and SIGTERM with Tk's
3+
handler which exits the process, so Ctrl-C raises :exc:`KeyboardInterrupt`
4+
again.

‎Modules/_tkinter.c‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Copyright (C) 1994 Steen Lumholt.
3333
#include "pycore_long.h" // _PyLong_IsNegative()
3434
#include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8String
3535

36+
#include <signal.h> // SIGINT
37+
3638
#ifdef MS_WINDOWS
3739
# include <windows.h>
3840
#endif
@@ -227,7 +229,25 @@ Tkinter_TkInit(Tcl_Interp *interp)
227229
does not search. Mount the DLL using Zipfs if possible. */
228230
mount_tk_dll_zip();
229231
#endif
230-
return Tk_Init(interp);
232+
#ifdef __APPLE__
233+
/* Tk on macOS replaces the handlers of these signals with its own,
234+
which exits the process. Keep the handlers installed by Python
235+
(gh-157672). */
236+
static const int signals[] = {SIGINT, SIGHUP, SIGTERM};
237+
PyOS_sighandler_t handlers[Py_ARRAY_LENGTH(signals)];
238+
for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) {
239+
handlers[i] = PyOS_getsig(signals[i]);
240+
}
241+
#endif
242+
int result = Tk_Init(interp);
243+
#ifdef __APPLE__
244+
for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) {
245+
if (handlers[i] != SIG_DFL) {
246+
PyOS_setsig(signals[i], handlers[i]);
247+
}
248+
}
249+
#endif
250+
return result;
231251
}
232252

233253
/* The threading situation is complicated. Tcl is not thread-safe, except

0 commit comments

Comments
 (0)