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