Skip to content

Commit 159a360

Browse files
[3.13] gh-157672: Keep Python's signal handlers when Tk is initialized on macOS (GH-157673) (GH-157989)
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. (cherry picked from commit 4532b36) On 3.13 there was no Tkinter_TkInit(), and tkappinit.c, which is used when Python is built with WITH_APPINIT (as on macOS), called Tk_Init() directly, bypassing the fix.
1 parent f941512 commit 159a360

5 files changed

Lines changed: 49 additions & 3 deletions

File tree

‎Lib/test/test_tkinter/test_misc.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import functools
22
import platform
3+
import signal
34
import sys
45
import textwrap
56
import unittest
@@ -9,6 +10,7 @@
910
import enum
1011
from test import support
1112
from test.support import os_helper
13+
from test.support.isolation import runInSubprocess
1214
from test.support.script_helper import assert_python_ok
1315
from test.test_tkinter.support import setUpModule # noqa: F401
1416
from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest,
@@ -1901,5 +1903,17 @@ def _info_commands(widget, pattern=None):
19011903
return widget.tk.splitlist(widget.tk.call('info', 'commands', pattern))
19021904

19031905

1906+
class SignalTest(unittest.TestCase):
1907+
1908+
@runInSubprocess()
1909+
def test_sigint_handler(self):
1910+
# gh-157672: Tk on macOS replaced the SIGINT handler with its own,
1911+
# which exits the process.
1912+
root = tkinter.Tk()
1913+
self.addCleanup(root.destroy)
1914+
with self.assertRaises(KeyboardInterrupt):
1915+
signal.raise_signal(signal.SIGINT)
1916+
1917+
19041918
if __name__ == "__main__":
19051919
unittest.main()
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: 28 additions & 2 deletions
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_sysmodule.h" // _PySys_GetOptionalAttrString()
3535

36+
#include <signal.h> // SIGINT
37+
3638
#ifdef MS_WINDOWS
3739
# include <windows.h>
3840
#endif
@@ -538,6 +540,30 @@ class _tkinter.tktimertoken "TkttObject *" "&Tktt_Type_spec"
538540
int Tcl_AppInit(Tcl_Interp *);
539541
#endif
540542

543+
int
544+
Tkinter_TkInit(Tcl_Interp *interp)
545+
{
546+
#ifdef __APPLE__
547+
/* Tk on macOS replaces the handlers of these signals with its own,
548+
which exits the process. Keep the handlers installed by Python
549+
(gh-157672). */
550+
static const int signals[] = {SIGINT, SIGHUP, SIGTERM};
551+
PyOS_sighandler_t handlers[Py_ARRAY_LENGTH(signals)];
552+
for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) {
553+
handlers[i] = PyOS_getsig(signals[i]);
554+
}
555+
#endif
556+
int result = Tk_Init(interp);
557+
#ifdef __APPLE__
558+
for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) {
559+
if (handlers[i] != SIG_DFL) {
560+
PyOS_setsig(signals[i], handlers[i]);
561+
}
562+
}
563+
#endif
564+
return result;
565+
}
566+
541567
#ifndef WITH_APPINIT
542568
int
543569
Tcl_AppInit(Tcl_Interp *interp)
@@ -556,7 +582,7 @@ Tcl_AppInit(Tcl_Interp *interp)
556582
return TCL_OK;
557583
}
558584

559-
if (Tk_Init(interp) == TCL_ERROR) {
585+
if (Tkinter_TkInit(interp) == TCL_ERROR) {
560586
PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp));
561587
return TCL_ERROR;
562588
}
@@ -2980,7 +3006,7 @@ _tkinter_tkapp_loadtk_impl(TkappObject *self)
29803006
return NULL;
29813007
}
29823008
if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0) {
2983-
if (Tk_Init(interp) == TCL_ERROR) {
3009+
if (Tkinter_TkInit(interp) == TCL_ERROR) {
29843010
Tkinter_Error(self);
29853011
return NULL;
29863012
}

‎Modules/tkappinit.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Tcl_AppInit(Tcl_Interp *interp)
3737
return TCL_OK;
3838
}
3939

40-
if (Tk_Init(interp) == TCL_ERROR) {
40+
if (Tkinter_TkInit(interp) == TCL_ERROR) {
4141
return TCL_ERROR;
4242
}
4343

‎Modules/tkinter.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@
1616
(TK_RELEASE_LEVEL << 8) | \
1717
(TK_RELEASE_SERIAL << 0))
1818

19+
int Tkinter_TkInit(Tcl_Interp *interp);
20+
1921
#endif /* !TKINTER_H */

0 commit comments

Comments
 (0)