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