1# -*- coding: utf-8 -*- 2""" 3Qt4's inputhook support function 4 5Author: Christian Boos 6""" 7 8#----------------------------------------------------------------------------- 9# Copyright (C) 2011 The IPython Development Team 10# 11# Distributed under the terms of the BSD License. The full license is in 12# the file COPYING, distributed as part of this software. 13#----------------------------------------------------------------------------- 14 15#----------------------------------------------------------------------------- 16# Imports 17#----------------------------------------------------------------------------- 18 19import os 20import signal 21 22import threading 23 24 25from pydev_ipython.qt_for_kernel import QtCore, QtGui 26from pydev_ipython.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready 27 28# To minimise future merging complexity, rather than edit the entire code base below 29# we fake InteractiveShell here 30class InteractiveShell: 31 _instance = None 32 @classmethod 33 def instance(cls): 34 if cls._instance is None: 35 cls._instance = cls() 36 return cls._instance 37 def set_hook(self, *args, **kwargs): 38 # We don't consider the pre_prompt_hook because we don't have 39 # KeyboardInterrupts to consider since we are running under PyDev 40 pass 41 42 43#----------------------------------------------------------------------------- 44# Module Globals 45#----------------------------------------------------------------------------- 46 47got_kbdint = False 48sigint_timer = None 49 50#----------------------------------------------------------------------------- 51# Code 52#----------------------------------------------------------------------------- 53 54def create_inputhook_qt4(mgr, app=None): 55 """Create an input hook for running the Qt4 application event loop. 56 57 Parameters 58 ---------- 59 mgr : an InputHookManager 60 61 app : Qt Application, optional. 62 Running application to use. If not given, we probe Qt for an 63 existing application object, and create a new one if none is found. 64 65 Returns 66 ------- 67 A pair consisting of a Qt Application (either the one given or the 68 one found or created) and a inputhook. 69 70 Notes 71 ----- 72 We use a custom input hook instead of PyQt4's default one, as it 73 interacts better with the readline packages (issue #481). 74 75 The inputhook function works in tandem with a 'pre_prompt_hook' 76 which automatically restores the hook as an inputhook in case the 77 latter has been temporarily disabled after having intercepted a 78 KeyboardInterrupt. 79 """ 80 81 if app is None: 82 app = QtCore.QCoreApplication.instance() 83 if app is None: 84 app = QtGui.QApplication([" "]) 85 86 # Re-use previously created inputhook if any 87 ip = InteractiveShell.instance() 88 if hasattr(ip, '_inputhook_qt4'): 89 return app, ip._inputhook_qt4 90 91 # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of 92 # hooks (they both share the got_kbdint flag) 93 94 def inputhook_qt4(): 95 """PyOS_InputHook python hook for Qt4. 96 97 Process pending Qt events and if there's no pending keyboard 98 input, spend a short slice of time (50ms) running the Qt event 99 loop. 100 101 As a Python ctypes callback can't raise an exception, we catch 102 the KeyboardInterrupt and temporarily deactivate the hook, 103 which will let a *second* CTRL+C be processed normally and go 104 back to a clean prompt line. 105 """ 106 try: 107 allow_CTRL_C() 108 app = QtCore.QCoreApplication.instance() 109 if not app: # shouldn't happen, but safer if it happens anyway... 110 return 0 111 app.processEvents(QtCore.QEventLoop.AllEvents, 300) 112 if not stdin_ready(): 113 # Generally a program would run QCoreApplication::exec() 114 # from main() to enter and process the Qt event loop until 115 # quit() or exit() is called and the program terminates. 116 # 117 # For our input hook integration, we need to repeatedly 118 # enter and process the Qt event loop for only a short 119 # amount of time (say 50ms) to ensure that Python stays 120 # responsive to other user inputs. 121 # 122 # A naive approach would be to repeatedly call 123 # QCoreApplication::exec(), using a timer to quit after a 124 # short amount of time. Unfortunately, QCoreApplication 125 # emits an aboutToQuit signal before stopping, which has 126 # the undesirable effect of closing all modal windows. 127 # 128 # To work around this problem, we instead create a 129 # QEventLoop and call QEventLoop::exec(). Other than 130 # setting some state variables which do not seem to be 131 # used anywhere, the only thing QCoreApplication adds is 132 # the aboutToQuit signal which is precisely what we are 133 # trying to avoid. 134 timer = QtCore.QTimer() 135 event_loop = QtCore.QEventLoop() 136 timer.timeout.connect(event_loop.quit) 137 while not stdin_ready(): 138 timer.start(50) 139 event_loop.exec_() 140 timer.stop() 141 except KeyboardInterrupt: 142 global got_kbdint, sigint_timer 143 144 ignore_CTRL_C() 145 got_kbdint = True 146 mgr.clear_inputhook() 147 148 # This generates a second SIGINT so the user doesn't have to 149 # press CTRL+C twice to get a clean prompt. 150 # 151 # Since we can't catch the resulting KeyboardInterrupt here 152 # (because this is a ctypes callback), we use a timer to 153 # generate the SIGINT after we leave this callback. 154 # 155 # Unfortunately this doesn't work on Windows (SIGINT kills 156 # Python and CTRL_C_EVENT doesn't work). 157 if(os.name == 'posix'): 158 pid = os.getpid() 159 if(not sigint_timer): 160 sigint_timer = threading.Timer(.01, os.kill, 161 args=[pid, signal.SIGINT] ) 162 sigint_timer.start() 163 else: 164 print("\nKeyboardInterrupt - Ctrl-C again for new prompt") 165 166 167 except: # NO exceptions are allowed to escape from a ctypes callback 168 ignore_CTRL_C() 169 from traceback import print_exc 170 print_exc() 171 print("Got exception from inputhook_qt4, unregistering.") 172 mgr.clear_inputhook() 173 finally: 174 allow_CTRL_C() 175 return 0 176 177 def preprompthook_qt4(ishell): 178 """'pre_prompt_hook' used to restore the Qt4 input hook 179 180 (in case the latter was temporarily deactivated after a 181 CTRL+C) 182 """ 183 global got_kbdint, sigint_timer 184 185 if(sigint_timer): 186 sigint_timer.cancel() 187 sigint_timer = None 188 189 if got_kbdint: 190 mgr.set_inputhook(inputhook_qt4) 191 got_kbdint = False 192 193 ip._inputhook_qt4 = inputhook_qt4 194 ip.set_hook('pre_prompt_hook', preprompthook_qt4) 195 196 return app, inputhook_qt4 197