https://github.com/python/cpython/commit/5f508b96a7395cad0563e3a19533c4bbf151017b
commit: 5f508b96a7395cad0563e3a19533c4bbf151017b
branch: 3.14
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-23T10:52:39Z
summary:

[3.14] gh-157672: Keep Python's signal handlers when Tk is initialized on macOS 
(GH-157673) (GH-157988)

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)

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

diff --git a/Lib/test/test_tkinter/test_misc.py 
b/Lib/test/test_tkinter/test_misc.py
index cc44449027f173d..8bd93ff77d6cc44 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,
@@ -1913,5 +1915,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 22126b67d8f4a0b..9b13c7ace7f4f29 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -34,6 +34,8 @@ Copyright (C) 1994 Steen Lumholt.
 #include "pycore_sysmodule.h"     // _PySys_GetOptionalAttrString()
 #include "pycore_unicodeobject.h" // _PyUnicode_AsUTF8String
 
+#include <signal.h>               // SIGINT
+
 #ifdef MS_WINDOWS
 #  include <windows.h>
 #endif
@@ -251,7 +253,25 @@ Tkinter_TkInit(Tcl_Interp *interp)
        does not search. Mount the DLL using Zipfs if possible.  */
     mount_tk_dll_zip();
 #endif
-    return Tk_Init(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;
 }
 
 /* The threading situation is complicated.  Tcl is not thread-safe, except

_______________________________________________
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]

Reply via email to