# -*- coding: utf-8 -*- """ Qt4's inputhook support function Author: Christian Boos """ #----------------------------------------------------------------------------- # Copyright (C) 2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- import os import signal import threading from IPython.core.interactiveshell import InteractiveShell from IPython.external.qt_for_kernel import QtCore, QtGui from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready #----------------------------------------------------------------------------- # Module Globals #----------------------------------------------------------------------------- got_kbdint = False sigint_timer = None #----------------------------------------------------------------------------- # Code #----------------------------------------------------------------------------- def create_inputhook_qt4(mgr, app=None): """Create an input hook for running the Qt4 application event loop. Parameters ---------- mgr : an InputHookManager app : Qt Application, optional. Running application to use. If not given, we probe Qt for an existing application object, and create a new one if none is found. Returns ------- A pair consisting of a Qt Application (either the one given or the one found or created) and a inputhook. Notes ----- We use a custom input hook instead of PyQt4's default one, as it interacts better with the readline packages (issue #481). The inputhook function works in tandem with a 'pre_prompt_hook' which automatically restores the hook as an inputhook in case the latter has been temporarily disabled after having intercepted a KeyboardInterrupt. """ if app is None: app = QtCore.QCoreApplication.instance() if app is None: app = QtGui.QApplication([" "]) # Re-use previously created inputhook if any ip = InteractiveShell.instance() if hasattr(ip, '_inputhook_qt4'): return app, ip._inputhook_qt4 # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of # hooks (they both share the got_kbdint flag) def inputhook_qt4(): """PyOS_InputHook python hook for Qt4. Process pending Qt events and if there's no pending keyboard input, spend a short slice of time (50ms) running the Qt event loop. As a Python ctypes callback can't raise an exception, we catch the KeyboardInterrupt and temporarily deactivate the hook, which will let a *second* CTRL+C be processed normally and go back to a clean prompt line. """ try: allow_CTRL_C() app = QtCore.QCoreApplication.instance() if not app: # shouldn't happen, but safer if it happens anyway... return 0 app.processEvents(QtCore.QEventLoop.AllEvents, 300) if not stdin_ready(): # Generally a program would run QCoreApplication::exec() # from main() to enter and process the Qt event loop until # quit() or exit() is called and the program terminates. # # For our input hook integration, we need to repeatedly # enter and process the Qt event loop for only a short # amount of time (say 50ms) to ensure that Python stays # responsive to other user inputs. # # A naive approach would be to repeatedly call # QCoreApplication::exec(), using a timer to quit after a # short amount of time. Unfortunately, QCoreApplication # emits an aboutToQuit signal before stopping, which has # the undesirable effect of closing all modal windows. # # To work around this problem, we instead create a # QEventLoop and call QEventLoop::exec(). Other than # setting some state variables which do not seem to be # used anywhere, the only thing QCoreApplication adds is # the aboutToQuit signal which is precisely what we are # trying to avoid. timer = QtCore.QTimer() event_loop = QtCore.QEventLoop() timer.timeout.connect(event_loop.quit) while not stdin_ready(): timer.start(50) event_loop.exec_() timer.stop() except KeyboardInterrupt: global got_kbdint, sigint_timer ignore_CTRL_C() got_kbdint = True mgr.clear_inputhook() # This generates a second SIGINT so the user doesn't have to # press CTRL+C twice to get a clean prompt. # # Since we can't catch the resulting KeyboardInterrupt here # (because this is a ctypes callback), we use a timer to # generate the SIGINT after we leave this callback. # # Unfortunately this doesn't work on Windows (SIGINT kills # Python and CTRL_C_EVENT doesn't work). if(os.name == 'posix'): pid = os.getpid() if(not sigint_timer): sigint_timer = threading.Timer(.01, os.kill, args=[pid, signal.SIGINT] ) sigint_timer.start() else: print("\nKeyboardInterrupt - Ctrl-C again for new prompt") except: # NO exceptions are allowed to escape from a ctypes callback ignore_CTRL_C() from traceback import print_exc print_exc() print("Got exception from inputhook_qt4, unregistering.") mgr.clear_inputhook() finally: allow_CTRL_C() return 0 def preprompthook_qt4(ishell): """'pre_prompt_hook' used to restore the Qt4 input hook (in case the latter was temporarily deactivated after a CTRL+C) """ global got_kbdint, sigint_timer if(sigint_timer): sigint_timer.cancel() sigint_timer = None if got_kbdint: mgr.set_inputhook(inputhook_qt4) got_kbdint = False ip._inputhook_qt4 = inputhook_qt4 ip.set_hook('pre_prompt_hook', preprompthook_qt4) return app, inputhook_qt4