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 | import ctypes |
|
17 | import ctypes | |
|
18 | import os | |||
18 | import sys |
|
19 | import sys | |
19 | import warnings |
|
20 | import warnings | |
20 |
|
21 | |||
@@ -31,11 +32,58 b" GUI_TK = 'tk'" | |||||
31 | GUI_OSX = 'osx' |
|
32 | GUI_OSX = 'osx' | |
32 | GUI_GLUT = 'glut' |
|
33 | GUI_GLUT = 'glut' | |
33 | GUI_PYGLET = 'pyglet' |
|
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 | # Main InputHookManager class |
|
89 | # Main InputHookManager class | |
@@ -70,6 +118,11 b' class InputHookManager(object):' | |||||
70 |
|
118 | |||
71 | def set_inputhook(self, callback): |
|
119 | def set_inputhook(self, callback): | |
72 | """Set PyOS_InputHook to callback and return the previous one.""" |
|
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 | self._callback = callback |
|
126 | self._callback = callback | |
74 | self._callback_pyfunctype = self.PYFUNC(callback) |
|
127 | self._callback_pyfunctype = self.PYFUNC(callback) | |
75 | pyos_inputhook_ptr = self.get_pyos_inputhook() |
|
128 | pyos_inputhook_ptr = self.get_pyos_inputhook() | |
@@ -93,6 +146,7 b' class InputHookManager(object):' | |||||
93 | pyos_inputhook_ptr = self.get_pyos_inputhook() |
|
146 | pyos_inputhook_ptr = self.get_pyos_inputhook() | |
94 | original = self.get_pyos_inputhook_as_func() |
|
147 | original = self.get_pyos_inputhook_as_func() | |
95 | pyos_inputhook_ptr.value = ctypes.c_void_p(None).value |
|
148 | pyos_inputhook_ptr.value = ctypes.c_void_p(None).value | |
|
149 | allow_CTRL_C() | |||
96 | self._reset() |
|
150 | self._reset() | |
97 | return original |
|
151 | return original | |
98 |
|
152 | |||
@@ -181,33 +235,11 b' class InputHookManager(object):' | |||||
181 | from PyQt4 import QtCore |
|
235 | from PyQt4 import QtCore | |
182 | app = QtGui.QApplication(sys.argv) |
|
236 | app = QtGui.QApplication(sys.argv) | |
183 | """ |
|
237 | """ | |
184 | from IPython.external.qt_for_kernel import QtCore, QtGui |
|
238 | from IPython.lib.inputhookqt4 import create_inputhook_qt4 | |
185 |
|
239 | app, inputhook_qt4 = create_inputhook_qt4(self, app) | ||
186 | if 'pyreadline' in sys.modules: |
|
240 | self.set_inputhook(inputhook_qt4) | |
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 |
|
|||
205 |
|
241 | |||
206 | self._current_gui = GUI_QT4 |
|
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 | app._in_event_loop = True |
|
243 | app._in_event_loop = True | |
212 | self._apps[GUI_QT4] = app |
|
244 | self._apps[GUI_QT4] = app | |
213 | return app |
|
245 | return app | |
@@ -416,8 +448,8 b' def enable_gui(gui=None, app=None):' | |||||
416 | Parameters |
|
448 | Parameters | |
417 | ---------- |
|
449 | ---------- | |
418 | gui : optional, string or None |
|
450 | gui : optional, string or None | |
419 |
If None, clears input hook, otherwise it must be one |
|
451 | If None (or 'none'), clears input hook, otherwise it must be one | |
420 | GUI names (see ``GUI_*`` constants in module). |
|
452 | of the recognized GUI names (see ``GUI_*`` constants in module). | |
421 |
|
453 | |||
422 | app : optional, existing application object. |
|
454 | app : optional, existing application object. | |
423 | For toolkits that have the concept of a global app, you can supply an |
|
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 | one. |
|
464 | one. | |
433 | """ |
|
465 | """ | |
434 | guis = {None: clear_inputhook, |
|
466 | guis = {None: clear_inputhook, | |
|
467 | GUI_NONE: clear_inputhook, | |||
435 | GUI_OSX: lambda app=False: None, |
|
468 | GUI_OSX: lambda app=False: None, | |
436 | GUI_TK: enable_tk, |
|
469 | GUI_TK: enable_tk, | |
437 | GUI_GTK: enable_gtk, |
|
470 | GUI_GTK: enable_gtk, |
@@ -24,26 +24,13 b' import time' | |||||
24 | from timeit import default_timer as clock |
|
24 | from timeit import default_timer as clock | |
25 | import wx |
|
25 | import wx | |
26 |
|
26 | |||
27 | if os.name == 'posix': |
|
27 | from IPython.lib.inputhook import stdin_ready | |
28 | import select |
|
28 | ||
29 | elif sys.platform == 'win32': |
|
|||
30 | import msvcrt |
|
|||
31 |
|
29 | |||
32 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
33 | # Code |
|
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 | def inputhook_wx1(): |
|
34 | def inputhook_wx1(): | |
48 | """Run the wx event loop by processing pending events only. |
|
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