##// END OF EJS Templates
Merge pull request #815 from cboos/issue481-qt4-input-hook...
Fernando Perez -
r5215:1f7e9d75 merge
parent child Browse files
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 # Utility classes
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 of the recognized
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