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