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