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