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