##// END OF EJS Templates
inputhook: disable CTRL+C when a hook is active....
Christian Boos -
Show More
@@ -1,453 +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 os
19 import sys
19 import sys
20 import warnings
20 import warnings
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Constants
23 # Constants
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 # Constants for identifying the GUI toolkits.
26 # Constants for identifying the GUI toolkits.
27 GUI_WX = 'wx'
27 GUI_WX = 'wx'
28 GUI_QT = 'qt'
28 GUI_QT = 'qt'
29 GUI_QT4 = 'qt4'
29 GUI_QT4 = 'qt4'
30 GUI_GTK = 'gtk'
30 GUI_GTK = 'gtk'
31 GUI_TK = 'tk'
31 GUI_TK = 'tk'
32 GUI_OSX = 'osx'
32 GUI_OSX = 'osx'
33 GUI_GLUT = 'glut'
33 GUI_GLUT = 'glut'
34 GUI_PYGLET = 'pyglet'
34 GUI_PYGLET = 'pyglet'
35 GUI_NONE = 'none' # i.e. disable
35 GUI_NONE = 'none' # i.e. disable
36
36
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38 # Utilities
38 # Utilities
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40
40
41 def _stdin_ready_posix():
41 def _stdin_ready_posix():
42 """Return True if there's something to read on stdin (posix version)."""
42 """Return True if there's something to read on stdin (posix version)."""
43 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
43 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
44 return bool(infds)
44 return bool(infds)
45
45
46 def _stdin_ready_nt():
46 def _stdin_ready_nt():
47 """Return True if there's something to read on stdin (nt version)."""
47 """Return True if there's something to read on stdin (nt version)."""
48 return msvcrt.kbhit()
48 return msvcrt.kbhit()
49
49
50 def _stdin_ready_other():
50 def _stdin_ready_other():
51 """Return True, assuming there's something to read on stdin."""
51 """Return True, assuming there's something to read on stdin."""
52 return True #
52 return True #
53
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
54 if os.name == 'posix':
71 if os.name == 'posix':
55 import select
72 import select
73 import signal
56 stdin_ready = _stdin_ready_posix
74 stdin_ready = _stdin_ready_posix
75 ignore_CTRL_C = _ignore_CTRL_C_posix
76 allow_CTRL_C = _allow_CTRL_C_posix
57 elif os.name == 'nt':
77 elif os.name == 'nt':
58 import msvcrt
78 import msvcrt
59 stdin_ready = _stdin_ready_nt
79 stdin_ready = _stdin_ready_nt
80 ignore_CTRL_C = _ignore_CTRL_C_other
81 allow_CTRL_C = _allow_CTRL_C_other
60 else:
82 else:
61 stdin_ready = _stdin_ready_other
83 stdin_ready = _stdin_ready_other
84 ignore_CTRL_C = _ignore_CTRL_C_other
85 allow_CTRL_C = _allow_CTRL_C_other
62
86
63
87
64 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
65 # Main InputHookManager class
89 # Main InputHookManager class
66 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
67
91
68
92
69 class InputHookManager(object):
93 class InputHookManager(object):
70 """Manage PyOS_InputHook for different GUI toolkits.
94 """Manage PyOS_InputHook for different GUI toolkits.
71
95
72 This class installs various hooks under ``PyOSInputHook`` to handle
96 This class installs various hooks under ``PyOSInputHook`` to handle
73 GUI event loop integration.
97 GUI event loop integration.
74 """
98 """
75
99
76 def __init__(self):
100 def __init__(self):
77 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
101 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
78 self._apps = {}
102 self._apps = {}
79 self._reset()
103 self._reset()
80
104
81 def _reset(self):
105 def _reset(self):
82 self._callback_pyfunctype = None
106 self._callback_pyfunctype = None
83 self._callback = None
107 self._callback = None
84 self._installed = False
108 self._installed = False
85 self._current_gui = None
109 self._current_gui = None
86
110
87 def get_pyos_inputhook(self):
111 def get_pyos_inputhook(self):
88 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
112 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
89 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
113 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
90
114
91 def get_pyos_inputhook_as_func(self):
115 def get_pyos_inputhook_as_func(self):
92 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
116 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
93 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
117 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
94
118
95 def set_inputhook(self, callback):
119 def set_inputhook(self, callback):
96 """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()
97 self._callback = callback
126 self._callback = callback
98 self._callback_pyfunctype = self.PYFUNC(callback)
127 self._callback_pyfunctype = self.PYFUNC(callback)
99 pyos_inputhook_ptr = self.get_pyos_inputhook()
128 pyos_inputhook_ptr = self.get_pyos_inputhook()
100 original = self.get_pyos_inputhook_as_func()
129 original = self.get_pyos_inputhook_as_func()
101 pyos_inputhook_ptr.value = \
130 pyos_inputhook_ptr.value = \
102 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
131 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
103 self._installed = True
132 self._installed = True
104 return original
133 return original
105
134
106 def clear_inputhook(self, app=None):
135 def clear_inputhook(self, app=None):
107 """Set PyOS_InputHook to NULL and return the previous one.
136 """Set PyOS_InputHook to NULL and return the previous one.
108
137
109 Parameters
138 Parameters
110 ----------
139 ----------
111 app : optional, ignored
140 app : optional, ignored
112 This parameter is allowed only so that clear_inputhook() can be
141 This parameter is allowed only so that clear_inputhook() can be
113 called with a similar interface as all the ``enable_*`` methods. But
142 called with a similar interface as all the ``enable_*`` methods. But
114 the actual value of the parameter is ignored. This uniform interface
143 the actual value of the parameter is ignored. This uniform interface
115 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
116 app like :meth:`enable_gui`."""
145 app like :meth:`enable_gui`."""
117 pyos_inputhook_ptr = self.get_pyos_inputhook()
146 pyos_inputhook_ptr = self.get_pyos_inputhook()
118 original = self.get_pyos_inputhook_as_func()
147 original = self.get_pyos_inputhook_as_func()
119 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()
120 self._reset()
150 self._reset()
121 return original
151 return original
122
152
123 def clear_app_refs(self, gui=None):
153 def clear_app_refs(self, gui=None):
124 """Clear IPython's internal reference to an application instance.
154 """Clear IPython's internal reference to an application instance.
125
155
126 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
127 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
128 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
129 method is provided to clear the references we are holding.
159 method is provided to clear the references we are holding.
130
160
131 Parameters
161 Parameters
132 ----------
162 ----------
133 gui : None or str
163 gui : None or str
134 If None, clear all app references. If ('wx', 'qt4') clear
164 If None, clear all app references. If ('wx', 'qt4') clear
135 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
136 as those toolkits don't have the notion of an app.
166 as those toolkits don't have the notion of an app.
137 """
167 """
138 if gui is None:
168 if gui is None:
139 self._apps = {}
169 self._apps = {}
140 elif self._apps.has_key(gui):
170 elif self._apps.has_key(gui):
141 del self._apps[gui]
171 del self._apps[gui]
142
172
143 def enable_wx(self, app=None):
173 def enable_wx(self, app=None):
144 """Enable event loop integration with wxPython.
174 """Enable event loop integration with wxPython.
145
175
146 Parameters
176 Parameters
147 ----------
177 ----------
148 app : WX Application, optional.
178 app : WX Application, optional.
149 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
150 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.
151
181
152 Notes
182 Notes
153 -----
183 -----
154 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
184 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
155 the wxPython to integrate with terminal based applications like
185 the wxPython to integrate with terminal based applications like
156 IPython.
186 IPython.
157
187
158 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
159 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
160 follows::
190 follows::
161
191
162 import wx
192 import wx
163 app = wx.App(redirect=False, clearSigInt=False)
193 app = wx.App(redirect=False, clearSigInt=False)
164 """
194 """
165 from IPython.lib.inputhookwx import inputhook_wx
195 from IPython.lib.inputhookwx import inputhook_wx
166 self.set_inputhook(inputhook_wx)
196 self.set_inputhook(inputhook_wx)
167 self._current_gui = GUI_WX
197 self._current_gui = GUI_WX
168 import wx
198 import wx
169 if app is None:
199 if app is None:
170 app = wx.GetApp()
200 app = wx.GetApp()
171 if app is None:
201 if app is None:
172 app = wx.App(redirect=False, clearSigInt=False)
202 app = wx.App(redirect=False, clearSigInt=False)
173 app._in_event_loop = True
203 app._in_event_loop = True
174 self._apps[GUI_WX] = app
204 self._apps[GUI_WX] = app
175 return app
205 return app
176
206
177 def disable_wx(self):
207 def disable_wx(self):
178 """Disable event loop integration with wxPython.
208 """Disable event loop integration with wxPython.
179
209
180 This merely sets PyOS_InputHook to NULL.
210 This merely sets PyOS_InputHook to NULL.
181 """
211 """
182 if self._apps.has_key(GUI_WX):
212 if self._apps.has_key(GUI_WX):
183 self._apps[GUI_WX]._in_event_loop = False
213 self._apps[GUI_WX]._in_event_loop = False
184 self.clear_inputhook()
214 self.clear_inputhook()
185
215
186 def enable_qt4(self, app=None):
216 def enable_qt4(self, app=None):
187 """Enable event loop integration with PyQt4.
217 """Enable event loop integration with PyQt4.
188
218
189 Parameters
219 Parameters
190 ----------
220 ----------
191 app : Qt Application, optional.
221 app : Qt Application, optional.
192 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
193 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.
194
224
195 Notes
225 Notes
196 -----
226 -----
197 This methods sets the PyOS_InputHook for PyQt4, which allows
227 This methods sets the PyOS_InputHook for PyQt4, which allows
198 the PyQt4 to integrate with terminal based applications like
228 the PyQt4 to integrate with terminal based applications like
199 IPython.
229 IPython.
200
230
201 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
202 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`
203 as follows::
233 as follows::
204
234
205 from PyQt4 import QtCore
235 from PyQt4 import QtCore
206 app = QtGui.QApplication(sys.argv)
236 app = QtGui.QApplication(sys.argv)
207 """
237 """
208 from IPython.lib.inputhookqt4 import create_inputhook_qt4
238 from IPython.lib.inputhookqt4 import create_inputhook_qt4
209 app, inputhook_qt4 = create_inputhook_qt4(self, app)
239 app, inputhook_qt4 = create_inputhook_qt4(self, app)
210 self.set_inputhook(inputhook_qt4)
240 self.set_inputhook(inputhook_qt4)
211
241
212 self._current_gui = GUI_QT4
242 self._current_gui = GUI_QT4
213 app._in_event_loop = True
243 app._in_event_loop = True
214 self._apps[GUI_QT4] = app
244 self._apps[GUI_QT4] = app
215 return app
245 return app
216
246
217 def disable_qt4(self):
247 def disable_qt4(self):
218 """Disable event loop integration with PyQt4.
248 """Disable event loop integration with PyQt4.
219
249
220 This merely sets PyOS_InputHook to NULL.
250 This merely sets PyOS_InputHook to NULL.
221 """
251 """
222 if self._apps.has_key(GUI_QT4):
252 if self._apps.has_key(GUI_QT4):
223 self._apps[GUI_QT4]._in_event_loop = False
253 self._apps[GUI_QT4]._in_event_loop = False
224 self.clear_inputhook()
254 self.clear_inputhook()
225
255
226 def enable_gtk(self, app=None):
256 def enable_gtk(self, app=None):
227 """Enable event loop integration with PyGTK.
257 """Enable event loop integration with PyGTK.
228
258
229 Parameters
259 Parameters
230 ----------
260 ----------
231 app : ignored
261 app : ignored
232 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
233 gui activation methods consistent, which simplifies the logic of
263 gui activation methods consistent, which simplifies the logic of
234 supporting magics.
264 supporting magics.
235
265
236 Notes
266 Notes
237 -----
267 -----
238 This methods sets the PyOS_InputHook for PyGTK, which allows
268 This methods sets the PyOS_InputHook for PyGTK, which allows
239 the PyGTK to integrate with terminal based applications like
269 the PyGTK to integrate with terminal based applications like
240 IPython.
270 IPython.
241 """
271 """
242 import gtk
272 import gtk
243 try:
273 try:
244 gtk.set_interactive(True)
274 gtk.set_interactive(True)
245 self._current_gui = GUI_GTK
275 self._current_gui = GUI_GTK
246 except AttributeError:
276 except AttributeError:
247 # For older versions of gtk, use our own ctypes version
277 # For older versions of gtk, use our own ctypes version
248 from IPython.lib.inputhookgtk import inputhook_gtk
278 from IPython.lib.inputhookgtk import inputhook_gtk
249 self.set_inputhook(inputhook_gtk)
279 self.set_inputhook(inputhook_gtk)
250 self._current_gui = GUI_GTK
280 self._current_gui = GUI_GTK
251
281
252 def disable_gtk(self):
282 def disable_gtk(self):
253 """Disable event loop integration with PyGTK.
283 """Disable event loop integration with PyGTK.
254
284
255 This merely sets PyOS_InputHook to NULL.
285 This merely sets PyOS_InputHook to NULL.
256 """
286 """
257 self.clear_inputhook()
287 self.clear_inputhook()
258
288
259 def enable_tk(self, app=None):
289 def enable_tk(self, app=None):
260 """Enable event loop integration with Tk.
290 """Enable event loop integration with Tk.
261
291
262 Parameters
292 Parameters
263 ----------
293 ----------
264 app : toplevel :class:`Tkinter.Tk` widget, optional.
294 app : toplevel :class:`Tkinter.Tk` widget, optional.
265 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
266 existing one, and create a new one if none is found.
296 existing one, and create a new one if none is found.
267
297
268 Notes
298 Notes
269 -----
299 -----
270 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
271 thing done by this method is to register with the
301 thing done by this method is to register with the
272 :class:`InputHookManager`, since creating that object automatically
302 :class:`InputHookManager`, since creating that object automatically
273 sets ``PyOS_InputHook``.
303 sets ``PyOS_InputHook``.
274 """
304 """
275 self._current_gui = GUI_TK
305 self._current_gui = GUI_TK
276 if app is None:
306 if app is None:
277 import Tkinter
307 import Tkinter
278 app = Tkinter.Tk()
308 app = Tkinter.Tk()
279 app.withdraw()
309 app.withdraw()
280 self._apps[GUI_TK] = app
310 self._apps[GUI_TK] = app
281 return app
311 return app
282
312
283 def disable_tk(self):
313 def disable_tk(self):
284 """Disable event loop integration with Tkinter.
314 """Disable event loop integration with Tkinter.
285
315
286 This merely sets PyOS_InputHook to NULL.
316 This merely sets PyOS_InputHook to NULL.
287 """
317 """
288 self.clear_inputhook()
318 self.clear_inputhook()
289
319
290
320
291 def enable_glut(self, app=None):
321 def enable_glut(self, app=None):
292 """ Enable event loop integration with GLUT.
322 """ Enable event loop integration with GLUT.
293
323
294 Parameters
324 Parameters
295 ----------
325 ----------
296
326
297 app : ignored
327 app : ignored
298 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
299 gui activation methods consistent, which simplifies the logic of
329 gui activation methods consistent, which simplifies the logic of
300 supporting magics.
330 supporting magics.
301
331
302 Notes
332 Notes
303 -----
333 -----
304
334
305 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
306 integrate with terminal based applications like IPython. Due to GLUT
336 integrate with terminal based applications like IPython. Due to GLUT
307 limitations, it is currently not possible to start the event loop
337 limitations, it is currently not possible to start the event loop
308 without first creating a window. You should thus not create another
338 without first creating a window. You should thus not create another
309 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
310 docs/examples/lib directory.
340 docs/examples/lib directory.
311
341
312 The default screen mode is set to:
342 The default screen mode is set to:
313 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
343 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
314 """
344 """
315
345
316 import OpenGL.GLUT as glut
346 import OpenGL.GLUT as glut
317 from IPython.lib.inputhookglut import glut_display_mode, \
347 from IPython.lib.inputhookglut import glut_display_mode, \
318 glut_close, glut_display, \
348 glut_close, glut_display, \
319 glut_idle, inputhook_glut
349 glut_idle, inputhook_glut
320
350
321 if not self._apps.has_key( GUI_GLUT ):
351 if not self._apps.has_key( GUI_GLUT ):
322 glut.glutInit( sys.argv )
352 glut.glutInit( sys.argv )
323 glut.glutInitDisplayMode( glut_display_mode )
353 glut.glutInitDisplayMode( glut_display_mode )
324 # This is specific to freeglut
354 # This is specific to freeglut
325 if bool(glut.glutSetOption):
355 if bool(glut.glutSetOption):
326 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
356 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
327 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
357 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
328 glut.glutCreateWindow( sys.argv[0] )
358 glut.glutCreateWindow( sys.argv[0] )
329 glut.glutReshapeWindow( 1, 1 )
359 glut.glutReshapeWindow( 1, 1 )
330 glut.glutHideWindow( )
360 glut.glutHideWindow( )
331 glut.glutWMCloseFunc( glut_close )
361 glut.glutWMCloseFunc( glut_close )
332 glut.glutDisplayFunc( glut_display )
362 glut.glutDisplayFunc( glut_display )
333 glut.glutIdleFunc( glut_idle )
363 glut.glutIdleFunc( glut_idle )
334 else:
364 else:
335 glut.glutWMCloseFunc( glut_close )
365 glut.glutWMCloseFunc( glut_close )
336 glut.glutDisplayFunc( glut_display )
366 glut.glutDisplayFunc( glut_display )
337 glut.glutIdleFunc( glut_idle)
367 glut.glutIdleFunc( glut_idle)
338 self.set_inputhook( inputhook_glut )
368 self.set_inputhook( inputhook_glut )
339 self._current_gui = GUI_GLUT
369 self._current_gui = GUI_GLUT
340 self._apps[GUI_GLUT] = True
370 self._apps[GUI_GLUT] = True
341
371
342
372
343 def disable_glut(self):
373 def disable_glut(self):
344 """Disable event loop integration with glut.
374 """Disable event loop integration with glut.
345
375
346 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
347 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
348 very far in the future.
378 very far in the future.
349 """
379 """
350 import OpenGL.GLUT as glut
380 import OpenGL.GLUT as glut
351 from glut_support import glutMainLoopEvent
381 from glut_support import glutMainLoopEvent
352
382
353 glut.glutHideWindow() # This is an event to be processed below
383 glut.glutHideWindow() # This is an event to be processed below
354 glutMainLoopEvent()
384 glutMainLoopEvent()
355 self.clear_inputhook()
385 self.clear_inputhook()
356
386
357 def enable_pyglet(self, app=None):
387 def enable_pyglet(self, app=None):
358 """Enable event loop integration with pyglet.
388 """Enable event loop integration with pyglet.
359
389
360 Parameters
390 Parameters
361 ----------
391 ----------
362 app : ignored
392 app : ignored
363 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
364 gui activation methods consistent, which simplifies the logic of
394 gui activation methods consistent, which simplifies the logic of
365 supporting magics.
395 supporting magics.
366
396
367 Notes
397 Notes
368 -----
398 -----
369 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
399 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
370 pyglet to integrate with terminal based applications like
400 pyglet to integrate with terminal based applications like
371 IPython.
401 IPython.
372
402
373 """
403 """
374 import pyglet
404 import pyglet
375 from IPython.lib.inputhookpyglet import inputhook_pyglet
405 from IPython.lib.inputhookpyglet import inputhook_pyglet
376 self.set_inputhook(inputhook_pyglet)
406 self.set_inputhook(inputhook_pyglet)
377 self._current_gui = GUI_PYGLET
407 self._current_gui = GUI_PYGLET
378 return app
408 return app
379
409
380 def disable_pyglet(self):
410 def disable_pyglet(self):
381 """Disable event loop integration with pyglet.
411 """Disable event loop integration with pyglet.
382
412
383 This merely sets PyOS_InputHook to NULL.
413 This merely sets PyOS_InputHook to NULL.
384 """
414 """
385 self.clear_inputhook()
415 self.clear_inputhook()
386
416
387 def current_gui(self):
417 def current_gui(self):
388 """Return a string indicating the currently active GUI or None."""
418 """Return a string indicating the currently active GUI or None."""
389 return self._current_gui
419 return self._current_gui
390
420
391 inputhook_manager = InputHookManager()
421 inputhook_manager = InputHookManager()
392
422
393 enable_wx = inputhook_manager.enable_wx
423 enable_wx = inputhook_manager.enable_wx
394 disable_wx = inputhook_manager.disable_wx
424 disable_wx = inputhook_manager.disable_wx
395 enable_qt4 = inputhook_manager.enable_qt4
425 enable_qt4 = inputhook_manager.enable_qt4
396 disable_qt4 = inputhook_manager.disable_qt4
426 disable_qt4 = inputhook_manager.disable_qt4
397 enable_gtk = inputhook_manager.enable_gtk
427 enable_gtk = inputhook_manager.enable_gtk
398 disable_gtk = inputhook_manager.disable_gtk
428 disable_gtk = inputhook_manager.disable_gtk
399 enable_tk = inputhook_manager.enable_tk
429 enable_tk = inputhook_manager.enable_tk
400 disable_tk = inputhook_manager.disable_tk
430 disable_tk = inputhook_manager.disable_tk
401 enable_glut = inputhook_manager.enable_glut
431 enable_glut = inputhook_manager.enable_glut
402 disable_glut = inputhook_manager.disable_glut
432 disable_glut = inputhook_manager.disable_glut
403 enable_pyglet = inputhook_manager.enable_pyglet
433 enable_pyglet = inputhook_manager.enable_pyglet
404 disable_pyglet = inputhook_manager.disable_pyglet
434 disable_pyglet = inputhook_manager.disable_pyglet
405 clear_inputhook = inputhook_manager.clear_inputhook
435 clear_inputhook = inputhook_manager.clear_inputhook
406 set_inputhook = inputhook_manager.set_inputhook
436 set_inputhook = inputhook_manager.set_inputhook
407 current_gui = inputhook_manager.current_gui
437 current_gui = inputhook_manager.current_gui
408 clear_app_refs = inputhook_manager.clear_app_refs
438 clear_app_refs = inputhook_manager.clear_app_refs
409
439
410
440
411 # Convenience function to switch amongst them
441 # Convenience function to switch amongst them
412 def enable_gui(gui=None, app=None):
442 def enable_gui(gui=None, app=None):
413 """Switch amongst GUI input hooks by name.
443 """Switch amongst GUI input hooks by name.
414
444
415 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
416 object.
446 object.
417
447
418 Parameters
448 Parameters
419 ----------
449 ----------
420 gui : optional, string or None
450 gui : optional, string or None
421 If None (or 'none'), clears input hook, otherwise it must be one
451 If None (or 'none'), clears input hook, otherwise it must be one
422 of the recognized GUI names (see ``GUI_*`` constants in module).
452 of the recognized GUI names (see ``GUI_*`` constants in module).
423
453
424 app : optional, existing application object.
454 app : optional, existing application object.
425 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
426 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
427 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
428 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.
429
459
430 Returns
460 Returns
431 -------
461 -------
432 The output of the underlying gui switch routine, typically the actual
462 The output of the underlying gui switch routine, typically the actual
433 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
434 one.
464 one.
435 """
465 """
436 guis = {None: clear_inputhook,
466 guis = {None: clear_inputhook,
437 GUI_NONE: clear_inputhook,
467 GUI_NONE: clear_inputhook,
438 GUI_OSX: lambda app=False: None,
468 GUI_OSX: lambda app=False: None,
439 GUI_TK: enable_tk,
469 GUI_TK: enable_tk,
440 GUI_GTK: enable_gtk,
470 GUI_GTK: enable_gtk,
441 GUI_WX: enable_wx,
471 GUI_WX: enable_wx,
442 GUI_QT: enable_qt4, # qt3 not supported
472 GUI_QT: enable_qt4, # qt3 not supported
443 GUI_QT4: enable_qt4,
473 GUI_QT4: enable_qt4,
444 GUI_GLUT: enable_glut,
474 GUI_GLUT: enable_glut,
445 GUI_PYGLET: enable_pyglet,
475 GUI_PYGLET: enable_pyglet,
446 }
476 }
447 try:
477 try:
448 gui_hook = guis[gui]
478 gui_hook = guis[gui]
449 except KeyError:
479 except KeyError:
450 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())
451 raise ValueError(e)
481 raise ValueError(e)
452 return gui_hook(app)
482 return gui_hook(app)
453
483
@@ -1,113 +1,115 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 from IPython.external.qt_for_kernel import QtCore, QtGui
19 from IPython.external.qt_for_kernel import QtCore, QtGui
20 from IPython.lib.inputhook import stdin_ready
20 from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Code
23 # Code
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 def create_inputhook_qt4(mgr, app=None):
26 def create_inputhook_qt4(mgr, app=None):
27 """Create an input hook for running the Qt4 application event loop.
27 """Create an input hook for running the Qt4 application event loop.
28
28
29 Parameters
29 Parameters
30 ----------
30 ----------
31 mgr : an InputHookManager
31 mgr : an InputHookManager
32
32
33 app : Qt Application, optional.
33 app : Qt Application, optional.
34 Running application to use. If not given, we probe Qt for an
34 Running application to use. If not given, we probe Qt for an
35 existing application object, and create a new one if none is found.
35 existing application object, and create a new one if none is found.
36
36
37 Returns
37 Returns
38 -------
38 -------
39 A pair consisting of a Qt Application (either the one given or the
39 A pair consisting of a Qt Application (either the one given or the
40 one found or created) and a inputhook.
40 one found or created) and a inputhook.
41
41
42 Notes
42 Notes
43 -----
43 -----
44 We use a custom input hook instead of PyQt4's default one, as it
44 We use a custom input hook instead of PyQt4's default one, as it
45 interacts better with the readline packages (issue #481).
45 interacts better with the readline packages (issue #481).
46
46
47 The inputhook function works in tandem with a 'pre_prompt_hook'
47 The inputhook function works in tandem with a 'pre_prompt_hook'
48 which automatically restores the hook as an inputhook in case the
48 which automatically restores the hook as an inputhook in case the
49 latter has been temporarily disabled after having intercepted a
49 latter has been temporarily disabled after having intercepted a
50 KeyboardInterrupt.
50 KeyboardInterrupt.
51 """
51 """
52
52
53 if app is None:
53 if app is None:
54 app = QtCore.QCoreApplication.instance()
54 app = QtCore.QCoreApplication.instance()
55 if app is None:
55 if app is None:
56 app = QtGui.QApplication([" "])
56 app = QtGui.QApplication([" "])
57
57
58 # Re-use previously created inputhook if any
58 # Re-use previously created inputhook if any
59 ip = get_ipython()
59 ip = get_ipython()
60 if hasattr(ip, '_inputhook_qt4'):
60 if hasattr(ip, '_inputhook_qt4'):
61 return app, ip._inputhook_qt4
61 return app, ip._inputhook_qt4
62
62
63 # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of
63 # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of
64 # hooks (they both share the got_kbdint flag)
64 # hooks (they both share the got_kbdint flag)
65
65
66 got_kbdint = [False]
66 got_kbdint = [False]
67
67
68 def inputhook_qt4():
68 def inputhook_qt4():
69 """PyOS_InputHook python hook for Qt4.
69 """PyOS_InputHook python hook for Qt4.
70
70
71 Process pending Qt events and if there's no pending keyboard
71 Process pending Qt events and if there's no pending keyboard
72 input, spend a short slice of time (50ms) running the Qt event
72 input, spend a short slice of time (50ms) running the Qt event
73 loop.
73 loop.
74
74
75 As a Python ctypes callback can't raise an exception, we catch
75 As a Python ctypes callback can't raise an exception, we catch
76 the KeyboardInterrupt and temporarily deactivate the hook,
76 the KeyboardInterrupt and temporarily deactivate the hook,
77 which will let a *second* CTRL+C be processed normally and go
77 which will let a *second* CTRL+C be processed normally and go
78 back to a clean prompt line.
78 back to a clean prompt line.
79 """
79 """
80 try:
80 try:
81 allow_CTRL_C()
81 app = QtCore.QCoreApplication.instance()
82 app = QtCore.QCoreApplication.instance()
82 app.processEvents(QtCore.QEventLoop.AllEvents, 300)
83 app.processEvents(QtCore.QEventLoop.AllEvents, 300)
83 if not stdin_ready():
84 if not stdin_ready():
84 timer = QtCore.QTimer()
85 timer = QtCore.QTimer()
85 timer.timeout.connect(app.quit)
86 timer.timeout.connect(app.quit)
86 while not stdin_ready():
87 while not stdin_ready():
87 timer.start(50)
88 timer.start(50)
88 app.exec_()
89 app.exec_()
89 timer.stop()
90 timer.stop()
90 except KeyboardInterrupt:
91 except KeyboardInterrupt:
92 ignore_CTRL_C()
91 got_kbdint[0] = True
93 got_kbdint[0] = True
92 mgr.clear_inputhook()
93 print("\nKeyboardInterrupt - qt4 event loop interrupted!"
94 print("\nKeyboardInterrupt - qt4 event loop interrupted!"
94 "\n * hit CTRL+C again to clear the prompt"
95 "\n * hit CTRL+C again to clear the prompt"
95 "\n * use '%gui none' to disable the event loop"
96 "\n * use '%gui none' to disable the event loop"
96 " permanently"
97 " permanently"
97 "\n and '%gui qt4' to re-enable it later")
98 "\n and '%gui qt4' to re-enable it later")
99 mgr.clear_inputhook()
98 return 0
100 return 0
99
101
100 def preprompthook_qt4(ishell):
102 def preprompthook_qt4(ishell):
101 """'pre_prompt_hook' used to restore the Qt4 input hook
103 """'pre_prompt_hook' used to restore the Qt4 input hook
102
104
103 (in case the latter was temporarily deactivated after a
105 (in case the latter was temporarily deactivated after a
104 CTRL+C)
106 CTRL+C)
105 """
107 """
106 if got_kbdint[0]:
108 if got_kbdint[0]:
107 mgr.set_inputhook(inputhook_qt4)
109 mgr.set_inputhook(inputhook_qt4)
108 got_kbdint[0] = False
110 got_kbdint[0] = False
109
111
110 ip._inputhook_qt4 = inputhook_qt4
112 ip._inputhook_qt4 = inputhook_qt4
111 ip.set_hook('pre_prompt_hook', preprompthook_qt4)
113 ip.set_hook('pre_prompt_hook', preprompthook_qt4)
112
114
113 return app, inputhook_qt4
115 return app, inputhook_qt4
General Comments 0
You need to be logged in to leave comments. Login now