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