##// 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
@@ -1,450 +1,483 b''
1 # coding: utf-8
1 # coding: utf-8
2 """
2 """
3 Inputhook management for GUI event loop integration.
3 Inputhook management for GUI event loop integration.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2009 The IPython Development Team
7 # Copyright (C) 2008-2009 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
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
21 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
22 # Constants
23 # Constants
23 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
24
25
25 # Constants for identifying the GUI toolkits.
26 # Constants for identifying the GUI toolkits.
26 GUI_WX = 'wx'
27 GUI_WX = 'wx'
27 GUI_QT = 'qt'
28 GUI_QT = 'qt'
28 GUI_QT4 = 'qt4'
29 GUI_QT4 = 'qt4'
29 GUI_GTK = 'gtk'
30 GUI_GTK = 'gtk'
30 GUI_TK = 'tk'
31 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
42 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
43
91
44
92
45 class InputHookManager(object):
93 class InputHookManager(object):
46 """Manage PyOS_InputHook for different GUI toolkits.
94 """Manage PyOS_InputHook for different GUI toolkits.
47
95
48 This class installs various hooks under ``PyOSInputHook`` to handle
96 This class installs various hooks under ``PyOSInputHook`` to handle
49 GUI event loop integration.
97 GUI event loop integration.
50 """
98 """
51
99
52 def __init__(self):
100 def __init__(self):
53 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
101 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
54 self._apps = {}
102 self._apps = {}
55 self._reset()
103 self._reset()
56
104
57 def _reset(self):
105 def _reset(self):
58 self._callback_pyfunctype = None
106 self._callback_pyfunctype = None
59 self._callback = None
107 self._callback = None
60 self._installed = False
108 self._installed = False
61 self._current_gui = None
109 self._current_gui = None
62
110
63 def get_pyos_inputhook(self):
111 def get_pyos_inputhook(self):
64 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
112 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
65 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
113 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
66
114
67 def get_pyos_inputhook_as_func(self):
115 def get_pyos_inputhook_as_func(self):
68 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
116 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
69 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
117 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
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()
76 original = self.get_pyos_inputhook_as_func()
129 original = self.get_pyos_inputhook_as_func()
77 pyos_inputhook_ptr.value = \
130 pyos_inputhook_ptr.value = \
78 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
131 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
79 self._installed = True
132 self._installed = True
80 return original
133 return original
81
134
82 def clear_inputhook(self, app=None):
135 def clear_inputhook(self, app=None):
83 """Set PyOS_InputHook to NULL and return the previous one.
136 """Set PyOS_InputHook to NULL and return the previous one.
84
137
85 Parameters
138 Parameters
86 ----------
139 ----------
87 app : optional, ignored
140 app : optional, ignored
88 This parameter is allowed only so that clear_inputhook() can be
141 This parameter is allowed only so that clear_inputhook() can be
89 called with a similar interface as all the ``enable_*`` methods. But
142 called with a similar interface as all the ``enable_*`` methods. But
90 the actual value of the parameter is ignored. This uniform interface
143 the actual value of the parameter is ignored. This uniform interface
91 makes it easier to have user-level entry points in the main IPython
144 makes it easier to have user-level entry points in the main IPython
92 app like :meth:`enable_gui`."""
145 app like :meth:`enable_gui`."""
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
99 def clear_app_refs(self, gui=None):
153 def clear_app_refs(self, gui=None):
100 """Clear IPython's internal reference to an application instance.
154 """Clear IPython's internal reference to an application instance.
101
155
102 Whenever we create an app for a user on qt4 or wx, we hold a
156 Whenever we create an app for a user on qt4 or wx, we hold a
103 reference to the app. This is needed because in some cases bad things
157 reference to the app. This is needed because in some cases bad things
104 can happen if a user doesn't hold a reference themselves. This
158 can happen if a user doesn't hold a reference themselves. This
105 method is provided to clear the references we are holding.
159 method is provided to clear the references we are holding.
106
160
107 Parameters
161 Parameters
108 ----------
162 ----------
109 gui : None or str
163 gui : None or str
110 If None, clear all app references. If ('wx', 'qt4') clear
164 If None, clear all app references. If ('wx', 'qt4') clear
111 the app for that toolkit. References are not held for gtk or tk
165 the app for that toolkit. References are not held for gtk or tk
112 as those toolkits don't have the notion of an app.
166 as those toolkits don't have the notion of an app.
113 """
167 """
114 if gui is None:
168 if gui is None:
115 self._apps = {}
169 self._apps = {}
116 elif self._apps.has_key(gui):
170 elif self._apps.has_key(gui):
117 del self._apps[gui]
171 del self._apps[gui]
118
172
119 def enable_wx(self, app=None):
173 def enable_wx(self, app=None):
120 """Enable event loop integration with wxPython.
174 """Enable event loop integration with wxPython.
121
175
122 Parameters
176 Parameters
123 ----------
177 ----------
124 app : WX Application, optional.
178 app : WX Application, optional.
125 Running application to use. If not given, we probe WX for an
179 Running application to use. If not given, we probe WX for an
126 existing application object, and create a new one if none is found.
180 existing application object, and create a new one if none is found.
127
181
128 Notes
182 Notes
129 -----
183 -----
130 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
184 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
131 the wxPython to integrate with terminal based applications like
185 the wxPython to integrate with terminal based applications like
132 IPython.
186 IPython.
133
187
134 If ``app`` is not given we probe for an existing one, and return it if
188 If ``app`` is not given we probe for an existing one, and return it if
135 found. If no existing app is found, we create an :class:`wx.App` as
189 found. If no existing app is found, we create an :class:`wx.App` as
136 follows::
190 follows::
137
191
138 import wx
192 import wx
139 app = wx.App(redirect=False, clearSigInt=False)
193 app = wx.App(redirect=False, clearSigInt=False)
140 """
194 """
141 from IPython.lib.inputhookwx import inputhook_wx
195 from IPython.lib.inputhookwx import inputhook_wx
142 self.set_inputhook(inputhook_wx)
196 self.set_inputhook(inputhook_wx)
143 self._current_gui = GUI_WX
197 self._current_gui = GUI_WX
144 import wx
198 import wx
145 if app is None:
199 if app is None:
146 app = wx.GetApp()
200 app = wx.GetApp()
147 if app is None:
201 if app is None:
148 app = wx.App(redirect=False, clearSigInt=False)
202 app = wx.App(redirect=False, clearSigInt=False)
149 app._in_event_loop = True
203 app._in_event_loop = True
150 self._apps[GUI_WX] = app
204 self._apps[GUI_WX] = app
151 return app
205 return app
152
206
153 def disable_wx(self):
207 def disable_wx(self):
154 """Disable event loop integration with wxPython.
208 """Disable event loop integration with wxPython.
155
209
156 This merely sets PyOS_InputHook to NULL.
210 This merely sets PyOS_InputHook to NULL.
157 """
211 """
158 if self._apps.has_key(GUI_WX):
212 if self._apps.has_key(GUI_WX):
159 self._apps[GUI_WX]._in_event_loop = False
213 self._apps[GUI_WX]._in_event_loop = False
160 self.clear_inputhook()
214 self.clear_inputhook()
161
215
162 def enable_qt4(self, app=None):
216 def enable_qt4(self, app=None):
163 """Enable event loop integration with PyQt4.
217 """Enable event loop integration with PyQt4.
164
218
165 Parameters
219 Parameters
166 ----------
220 ----------
167 app : Qt Application, optional.
221 app : Qt Application, optional.
168 Running application to use. If not given, we probe Qt for an
222 Running application to use. If not given, we probe Qt for an
169 existing application object, and create a new one if none is found.
223 existing application object, and create a new one if none is found.
170
224
171 Notes
225 Notes
172 -----
226 -----
173 This methods sets the PyOS_InputHook for PyQt4, which allows
227 This methods sets the PyOS_InputHook for PyQt4, which allows
174 the PyQt4 to integrate with terminal based applications like
228 the PyQt4 to integrate with terminal based applications like
175 IPython.
229 IPython.
176
230
177 If ``app`` is not given we probe for an existing one, and return it if
231 If ``app`` is not given we probe for an existing one, and return it if
178 found. If no existing app is found, we create an :class:`QApplication`
232 found. If no existing app is found, we create an :class:`QApplication`
179 as follows::
233 as follows::
180
234
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
214
246
215 def disable_qt4(self):
247 def disable_qt4(self):
216 """Disable event loop integration with PyQt4.
248 """Disable event loop integration with PyQt4.
217
249
218 This merely sets PyOS_InputHook to NULL.
250 This merely sets PyOS_InputHook to NULL.
219 """
251 """
220 if self._apps.has_key(GUI_QT4):
252 if self._apps.has_key(GUI_QT4):
221 self._apps[GUI_QT4]._in_event_loop = False
253 self._apps[GUI_QT4]._in_event_loop = False
222 self.clear_inputhook()
254 self.clear_inputhook()
223
255
224 def enable_gtk(self, app=None):
256 def enable_gtk(self, app=None):
225 """Enable event loop integration with PyGTK.
257 """Enable event loop integration with PyGTK.
226
258
227 Parameters
259 Parameters
228 ----------
260 ----------
229 app : ignored
261 app : ignored
230 Ignored, it's only a placeholder to keep the call signature of all
262 Ignored, it's only a placeholder to keep the call signature of all
231 gui activation methods consistent, which simplifies the logic of
263 gui activation methods consistent, which simplifies the logic of
232 supporting magics.
264 supporting magics.
233
265
234 Notes
266 Notes
235 -----
267 -----
236 This methods sets the PyOS_InputHook for PyGTK, which allows
268 This methods sets the PyOS_InputHook for PyGTK, which allows
237 the PyGTK to integrate with terminal based applications like
269 the PyGTK to integrate with terminal based applications like
238 IPython.
270 IPython.
239 """
271 """
240 import gtk
272 import gtk
241 try:
273 try:
242 gtk.set_interactive(True)
274 gtk.set_interactive(True)
243 self._current_gui = GUI_GTK
275 self._current_gui = GUI_GTK
244 except AttributeError:
276 except AttributeError:
245 # For older versions of gtk, use our own ctypes version
277 # For older versions of gtk, use our own ctypes version
246 from IPython.lib.inputhookgtk import inputhook_gtk
278 from IPython.lib.inputhookgtk import inputhook_gtk
247 self.set_inputhook(inputhook_gtk)
279 self.set_inputhook(inputhook_gtk)
248 self._current_gui = GUI_GTK
280 self._current_gui = GUI_GTK
249
281
250 def disable_gtk(self):
282 def disable_gtk(self):
251 """Disable event loop integration with PyGTK.
283 """Disable event loop integration with PyGTK.
252
284
253 This merely sets PyOS_InputHook to NULL.
285 This merely sets PyOS_InputHook to NULL.
254 """
286 """
255 self.clear_inputhook()
287 self.clear_inputhook()
256
288
257 def enable_tk(self, app=None):
289 def enable_tk(self, app=None):
258 """Enable event loop integration with Tk.
290 """Enable event loop integration with Tk.
259
291
260 Parameters
292 Parameters
261 ----------
293 ----------
262 app : toplevel :class:`Tkinter.Tk` widget, optional.
294 app : toplevel :class:`Tkinter.Tk` widget, optional.
263 Running toplevel widget to use. If not given, we probe Tk for an
295 Running toplevel widget to use. If not given, we probe Tk for an
264 existing one, and create a new one if none is found.
296 existing one, and create a new one if none is found.
265
297
266 Notes
298 Notes
267 -----
299 -----
268 If you have already created a :class:`Tkinter.Tk` object, the only
300 If you have already created a :class:`Tkinter.Tk` object, the only
269 thing done by this method is to register with the
301 thing done by this method is to register with the
270 :class:`InputHookManager`, since creating that object automatically
302 :class:`InputHookManager`, since creating that object automatically
271 sets ``PyOS_InputHook``.
303 sets ``PyOS_InputHook``.
272 """
304 """
273 self._current_gui = GUI_TK
305 self._current_gui = GUI_TK
274 if app is None:
306 if app is None:
275 import Tkinter
307 import Tkinter
276 app = Tkinter.Tk()
308 app = Tkinter.Tk()
277 app.withdraw()
309 app.withdraw()
278 self._apps[GUI_TK] = app
310 self._apps[GUI_TK] = app
279 return app
311 return app
280
312
281 def disable_tk(self):
313 def disable_tk(self):
282 """Disable event loop integration with Tkinter.
314 """Disable event loop integration with Tkinter.
283
315
284 This merely sets PyOS_InputHook to NULL.
316 This merely sets PyOS_InputHook to NULL.
285 """
317 """
286 self.clear_inputhook()
318 self.clear_inputhook()
287
319
288
320
289 def enable_glut(self, app=None):
321 def enable_glut(self, app=None):
290 """ Enable event loop integration with GLUT.
322 """ Enable event loop integration with GLUT.
291
323
292 Parameters
324 Parameters
293 ----------
325 ----------
294
326
295 app : ignored
327 app : ignored
296 Ignored, it's only a placeholder to keep the call signature of all
328 Ignored, it's only a placeholder to keep the call signature of all
297 gui activation methods consistent, which simplifies the logic of
329 gui activation methods consistent, which simplifies the logic of
298 supporting magics.
330 supporting magics.
299
331
300 Notes
332 Notes
301 -----
333 -----
302
334
303 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
335 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
304 integrate with terminal based applications like IPython. Due to GLUT
336 integrate with terminal based applications like IPython. Due to GLUT
305 limitations, it is currently not possible to start the event loop
337 limitations, it is currently not possible to start the event loop
306 without first creating a window. You should thus not create another
338 without first creating a window. You should thus not create another
307 window but use instead the created one. See 'gui-glut.py' in the
339 window but use instead the created one. See 'gui-glut.py' in the
308 docs/examples/lib directory.
340 docs/examples/lib directory.
309
341
310 The default screen mode is set to:
342 The default screen mode is set to:
311 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
343 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
312 """
344 """
313
345
314 import OpenGL.GLUT as glut
346 import OpenGL.GLUT as glut
315 from IPython.lib.inputhookglut import glut_display_mode, \
347 from IPython.lib.inputhookglut import glut_display_mode, \
316 glut_close, glut_display, \
348 glut_close, glut_display, \
317 glut_idle, inputhook_glut
349 glut_idle, inputhook_glut
318
350
319 if not self._apps.has_key( GUI_GLUT ):
351 if not self._apps.has_key( GUI_GLUT ):
320 glut.glutInit( sys.argv )
352 glut.glutInit( sys.argv )
321 glut.glutInitDisplayMode( glut_display_mode )
353 glut.glutInitDisplayMode( glut_display_mode )
322 # This is specific to freeglut
354 # This is specific to freeglut
323 if bool(glut.glutSetOption):
355 if bool(glut.glutSetOption):
324 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
356 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
325 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
357 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
326 glut.glutCreateWindow( sys.argv[0] )
358 glut.glutCreateWindow( sys.argv[0] )
327 glut.glutReshapeWindow( 1, 1 )
359 glut.glutReshapeWindow( 1, 1 )
328 glut.glutHideWindow( )
360 glut.glutHideWindow( )
329 glut.glutWMCloseFunc( glut_close )
361 glut.glutWMCloseFunc( glut_close )
330 glut.glutDisplayFunc( glut_display )
362 glut.glutDisplayFunc( glut_display )
331 glut.glutIdleFunc( glut_idle )
363 glut.glutIdleFunc( glut_idle )
332 else:
364 else:
333 glut.glutWMCloseFunc( glut_close )
365 glut.glutWMCloseFunc( glut_close )
334 glut.glutDisplayFunc( glut_display )
366 glut.glutDisplayFunc( glut_display )
335 glut.glutIdleFunc( glut_idle)
367 glut.glutIdleFunc( glut_idle)
336 self.set_inputhook( inputhook_glut )
368 self.set_inputhook( inputhook_glut )
337 self._current_gui = GUI_GLUT
369 self._current_gui = GUI_GLUT
338 self._apps[GUI_GLUT] = True
370 self._apps[GUI_GLUT] = True
339
371
340
372
341 def disable_glut(self):
373 def disable_glut(self):
342 """Disable event loop integration with glut.
374 """Disable event loop integration with glut.
343
375
344 This sets PyOS_InputHook to NULL and set the display function to a
376 This sets PyOS_InputHook to NULL and set the display function to a
345 dummy one and set the timer to a dummy timer that will be triggered
377 dummy one and set the timer to a dummy timer that will be triggered
346 very far in the future.
378 very far in the future.
347 """
379 """
348 import OpenGL.GLUT as glut
380 import OpenGL.GLUT as glut
349 from glut_support import glutMainLoopEvent
381 from glut_support import glutMainLoopEvent
350
382
351 glut.glutHideWindow() # This is an event to be processed below
383 glut.glutHideWindow() # This is an event to be processed below
352 glutMainLoopEvent()
384 glutMainLoopEvent()
353 self.clear_inputhook()
385 self.clear_inputhook()
354
386
355 def enable_pyglet(self, app=None):
387 def enable_pyglet(self, app=None):
356 """Enable event loop integration with pyglet.
388 """Enable event loop integration with pyglet.
357
389
358 Parameters
390 Parameters
359 ----------
391 ----------
360 app : ignored
392 app : ignored
361 Ignored, it's only a placeholder to keep the call signature of all
393 Ignored, it's only a placeholder to keep the call signature of all
362 gui activation methods consistent, which simplifies the logic of
394 gui activation methods consistent, which simplifies the logic of
363 supporting magics.
395 supporting magics.
364
396
365 Notes
397 Notes
366 -----
398 -----
367 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
399 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
368 pyglet to integrate with terminal based applications like
400 pyglet to integrate with terminal based applications like
369 IPython.
401 IPython.
370
402
371 """
403 """
372 import pyglet
404 import pyglet
373 from IPython.lib.inputhookpyglet import inputhook_pyglet
405 from IPython.lib.inputhookpyglet import inputhook_pyglet
374 self.set_inputhook(inputhook_pyglet)
406 self.set_inputhook(inputhook_pyglet)
375 self._current_gui = GUI_PYGLET
407 self._current_gui = GUI_PYGLET
376 return app
408 return app
377
409
378 def disable_pyglet(self):
410 def disable_pyglet(self):
379 """Disable event loop integration with pyglet.
411 """Disable event loop integration with pyglet.
380
412
381 This merely sets PyOS_InputHook to NULL.
413 This merely sets PyOS_InputHook to NULL.
382 """
414 """
383 self.clear_inputhook()
415 self.clear_inputhook()
384
416
385 def current_gui(self):
417 def current_gui(self):
386 """Return a string indicating the currently active GUI or None."""
418 """Return a string indicating the currently active GUI or None."""
387 return self._current_gui
419 return self._current_gui
388
420
389 inputhook_manager = InputHookManager()
421 inputhook_manager = InputHookManager()
390
422
391 enable_wx = inputhook_manager.enable_wx
423 enable_wx = inputhook_manager.enable_wx
392 disable_wx = inputhook_manager.disable_wx
424 disable_wx = inputhook_manager.disable_wx
393 enable_qt4 = inputhook_manager.enable_qt4
425 enable_qt4 = inputhook_manager.enable_qt4
394 disable_qt4 = inputhook_manager.disable_qt4
426 disable_qt4 = inputhook_manager.disable_qt4
395 enable_gtk = inputhook_manager.enable_gtk
427 enable_gtk = inputhook_manager.enable_gtk
396 disable_gtk = inputhook_manager.disable_gtk
428 disable_gtk = inputhook_manager.disable_gtk
397 enable_tk = inputhook_manager.enable_tk
429 enable_tk = inputhook_manager.enable_tk
398 disable_tk = inputhook_manager.disable_tk
430 disable_tk = inputhook_manager.disable_tk
399 enable_glut = inputhook_manager.enable_glut
431 enable_glut = inputhook_manager.enable_glut
400 disable_glut = inputhook_manager.disable_glut
432 disable_glut = inputhook_manager.disable_glut
401 enable_pyglet = inputhook_manager.enable_pyglet
433 enable_pyglet = inputhook_manager.enable_pyglet
402 disable_pyglet = inputhook_manager.disable_pyglet
434 disable_pyglet = inputhook_manager.disable_pyglet
403 clear_inputhook = inputhook_manager.clear_inputhook
435 clear_inputhook = inputhook_manager.clear_inputhook
404 set_inputhook = inputhook_manager.set_inputhook
436 set_inputhook = inputhook_manager.set_inputhook
405 current_gui = inputhook_manager.current_gui
437 current_gui = inputhook_manager.current_gui
406 clear_app_refs = inputhook_manager.clear_app_refs
438 clear_app_refs = inputhook_manager.clear_app_refs
407
439
408
440
409 # Convenience function to switch amongst them
441 # Convenience function to switch amongst them
410 def enable_gui(gui=None, app=None):
442 def enable_gui(gui=None, app=None):
411 """Switch amongst GUI input hooks by name.
443 """Switch amongst GUI input hooks by name.
412
444
413 This is just a utility wrapper around the methods of the InputHookManager
445 This is just a utility wrapper around the methods of the InputHookManager
414 object.
446 object.
415
447
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
424 existing one. If not given, the toolkit will be probed for one, and if
456 existing one. If not given, the toolkit will be probed for one, and if
425 none is found, a new one will be created. Note that GTK does not have
457 none is found, a new one will be created. Note that GTK does not have
426 this concept, and passing an app if `gui`=="GTK" will raise an error.
458 this concept, and passing an app if `gui`=="GTK" will raise an error.
427
459
428 Returns
460 Returns
429 -------
461 -------
430 The output of the underlying gui switch routine, typically the actual
462 The output of the underlying gui switch routine, typically the actual
431 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
463 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
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,
438 GUI_WX: enable_wx,
471 GUI_WX: enable_wx,
439 GUI_QT: enable_qt4, # qt3 not supported
472 GUI_QT: enable_qt4, # qt3 not supported
440 GUI_QT4: enable_qt4,
473 GUI_QT4: enable_qt4,
441 GUI_GLUT: enable_glut,
474 GUI_GLUT: enable_glut,
442 GUI_PYGLET: enable_pyglet,
475 GUI_PYGLET: enable_pyglet,
443 }
476 }
444 try:
477 try:
445 gui_hook = guis[gui]
478 gui_hook = guis[gui]
446 except KeyError:
479 except KeyError:
447 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
480 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
448 raise ValueError(e)
481 raise ValueError(e)
449 return gui_hook(app)
482 return gui_hook(app)
450
483
@@ -1,178 +1,165 b''
1 # encoding: utf-8
1 # encoding: utf-8
2
2
3 """
3 """
4 Enable wxPython to be used interacive by setting PyOS_InputHook.
4 Enable wxPython to be used interacive by setting PyOS_InputHook.
5
5
6 Authors: Robin Dunn, Brian Granger, Ondrej Certik
6 Authors: Robin Dunn, Brian Granger, Ondrej Certik
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2009 The IPython Development Team
10 # Copyright (C) 2008-2009 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import os
20 import os
21 import signal
21 import signal
22 import sys
22 import sys
23 import time
23 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
50 This approach seems to work, but its performance is not great as it
37 This approach seems to work, but its performance is not great as it
51 relies on having PyOS_InputHook called regularly.
38 relies on having PyOS_InputHook called regularly.
52 """
39 """
53 try:
40 try:
54 app = wx.GetApp()
41 app = wx.GetApp()
55 if app is not None:
42 if app is not None:
56 assert wx.Thread_IsMain()
43 assert wx.Thread_IsMain()
57
44
58 # Make a temporary event loop and process system events until
45 # Make a temporary event loop and process system events until
59 # there are no more waiting, then allow idle events (which
46 # there are no more waiting, then allow idle events (which
60 # will also deal with pending or posted wx events.)
47 # will also deal with pending or posted wx events.)
61 evtloop = wx.EventLoop()
48 evtloop = wx.EventLoop()
62 ea = wx.EventLoopActivator(evtloop)
49 ea = wx.EventLoopActivator(evtloop)
63 while evtloop.Pending():
50 while evtloop.Pending():
64 evtloop.Dispatch()
51 evtloop.Dispatch()
65 app.ProcessIdle()
52 app.ProcessIdle()
66 del ea
53 del ea
67 except KeyboardInterrupt:
54 except KeyboardInterrupt:
68 pass
55 pass
69 return 0
56 return 0
70
57
71 class EventLoopTimer(wx.Timer):
58 class EventLoopTimer(wx.Timer):
72
59
73 def __init__(self, func):
60 def __init__(self, func):
74 self.func = func
61 self.func = func
75 wx.Timer.__init__(self)
62 wx.Timer.__init__(self)
76
63
77 def Notify(self):
64 def Notify(self):
78 self.func()
65 self.func()
79
66
80 class EventLoopRunner(object):
67 class EventLoopRunner(object):
81
68
82 def Run(self, time):
69 def Run(self, time):
83 self.evtloop = wx.EventLoop()
70 self.evtloop = wx.EventLoop()
84 self.timer = EventLoopTimer(self.check_stdin)
71 self.timer = EventLoopTimer(self.check_stdin)
85 self.timer.Start(time)
72 self.timer.Start(time)
86 self.evtloop.Run()
73 self.evtloop.Run()
87
74
88 def check_stdin(self):
75 def check_stdin(self):
89 if stdin_ready():
76 if stdin_ready():
90 self.timer.Stop()
77 self.timer.Stop()
91 self.evtloop.Exit()
78 self.evtloop.Exit()
92
79
93 def inputhook_wx2():
80 def inputhook_wx2():
94 """Run the wx event loop, polling for stdin.
81 """Run the wx event loop, polling for stdin.
95
82
96 This version runs the wx eventloop for an undetermined amount of time,
83 This version runs the wx eventloop for an undetermined amount of time,
97 during which it periodically checks to see if anything is ready on
84 during which it periodically checks to see if anything is ready on
98 stdin. If anything is ready on stdin, the event loop exits.
85 stdin. If anything is ready on stdin, the event loop exits.
99
86
100 The argument to elr.Run controls how often the event loop looks at stdin.
87 The argument to elr.Run controls how often the event loop looks at stdin.
101 This determines the responsiveness at the keyboard. A setting of 1000
88 This determines the responsiveness at the keyboard. A setting of 1000
102 enables a user to type at most 1 char per second. I have found that a
89 enables a user to type at most 1 char per second. I have found that a
103 setting of 10 gives good keyboard response. We can shorten it further,
90 setting of 10 gives good keyboard response. We can shorten it further,
104 but eventually performance would suffer from calling select/kbhit too
91 but eventually performance would suffer from calling select/kbhit too
105 often.
92 often.
106 """
93 """
107 try:
94 try:
108 app = wx.GetApp()
95 app = wx.GetApp()
109 if app is not None:
96 if app is not None:
110 assert wx.Thread_IsMain()
97 assert wx.Thread_IsMain()
111 elr = EventLoopRunner()
98 elr = EventLoopRunner()
112 # As this time is made shorter, keyboard response improves, but idle
99 # As this time is made shorter, keyboard response improves, but idle
113 # CPU load goes up. 10 ms seems like a good compromise.
100 # CPU load goes up. 10 ms seems like a good compromise.
114 elr.Run(time=10) # CHANGE time here to control polling interval
101 elr.Run(time=10) # CHANGE time here to control polling interval
115 except KeyboardInterrupt:
102 except KeyboardInterrupt:
116 pass
103 pass
117 return 0
104 return 0
118
105
119 def inputhook_wx3():
106 def inputhook_wx3():
120 """Run the wx event loop by processing pending events only.
107 """Run the wx event loop by processing pending events only.
121
108
122 This is like inputhook_wx1, but it keeps processing pending events
109 This is like inputhook_wx1, but it keeps processing pending events
123 until stdin is ready. After processing all pending events, a call to
110 until stdin is ready. After processing all pending events, a call to
124 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
111 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
125 This sleep time should be tuned though for best performance.
112 This sleep time should be tuned though for best performance.
126 """
113 """
127 # We need to protect against a user pressing Control-C when IPython is
114 # We need to protect against a user pressing Control-C when IPython is
128 # idle and this is running. We trap KeyboardInterrupt and pass.
115 # idle and this is running. We trap KeyboardInterrupt and pass.
129 try:
116 try:
130 app = wx.GetApp()
117 app = wx.GetApp()
131 if app is not None:
118 if app is not None:
132 assert wx.Thread_IsMain()
119 assert wx.Thread_IsMain()
133
120
134 # The import of wx on Linux sets the handler for signal.SIGINT
121 # The import of wx on Linux sets the handler for signal.SIGINT
135 # to 0. This is a bug in wx or gtk. We fix by just setting it
122 # to 0. This is a bug in wx or gtk. We fix by just setting it
136 # back to the Python default.
123 # back to the Python default.
137 if not callable(signal.getsignal(signal.SIGINT)):
124 if not callable(signal.getsignal(signal.SIGINT)):
138 signal.signal(signal.SIGINT, signal.default_int_handler)
125 signal.signal(signal.SIGINT, signal.default_int_handler)
139
126
140 evtloop = wx.EventLoop()
127 evtloop = wx.EventLoop()
141 ea = wx.EventLoopActivator(evtloop)
128 ea = wx.EventLoopActivator(evtloop)
142 t = clock()
129 t = clock()
143 while not stdin_ready():
130 while not stdin_ready():
144 while evtloop.Pending():
131 while evtloop.Pending():
145 t = clock()
132 t = clock()
146 evtloop.Dispatch()
133 evtloop.Dispatch()
147 app.ProcessIdle()
134 app.ProcessIdle()
148 # We need to sleep at this point to keep the idle CPU load
135 # We need to sleep at this point to keep the idle CPU load
149 # low. However, if sleep to long, GUI response is poor. As
136 # low. However, if sleep to long, GUI response is poor. As
150 # a compromise, we watch how often GUI events are being processed
137 # a compromise, we watch how often GUI events are being processed
151 # and switch between a short and long sleep time. Here are some
138 # and switch between a short and long sleep time. Here are some
152 # stats useful in helping to tune this.
139 # stats useful in helping to tune this.
153 # time CPU load
140 # time CPU load
154 # 0.001 13%
141 # 0.001 13%
155 # 0.005 3%
142 # 0.005 3%
156 # 0.01 1.5%
143 # 0.01 1.5%
157 # 0.05 0.5%
144 # 0.05 0.5%
158 used_time = clock() - t
145 used_time = clock() - t
159 if used_time > 5*60.0:
146 if used_time > 5*60.0:
160 # print 'Sleep for 5 s' # dbg
147 # print 'Sleep for 5 s' # dbg
161 time.sleep(5.0)
148 time.sleep(5.0)
162 elif used_time > 10.0:
149 elif used_time > 10.0:
163 # print 'Sleep for 1 s' # dbg
150 # print 'Sleep for 1 s' # dbg
164 time.sleep(1.0)
151 time.sleep(1.0)
165 elif used_time > 0.1:
152 elif used_time > 0.1:
166 # Few GUI events coming in, so we can sleep longer
153 # Few GUI events coming in, so we can sleep longer
167 # print 'Sleep for 0.05 s' # dbg
154 # print 'Sleep for 0.05 s' # dbg
168 time.sleep(0.05)
155 time.sleep(0.05)
169 else:
156 else:
170 # Many GUI events coming in, so sleep only very little
157 # Many GUI events coming in, so sleep only very little
171 time.sleep(0.001)
158 time.sleep(0.001)
172 del ea
159 del ea
173 except KeyboardInterrupt:
160 except KeyboardInterrupt:
174 pass
161 pass
175 return 0
162 return 0
176
163
177 # This is our default implementation
164 # This is our default implementation
178 inputhook_wx = inputhook_wx3
165 inputhook_wx = inputhook_wx3
General Comments 0
You need to be logged in to leave comments. Login now