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