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