From 0fc80df31e0b11aafbcca408310345e3f0305f97 2011-10-11 18:01:23 From: Christian Boos Date: 2011-10-11 18:01:23 Subject: [PATCH] inputhook: disable CTRL+C when a hook is active. On systems with 'readline', it's very likely to intercept a signal during a select() call. The default SIGINT handler will schedule a KeyboardInterrupt exception to be raised as soon as possible. If ctypes is used to install a Python callback for PyOS_InputHook, this will happen as soon as the bytecode execution starts, so even if the first instruction of the callback is a `try: ... except KeyboardInterrupt` clause, it's actually too late. As ctypes doesn't allow a Python callback to raise an exception, this ends up with IPython detecting an internal error... not pretty. We must therefore ignore the SIGINT signals until we are sure the exception handler is active, in the Python callback. --- diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py index e60d238..7e9b954 100644 --- a/IPython/lib/inputhook.py +++ b/IPython/lib/inputhook.py @@ -51,14 +51,38 @@ def _stdin_ready_other(): """Return True, assuming there's something to read on stdin.""" return True # + +def _ignore_CTRL_C_posix(): + """Ignore CTRL+C (SIGINT).""" + signal.signal(signal.SIGINT, signal.SIG_IGN) + +def _allow_CTRL_C_posix(): + """Take CTRL+C into account (SIGINT).""" + signal.signal(signal.SIGINT, signal.default_int_handler) + +def _ignore_CTRL_C_other(): + """Ignore CTRL+C (not implemented).""" + pass + +def _allow_CTRL_C_other(): + """Take CTRL+C into account (not implemented).""" + pass + if os.name == 'posix': import select + import signal stdin_ready = _stdin_ready_posix + ignore_CTRL_C = _ignore_CTRL_C_posix + allow_CTRL_C = _allow_CTRL_C_posix elif os.name == 'nt': import msvcrt stdin_ready = _stdin_ready_nt + ignore_CTRL_C = _ignore_CTRL_C_other + allow_CTRL_C = _allow_CTRL_C_other else: stdin_ready = _stdin_ready_other + ignore_CTRL_C = _ignore_CTRL_C_other + allow_CTRL_C = _allow_CTRL_C_other #----------------------------------------------------------------------------- @@ -94,6 +118,11 @@ class InputHookManager(object): def set_inputhook(self, callback): """Set PyOS_InputHook to callback and return the previous one.""" + # On platforms with 'readline' support, it's all too likely to + # have a KeyboardInterrupt signal delivered *even before* an + # initial ``try:`` clause in the callback can be executed, so + # we need to disable CTRL+C in this situation. + ignore_CTRL_C() self._callback = callback self._callback_pyfunctype = self.PYFUNC(callback) pyos_inputhook_ptr = self.get_pyos_inputhook() @@ -117,6 +146,7 @@ class InputHookManager(object): pyos_inputhook_ptr = self.get_pyos_inputhook() original = self.get_pyos_inputhook_as_func() pyos_inputhook_ptr.value = ctypes.c_void_p(None).value + allow_CTRL_C() self._reset() return original diff --git a/IPython/lib/inputhookqt4.py b/IPython/lib/inputhookqt4.py index 5e896a6..128bf9c 100644 --- a/IPython/lib/inputhookqt4.py +++ b/IPython/lib/inputhookqt4.py @@ -17,7 +17,7 @@ Author: Christian Boos #----------------------------------------------------------------------------- from IPython.external.qt_for_kernel import QtCore, QtGui -from IPython.lib.inputhook import stdin_ready +from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready #----------------------------------------------------------------------------- # Code @@ -78,6 +78,7 @@ def create_inputhook_qt4(mgr, app=None): back to a clean prompt line. """ try: + allow_CTRL_C() app = QtCore.QCoreApplication.instance() app.processEvents(QtCore.QEventLoop.AllEvents, 300) if not stdin_ready(): @@ -88,13 +89,14 @@ def create_inputhook_qt4(mgr, app=None): app.exec_() timer.stop() except KeyboardInterrupt: + ignore_CTRL_C() got_kbdint[0] = True - mgr.clear_inputhook() print("\nKeyboardInterrupt - qt4 event loop interrupted!" "\n * hit CTRL+C again to clear the prompt" "\n * use '%gui none' to disable the event loop" " permanently" "\n and '%gui qt4' to re-enable it later") + mgr.clear_inputhook() return 0 def preprompthook_qt4(ishell):