diff --git a/IPython/frontend/terminal/ipapp.py b/IPython/frontend/terminal/ipapp.py index 1840f9682fc..f496e2f64fc 100755 --- a/IPython/frontend/terminal/ipapp.py +++ b/IPython/frontend/terminal/ipapp.py @@ -229,8 +229,8 @@ def _quick_changed(self, name, old, new): self.load_config_file = lambda *a, **kw: None self.ignore_old_config=True - gui = CaselessStrEnum(('qt','wx','gtk'), config=True, - help="Enable GUI event loop integration ('qt', 'wx', 'gtk')." + gui = CaselessStrEnum(('qt','wx','gtk', 'pyglet'), config=True, + help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'pyglet')." ) pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'], config=True, diff --git a/IPython/lib/__init__.py b/IPython/lib/__init__.py index b5c9e53963c..c390e2e95d6 100644 --- a/IPython/lib/__init__.py +++ b/IPython/lib/__init__.py @@ -19,6 +19,7 @@ enable_gtk, disable_gtk, enable_qt4, disable_qt4, enable_tk, disable_tk, + enable_pyglet, disable_pyglet, set_inputhook, clear_inputhook, current_gui ) diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py index 9c1ea66aa43..fb697c0099c 100755 --- a/IPython/lib/inputhook.py +++ b/IPython/lib/inputhook.py @@ -29,6 +29,7 @@ GUI_GTK = 'gtk' GUI_TK = 'tk' GUI_OSX = 'osx' +GUI_PYGLET = 'pyglet' #----------------------------------------------------------------------------- # Utility classes @@ -283,6 +284,39 @@ def disable_tk(self): """ self.clear_inputhook() + + + def enable_pyglet(self, app=None): + """Enable event loop integration with pyglet. + + Parameters + ---------- + app : ignored + Ignored, it's only a placeholder to keep the call signature of all + gui activation methods consistent, which simplifies the logic of + supporting magics. + + Notes + ----- + This methods sets the ``PyOS_InputHook`` for pyglet, which allows + pyglet to integrate with terminal based applications like + IPython. + + """ + import pyglet + from IPython.lib.inputhookpyglet import inputhook_pyglet + self.set_inputhook(inputhook_pyglet) + self._current_gui = GUI_PYGLET + return app + + def disable_pyglet(self): + """Disable event loop integration with pyglet. + + This merely sets PyOS_InputHook to NULL. + """ + self.clear_inputhook() + + def current_gui(self): """Return a string indicating the currently active GUI or None.""" return self._current_gui @@ -297,6 +331,8 @@ def current_gui(self): disable_gtk = inputhook_manager.disable_gtk enable_tk = inputhook_manager.enable_tk disable_tk = inputhook_manager.disable_tk +enable_pyglet = inputhook_manager.enable_pyglet +disable_pyglet = inputhook_manager.disable_pyglet clear_inputhook = inputhook_manager.clear_inputhook set_inputhook = inputhook_manager.set_inputhook current_gui = inputhook_manager.current_gui @@ -334,7 +370,9 @@ def enable_gui(gui=None, app=None): GUI_GTK: enable_gtk, GUI_WX: enable_wx, GUI_QT: enable_qt4, # qt3 not supported - GUI_QT4: enable_qt4 } + GUI_QT4: enable_qt4, + GUI_PYGLET: enable_pyglet, + } try: gui_hook = guis[gui] except KeyError: diff --git a/IPython/lib/inputhookpyglet.py b/IPython/lib/inputhookpyglet.py new file mode 100644 index 00000000000..45fbc0375e3 --- /dev/null +++ b/IPython/lib/inputhookpyglet.py @@ -0,0 +1,115 @@ +# encoding: utf-8 +""" +Enable pyglet to be used interacive by setting PyOS_InputHook. + +Authors +------- + +* Nicolas P. Rougier +* Fernando Perez +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import signal +import sys +import time +from timeit import default_timer as clock +import pyglet + +#----------------------------------------------------------------------------- +# Platform-dependent imports and functions +#----------------------------------------------------------------------------- + +if os.name == 'posix': + import select + + def stdin_ready(): + infds, outfds, erfds = select.select([sys.stdin],[],[],0) + if infds: + return True + else: + return False + +elif sys.platform == 'win32': + import msvcrt + + def stdin_ready(): + return msvcrt.kbhit() + + +# On linux only, window.flip() has a bug that causes an AttributeError on +# window close. For details, see: +# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e + +if sys.platform.startswith('linux'): + def flip(window): + try: + window.flip() + except AttributeError: + pass +else: + def flip(window): + window.flip() + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def inputhook_pyglet(): + """Run the pyglet event loop by processing pending events only. + + This keeps processing pending events until stdin is ready. After + processing all pending events, a call to time.sleep is inserted. This is + needed, otherwise, CPU usage is at 100%. This sleep time should be tuned + though for best performance. + """ + # We need to protect against a user pressing Control-C when IPython is + # idle and this is running. We trap KeyboardInterrupt and pass. + try: + t = clock() + while not stdin_ready(): + pyglet.clock.tick() + for window in pyglet.app.windows: + window.switch_to() + window.dispatch_events() + window.dispatch_event('on_draw') + flip(window) + + # We need to sleep at this point to keep the idle CPU load + # low. However, if sleep to long, GUI response is poor. As + # a compromise, we watch how often GUI events are being processed + # and switch between a short and long sleep time. Here are some + # stats useful in helping to tune this. + # time CPU load + # 0.001 13% + # 0.005 3% + # 0.01 1.5% + # 0.05 0.5% + used_time = clock() - t + if used_time > 5*60.0: + # print 'Sleep for 5 s' # dbg + time.sleep(5.0) + elif used_time > 10.0: + # print 'Sleep for 1 s' # dbg + time.sleep(1.0) + elif used_time > 0.1: + # Few GUI events coming in, so we can sleep longer + # print 'Sleep for 0.05 s' # dbg + time.sleep(0.05) + else: + # Many GUI events coming in, so sleep only very little + time.sleep(0.001) + except KeyboardInterrupt: + pass + return 0 diff --git a/docs/examples/lib/gui-pyglet.py b/docs/examples/lib/gui-pyglet.py new file mode 100644 index 00000000000..3bdb699b638 --- /dev/null +++ b/docs/examples/lib/gui-pyglet.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +"""Simple pyglet example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [5]: %gui pyglet + +In [6]: %run gui-pyglet.py +""" + +import pyglet + + +window = pyglet.window.Window() +label = pyglet.text.Label('Hello, world', + font_name='Times New Roman', + font_size=36, + x=window.width//2, y=window.height//2, + anchor_x='center', anchor_y='center') +@window.event +def on_close(): + window.close() + +@window.event +def on_draw(): + window.clear() + label.draw() + +try: + from IPython.lib.inputhook import enable_pyglet + enable_pyglet() +except ImportError: + pyglet.app.run()