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