Show More
@@ -0,0 +1,124 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | """ | |
|
3 | Qt4's inputhook support function | |
|
4 | ||
|
5 | Author: 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 | ||
|
19 | from IPython.core.interactiveshell import InteractiveShell | |
|
20 | from IPython.external.qt_for_kernel import QtCore, QtGui | |
|
21 | from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready | |
|
22 | ||
|
23 | #----------------------------------------------------------------------------- | |
|
24 | # Code | |
|
25 | #----------------------------------------------------------------------------- | |
|
26 | ||
|
27 | def create_inputhook_qt4(mgr, app=None): | |
|
28 | """Create an input hook for running the Qt4 application event loop. | |
|
29 | ||
|
30 | Parameters | |
|
31 | ---------- | |
|
32 | mgr : an InputHookManager | |
|
33 | ||
|
34 | app : Qt Application, optional. | |
|
35 | 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. | |
|
37 | ||
|
38 | Returns | |
|
39 | ------- | |
|
40 | A pair consisting of a Qt Application (either the one given or the | |
|
41 | one found or created) and a inputhook. | |
|
42 | ||
|
43 | Notes | |
|
44 | ----- | |
|
45 | We use a custom input hook instead of PyQt4's default one, as it | |
|
46 | interacts better with the readline packages (issue #481). | |
|
47 | ||
|
48 | The inputhook function works in tandem with a 'pre_prompt_hook' | |
|
49 | which automatically restores the hook as an inputhook in case the | |
|
50 | latter has been temporarily disabled after having intercepted a | |
|
51 | KeyboardInterrupt. | |
|
52 | """ | |
|
53 | ||
|
54 | if app is None: | |
|
55 | app = QtCore.QCoreApplication.instance() | |
|
56 | if app is None: | |
|
57 | app = QtGui.QApplication([" "]) | |
|
58 | ||
|
59 | # Re-use previously created inputhook if any | |
|
60 | ip = InteractiveShell.instance() | |
|
61 | if hasattr(ip, '_inputhook_qt4'): | |
|
62 | return app, ip._inputhook_qt4 | |
|
63 | ||
|
64 | # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of | |
|
65 | # hooks (they both share the got_kbdint flag) | |
|
66 | ||
|
67 | got_kbdint = [False] | |
|
68 | ||
|
69 | def inputhook_qt4(): | |
|
70 | """PyOS_InputHook python hook for Qt4. | |
|
71 | ||
|
72 | Process pending Qt events and if there's no pending keyboard | |
|
73 | input, spend a short slice of time (50ms) running the Qt event | |
|
74 | loop. | |
|
75 | ||
|
76 | As a Python ctypes callback can't raise an exception, we catch | |
|
77 | the KeyboardInterrupt and temporarily deactivate the hook, | |
|
78 | which will let a *second* CTRL+C be processed normally and go | |
|
79 | back to a clean prompt line. | |
|
80 | """ | |
|
81 | try: | |
|
82 | allow_CTRL_C() | |
|
83 | app = QtCore.QCoreApplication.instance() | |
|
84 | if not app: # shouldn't happen, but safer if it happens anyway... | |
|
85 | return 0 | |
|
86 | app.processEvents(QtCore.QEventLoop.AllEvents, 300) | |
|
87 | if not stdin_ready(): | |
|
88 | timer = QtCore.QTimer() | |
|
89 | timer.timeout.connect(app.quit) | |
|
90 | while not stdin_ready(): | |
|
91 | timer.start(50) | |
|
92 | app.exec_() | |
|
93 | timer.stop() | |
|
94 | ignore_CTRL_C() | |
|
95 | except KeyboardInterrupt: | |
|
96 | ignore_CTRL_C() | |
|
97 | got_kbdint[0] = True | |
|
98 | print("\nKeyboardInterrupt - qt4 event loop interrupted!" | |
|
99 | "\n * hit CTRL+C again to clear the prompt" | |
|
100 | "\n * use '%gui none' to disable the event loop" | |
|
101 | " permanently" | |
|
102 | "\n and '%gui qt4' to re-enable it later") | |
|
103 | mgr.clear_inputhook() | |
|
104 | except: # NO exceptions are allowed to escape from a ctypes callback | |
|
105 | mgr.clear_inputhook() | |
|
106 | from traceback import print_exc | |
|
107 | print_exc() | |
|
108 | print("Got exception from inputhook_qt4, unregistering.") | |
|
109 | return 0 | |
|
110 | ||
|
111 | def preprompthook_qt4(ishell): | |
|
112 | """'pre_prompt_hook' used to restore the Qt4 input hook | |
|
113 | ||
|
114 | (in case the latter was temporarily deactivated after a | |
|
115 | CTRL+C) | |
|
116 | """ | |
|
117 | if got_kbdint[0]: | |
|
118 | mgr.set_inputhook(inputhook_qt4) | |
|
119 | got_kbdint[0] = False | |
|
120 | ||
|
121 | ip._inputhook_qt4 = inputhook_qt4 | |
|
122 | ip.set_hook('pre_prompt_hook', preprompthook_qt4) | |
|
123 | ||
|
124 | return app, inputhook_qt4 |
@@ -15,6 +15,7 b' Inputhook management for GUI event loop integration.' | |||
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import ctypes |
|
18 | import os | |
|
18 | 19 | import sys |
|
19 | 20 | import warnings |
|
20 | 21 | |
@@ -31,11 +32,58 b" GUI_TK = 'tk'" | |||
|
31 | 32 | GUI_OSX = 'osx' |
|
32 | 33 | GUI_GLUT = 'glut' |
|
33 | 34 | GUI_PYGLET = 'pyglet' |
|
35 | GUI_NONE = 'none' # i.e. disable | |
|
34 | 36 | |
|
35 | 37 | #----------------------------------------------------------------------------- |
|
36 |
# Utilit |
|
|
38 | # Utilities | |
|
37 | 39 | #----------------------------------------------------------------------------- |
|
38 | 40 | |
|
41 | def _stdin_ready_posix(): | |
|
42 | """Return True if there's something to read on stdin (posix version).""" | |
|
43 | infds, outfds, erfds = select.select([sys.stdin],[],[],0) | |
|
44 | return bool(infds) | |
|
45 | ||
|
46 | def _stdin_ready_nt(): | |
|
47 | """Return True if there's something to read on stdin (nt version).""" | |
|
48 | return msvcrt.kbhit() | |
|
49 | ||
|
50 | def _stdin_ready_other(): | |
|
51 | """Return True, assuming there's something to read on stdin.""" | |
|
52 | return True # | |
|
53 | ||
|
54 | ||
|
55 | def _ignore_CTRL_C_posix(): | |
|
56 | """Ignore CTRL+C (SIGINT).""" | |
|
57 | signal.signal(signal.SIGINT, signal.SIG_IGN) | |
|
58 | ||
|
59 | def _allow_CTRL_C_posix(): | |
|
60 | """Take CTRL+C into account (SIGINT).""" | |
|
61 | signal.signal(signal.SIGINT, signal.default_int_handler) | |
|
62 | ||
|
63 | def _ignore_CTRL_C_other(): | |
|
64 | """Ignore CTRL+C (not implemented).""" | |
|
65 | pass | |
|
66 | ||
|
67 | def _allow_CTRL_C_other(): | |
|
68 | """Take CTRL+C into account (not implemented).""" | |
|
69 | pass | |
|
70 | ||
|
71 | if os.name == 'posix': | |
|
72 | import select | |
|
73 | import signal | |
|
74 | stdin_ready = _stdin_ready_posix | |
|
75 | ignore_CTRL_C = _ignore_CTRL_C_posix | |
|
76 | allow_CTRL_C = _allow_CTRL_C_posix | |
|
77 | elif os.name == 'nt': | |
|
78 | import msvcrt | |
|
79 | stdin_ready = _stdin_ready_nt | |
|
80 | ignore_CTRL_C = _ignore_CTRL_C_other | |
|
81 | allow_CTRL_C = _allow_CTRL_C_other | |
|
82 | else: | |
|
83 | stdin_ready = _stdin_ready_other | |
|
84 | ignore_CTRL_C = _ignore_CTRL_C_other | |
|
85 | allow_CTRL_C = _allow_CTRL_C_other | |
|
86 | ||
|
39 | 87 | |
|
40 | 88 | #----------------------------------------------------------------------------- |
|
41 | 89 | # Main InputHookManager class |
@@ -70,6 +118,11 b' class InputHookManager(object):' | |||
|
70 | 118 | |
|
71 | 119 | def set_inputhook(self, callback): |
|
72 | 120 | """Set PyOS_InputHook to callback and return the previous one.""" |
|
121 | # On platforms with 'readline' support, it's all too likely to | |
|
122 | # have a KeyboardInterrupt signal delivered *even before* an | |
|
123 | # initial ``try:`` clause in the callback can be executed, so | |
|
124 | # we need to disable CTRL+C in this situation. | |
|
125 | ignore_CTRL_C() | |
|
73 | 126 | self._callback = callback |
|
74 | 127 | self._callback_pyfunctype = self.PYFUNC(callback) |
|
75 | 128 | pyos_inputhook_ptr = self.get_pyos_inputhook() |
@@ -93,6 +146,7 b' class InputHookManager(object):' | |||
|
93 | 146 | pyos_inputhook_ptr = self.get_pyos_inputhook() |
|
94 | 147 | original = self.get_pyos_inputhook_as_func() |
|
95 | 148 | pyos_inputhook_ptr.value = ctypes.c_void_p(None).value |
|
149 | allow_CTRL_C() | |
|
96 | 150 | self._reset() |
|
97 | 151 | return original |
|
98 | 152 | |
@@ -181,33 +235,11 b' class InputHookManager(object):' | |||
|
181 | 235 | from PyQt4 import QtCore |
|
182 | 236 | app = QtGui.QApplication(sys.argv) |
|
183 | 237 | """ |
|
184 | from IPython.external.qt_for_kernel import QtCore, QtGui | |
|
185 | ||
|
186 | if 'pyreadline' in sys.modules: | |
|
187 | # see IPython GitHub Issue #281 for more info on this issue | |
|
188 | # Similar intermittent behavior has been reported on OSX, | |
|
189 | # but not consistently reproducible | |
|
190 | warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays | |
|
191 | in interactive input. If you do see this issue, we recommend using another GUI | |
|
192 | toolkit if you can, or disable readline with the configuration option | |
|
193 | 'TerminalInteractiveShell.readline_use=False', specified in a config file or | |
|
194 | at the command-line""", | |
|
195 | RuntimeWarning) | |
|
196 | ||
|
197 | # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook | |
|
198 | # was set when QtCore was imported, but if it ever got removed, | |
|
199 | # you couldn't reset it. For earlier versions we can | |
|
200 | # probably implement a ctypes version. | |
|
201 | try: | |
|
202 | QtCore.pyqtRestoreInputHook() | |
|
203 | except AttributeError: | |
|
204 | pass | |
|
238 | from IPython.lib.inputhookqt4 import create_inputhook_qt4 | |
|
239 | app, inputhook_qt4 = create_inputhook_qt4(self, app) | |
|
240 | self.set_inputhook(inputhook_qt4) | |
|
205 | 241 | |
|
206 | 242 | self._current_gui = GUI_QT4 |
|
207 | if app is None: | |
|
208 | app = QtCore.QCoreApplication.instance() | |
|
209 | if app is None: | |
|
210 | app = QtGui.QApplication([" "]) | |
|
211 | 243 | app._in_event_loop = True |
|
212 | 244 | self._apps[GUI_QT4] = app |
|
213 | 245 | return app |
@@ -416,8 +448,8 b' def enable_gui(gui=None, app=None):' | |||
|
416 | 448 | Parameters |
|
417 | 449 | ---------- |
|
418 | 450 | gui : optional, string or None |
|
419 |
If None, clears input hook, otherwise it must be one |
|
|
420 | GUI names (see ``GUI_*`` constants in module). | |
|
451 | If None (or 'none'), clears input hook, otherwise it must be one | |
|
452 | of the recognized GUI names (see ``GUI_*`` constants in module). | |
|
421 | 453 | |
|
422 | 454 | app : optional, existing application object. |
|
423 | 455 | For toolkits that have the concept of a global app, you can supply an |
@@ -432,6 +464,7 b' def enable_gui(gui=None, app=None):' | |||
|
432 | 464 | one. |
|
433 | 465 | """ |
|
434 | 466 | guis = {None: clear_inputhook, |
|
467 | GUI_NONE: clear_inputhook, | |
|
435 | 468 | GUI_OSX: lambda app=False: None, |
|
436 | 469 | GUI_TK: enable_tk, |
|
437 | 470 | GUI_GTK: enable_gtk, |
@@ -24,26 +24,13 b' import time' | |||
|
24 | 24 | from timeit import default_timer as clock |
|
25 | 25 | import wx |
|
26 | 26 | |
|
27 | if os.name == 'posix': | |
|
28 | import select | |
|
29 | elif sys.platform == 'win32': | |
|
30 | import msvcrt | |
|
27 | from IPython.lib.inputhook import stdin_ready | |
|
28 | ||
|
31 | 29 | |
|
32 | 30 | #----------------------------------------------------------------------------- |
|
33 | 31 | # Code |
|
34 | 32 | #----------------------------------------------------------------------------- |
|
35 | 33 | |
|
36 | def stdin_ready(): | |
|
37 | if os.name == 'posix': | |
|
38 | infds, outfds, erfds = select.select([sys.stdin],[],[],0) | |
|
39 | if infds: | |
|
40 | return True | |
|
41 | else: | |
|
42 | return False | |
|
43 | elif sys.platform == 'win32': | |
|
44 | return msvcrt.kbhit() | |
|
45 | ||
|
46 | ||
|
47 | 34 | def inputhook_wx1(): |
|
48 | 35 | """Run the wx event loop by processing pending events only. |
|
49 | 36 |
General Comments 0
You need to be logged in to leave comments.
Login now