https://github.com/python/cpython/commit/159a3604b9515d3fc63382957acb64dc3f354c43
commit: 159a3604b9515d3fc63382957acb64dc3f354c43
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-23T11:40:46Z
summary:
[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 4532b36db5b9a43f63bf295aaf51df426e4c488d)
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.
files:
A Misc/NEWS.d/next/Library/2026-09-17-14-00-00.gh-issue-157672.tksigs.rst
M Lib/test/test_tkinter/test_misc.py
M Modules/_tkinter.c
M Modules/tkappinit.c
M Modules/tkinter.h
diff --git a/Lib/test/test_tkinter/test_misc.py
b/Lib/test/test_tkinter/test_misc.py
index 29a72692fabe398..61b5b36c98a39b8 100644
--- a/Lib/test/test_tkinter/test_misc.py
+++ b/Lib/test/test_tkinter/test_misc.py
@@ -1,5 +1,6 @@
import functools
import platform
+import signal
import sys
import textwrap
import unittest
@@ -9,6 +10,7 @@
import enum
from test import support
from test.support import os_helper
+from test.support.isolation import runInSubprocess
from test.support.script_helper import assert_python_ok
from test.test_tkinter.support import setUpModule # noqa: F401
from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest,
@@ -1901,5 +1903,17 @@ def _info_commands(widget, pattern=None):
return widget.tk.splitlist(widget.tk.call('info', 'commands', pattern))
+class SignalTest(unittest.TestCase):
+
+ @runInSubprocess()
+ def test_sigint_handler(self):
+ # gh-157672: Tk on macOS replaced the SIGINT handler with its own,
+ # which exits the process.
+ root = tkinter.Tk()
+ self.addCleanup(root.destroy)
+ with self.assertRaises(KeyboardInterrupt):
+ signal.raise_signal(signal.SIGINT)
+
+
if __name__ == "__main__":
unittest.main()
diff --git
a/Misc/NEWS.d/next/Library/2026-09-17-14-00-00.gh-issue-157672.tksigs.rst
b/Misc/NEWS.d/next/Library/2026-09-17-14-00-00.gh-issue-157672.tksigs.rst
new file mode 100644
index 000000000000000..a751a2f9dc406b1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-17-14-00-00.gh-issue-157672.tksigs.rst
@@ -0,0 +1,4 @@
+Fix :mod:`tkinter` on macOS: creating a :class:`~tkinter.Tk` instance no
+longer replaces the Python handlers of SIGINT, SIGHUP and SIGTERM with Tk's
+handler which exits the process, so Ctrl-C raises :exc:`KeyboardInterrupt`
+again.
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 4e8d75e8e1ee367..d78de03515a346f 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -33,6 +33,8 @@ Copyright (C) 1994 Steen Lumholt.
#include "pycore_long.h" // _PyLong_IsNegative()
#include "pycore_sysmodule.h" // _PySys_GetOptionalAttrString()
+#include <signal.h> // SIGINT
+
#ifdef MS_WINDOWS
# include <windows.h>
#endif
@@ -538,6 +540,30 @@ class _tkinter.tktimertoken "TkttObject *"
"&Tktt_Type_spec"
int Tcl_AppInit(Tcl_Interp *);
#endif
+int
+Tkinter_TkInit(Tcl_Interp *interp)
+{
+#ifdef __APPLE__
+ /* Tk on macOS replaces the handlers of these signals with its own,
+ which exits the process. Keep the handlers installed by Python
+ (gh-157672). */
+ static const int signals[] = {SIGINT, SIGHUP, SIGTERM};
+ PyOS_sighandler_t handlers[Py_ARRAY_LENGTH(signals)];
+ for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) {
+ handlers[i] = PyOS_getsig(signals[i]);
+ }
+#endif
+ int result = Tk_Init(interp);
+#ifdef __APPLE__
+ for (size_t i = 0; i < Py_ARRAY_LENGTH(signals); i++) {
+ if (handlers[i] != SIG_DFL) {
+ PyOS_setsig(signals[i], handlers[i]);
+ }
+ }
+#endif
+ return result;
+}
+
#ifndef WITH_APPINIT
int
Tcl_AppInit(Tcl_Interp *interp)
@@ -556,7 +582,7 @@ Tcl_AppInit(Tcl_Interp *interp)
return TCL_OK;
}
- if (Tk_Init(interp) == TCL_ERROR) {
+ if (Tkinter_TkInit(interp) == TCL_ERROR) {
PySys_WriteStderr("Tk_Init error: %s\n", Tcl_GetStringResult(interp));
return TCL_ERROR;
}
@@ -2980,7 +3006,7 @@ _tkinter_tkapp_loadtk_impl(TkappObject *self)
return NULL;
}
if (_tk_exists == NULL || strcmp(_tk_exists, "1") != 0) {
- if (Tk_Init(interp) == TCL_ERROR) {
+ if (Tkinter_TkInit(interp) == TCL_ERROR) {
Tkinter_Error(self);
return NULL;
}
diff --git a/Modules/tkappinit.c b/Modules/tkappinit.c
index 4c4081e43a8e3dd..1075ccf24d44877 100644
--- a/Modules/tkappinit.c
+++ b/Modules/tkappinit.c
@@ -37,7 +37,7 @@ Tcl_AppInit(Tcl_Interp *interp)
return TCL_OK;
}
- if (Tk_Init(interp) == TCL_ERROR) {
+ if (Tkinter_TkInit(interp) == TCL_ERROR) {
return TCL_ERROR;
}
diff --git a/Modules/tkinter.h b/Modules/tkinter.h
index 40281c217603318..b73e99b28a40212 100644
--- a/Modules/tkinter.h
+++ b/Modules/tkinter.h
@@ -16,4 +16,6 @@
(TK_RELEASE_LEVEL << 8) | \
(TK_RELEASE_SERIAL << 0))
+int Tkinter_TkInit(Tcl_Interp *interp);
+
#endif /* !TKINTER_H */
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]