diff --git a/IPython/frontend/terminal/ipapp.py b/IPython/frontend/terminal/ipapp.py index f496e2f..fac3486 100755 --- a/IPython/frontend/terminal/ipapp.py +++ b/IPython/frontend/terminal/ipapp.py @@ -229,8 +229,8 @@ class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): self.load_config_file = lambda *a, **kw: None self.ignore_old_config=True - gui = CaselessStrEnum(('qt','wx','gtk', 'pyglet'), config=True, - help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'pyglet')." + gui = CaselessStrEnum(('qt', 'wx', 'gtk', 'glut', 'pyglet'), config=True, + help="Enable GUI event loop integration ('qt', 'wx', 'gtk', 'glut', 'pyglet')." ) pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'], config=True, diff --git a/IPython/lib/__init__.py b/IPython/lib/__init__.py index c390e2e..65b0ae3 100644 --- a/IPython/lib/__init__.py +++ b/IPython/lib/__init__.py @@ -19,6 +19,7 @@ from IPython.lib.inputhook import ( enable_gtk, disable_gtk, enable_qt4, disable_qt4, enable_tk, disable_tk, + enable_glut, disable_glut, enable_pyglet, disable_pyglet, set_inputhook, clear_inputhook, current_gui diff --git a/IPython/lib/inputhook.py b/IPython/lib/inputhook.py index fb697c0..8bf3311 100755 --- a/IPython/lib/inputhook.py +++ b/IPython/lib/inputhook.py @@ -29,6 +29,7 @@ GUI_QT4 = 'qt4' GUI_GTK = 'gtk' GUI_TK = 'tk' GUI_OSX = 'osx' +GUI_GLUT = 'glut' GUI_PYGLET = 'pyglet' #----------------------------------------------------------------------------- @@ -284,7 +285,207 @@ class InputHookManager(object): """ self.clear_inputhook() + def enable_glut(self, app=None): + """Enable event loop integration with GLUT. + 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 GLUT, which allows the GLUT to + integrate with terminal based applications like IPython. Due to GLUT + limitations, it is currently not possible to start the event loop + without first creating a window. You should thus not create another + window but use instead the created one. See 'gui-glut.py' in the + docs/examples/lib directory. + + The default screen mode is set to: + + glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH + + Script integration + ------------------ + + if glut.glutGetWindow() > 0: + interactive = True + glut.glutShowWindow() + else: + interactive = False + glut.glutInit(sys.argv) + glut.glutInitDisplayMode( glut.GLUT_DOUBLE | + glut.GLUT_RGBA | + glut.GLUT_DEPTH ) + ... + if not interactive: + glut.glutMainLoop() + """ + # GLUT is quite an old library and it is difficult to ensure proper + # integration within IPython since original GLUT does not allow to handle + # events one by one. Instead, it requires for the mainloop to be entered + # and never returned (there is not even a function to exit he + # mainloop). Fortunately, there are alternatives such as freeglut + # (available for linux and windows) and the OSX implementation gives + # access to a glutCheckLoop() function that blocks itself until a new + # event is received. This means we have to setup a default timer to + # ensure we got at least one event that will unblock the function. We set + # a default timer of 60fps. + # + # Furthermore, it is not possible to install these handlers without a + # window being first created. We choose to make this window invisible and + # the user is supposed to make it visible when needed (see gui-glut.py in + # the docs/examples/lib directory). This means that display mode options + # are set at this level and user won't be able to change them later + # without modifying the code. This should probably be made available via + # IPython options system. + + import OpenGL + OpenGL.ERROR_CHECKING = False + import OpenGL.GLUT as glut + import OpenGL.platform as platform + import time + + + # Frame per second : 60 + # Should probably be an IPython option + glut_fps = 60 + + + # Display mode : double buffeed + rgba + depth + # Should probably be an IPython option + glut_display_mode = (glut.GLUT_DOUBLE | + glut.GLUT_RGBA | + glut.GLUT_DEPTH) + + glut_interrupted = False + + def display(): + ''' Dummy display function ''' + pass + + def timer(fps): + # We should normally set the active window to 1 and post a + # redisplay for each window. The problem is that we do not know + # how much active windows we have and there is no function in glut + # to get that number. + # glut.glutSetWindow(1) + glut.glutTimerFunc( int(1000.0/fps), timer, fps) + glut.glutPostRedisplay() + + def close(): + glut.glutHideWindow() + + glutMainLoopEvent = None + if sys.platform == 'darwin': + try: + glutCheckLoop = platform.createBaseFunction( + 'glutCheckLoop', dll=platform.GLUT, resultType=None, + argTypes=[], + doc='glutCheckLoop( ) -> None', + argNames=(), + ) + except AttributeError: + raise RuntimeError( + '''Your glut implementation does not allow interactive sessions''' + '''Consider installing freeglut.''') + glutMainLoopEvent = glutCheckLoop + elif glut.HAVE_FREEGLUT: + glutMainLoopEvent = glut.glutMainLoopEvent + else: + raise RuntimeError( + '''Your glut implementation does not allow interactive sessions. ''' + '''Consider installing freeglut.''') + + def inputhook_glut(): + """ Process pending GLUT events only. """ + + # We need to protect against a user pressing Control-C when IPython + # is idle and this is running. We should trap KeyboardInterrupt and + # pass but it does not seem to work with glutMainLoopEvent. + # Instead, we setup a signal handler on SIGINT and returns after + # having restored the default python SIGINT handler. + import signal + def handler(signum, frame): + signal.signal(signal.SIGINT, signal.default_int_handler) + print '\nKeyboardInterrupt' + # Need to reprint the prompt at this stage + + signal.signal(signal.SIGINT, handler) + + try: + glutMainLoopEvent() + except KeyboardInterrupt: # this catch doesn't work for some reasons... + pass + + return 0 + + if not self._apps.has_key(GUI_GLUT): + glut.glutInit(sys.argv) + # Display mode should be also an Ipython option since user won't be able + # to change it later + glut.glutInitDisplayMode(glut_display_mode) + glut.glutCreateWindow(sys.argv[0]) + glut.glutHideWindow() + glut.glutWMCloseFunc(close) + glut.glutDisplayFunc(display) + glut.glutTimerFunc( int(1000.0/glut_fps), timer, glut_fps) + else: + glut.glutWMCloseFunc(close) + glut.glutDisplayFunc(display) + glut.glutTimerFunc( int(1000.0/glut_fps), timer, glut_fps) + + self.set_inputhook(inputhook_glut) + self._current_gui = GUI_GLUT + self._apps[GUI_GLUT] = True + + def disable_glut(self): + """Disable event loop integration with glut. + + This sets PyOS_InputHook to NULL and set the display function to a + dummy one and set the timer to a dummy timer that will be triggered + very far in the future. + """ + import signal + import OpenGL + OpenGL.ERROR_CHECKING = False + import OpenGL.GLUT as glut + import OpenGL.platform as platform + + def timer_none(fps): + ''' Dummy timer function ''' + pass + + glutMainLoopEvent = None + if sys.platform == 'darwin': + try: + glutCheckLoop = platform.createBaseFunction( + 'glutCheckLoop', dll=platform.GLUT, resultType=None, + argTypes=[], + doc='glutCheckLoop( ) -> None', + argNames=(), + ) + except AttributeError: + raise RuntimeError( + '''Your glut implementation does not allow interactive sessions''' + '''Consider installing freeglut.''') + glutMainLoopEvent = glutCheckLoop + elif glut.HAVE_FREEGLUT: + glutMainLoopEvent = glut.glutMainLoopEvent + else: + raise RuntimeError( + '''Your glut implementation does not allow interactive sessions. ''' + '''Consider installing freeglut.''') + + glut.glutHideWindow() # This is an event to be processed below + glutMainLoopEvent() + #glut.glutTimerFunc( sys.maxint-1, timer_none, 0) + self.clear_inputhook() + #signal.signal(signal.SIGINT, signal.default_int_handler) def enable_pyglet(self, app=None): """Enable event loop integration with pyglet. @@ -316,7 +517,6 @@ class InputHookManager(object): """ self.clear_inputhook() - def current_gui(self): """Return a string indicating the currently active GUI or None.""" return self._current_gui @@ -331,6 +531,8 @@ enable_gtk = inputhook_manager.enable_gtk disable_gtk = inputhook_manager.disable_gtk enable_tk = inputhook_manager.enable_tk disable_tk = inputhook_manager.disable_tk +enable_glut = inputhook_manager.enable_glut +disable_glut = inputhook_manager.disable_glut enable_pyglet = inputhook_manager.enable_pyglet disable_pyglet = inputhook_manager.disable_pyglet clear_inputhook = inputhook_manager.clear_inputhook @@ -371,6 +573,7 @@ def enable_gui(gui=None, app=None): GUI_WX: enable_wx, GUI_QT: enable_qt4, # qt3 not supported GUI_QT4: enable_qt4, + GUI_GLUT: enable_glut, GUI_PYGLET: enable_pyglet, } try: diff --git a/docs/examples/lib/gui-glut.py b/docs/examples/lib/gui-glut.py new file mode 100755 index 0000000..c9982e1 --- /dev/null +++ b/docs/examples/lib/gui-glut.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +"""Simple GLUT example to manually test event loop integration. + +This is meant to run tests manually in ipython as: + +In [5]: %gui glut + +In [6]: %run gui-glut.py + +In [7]: gl.glClearColor(1,1,1,1) +""" + +#!/usr/bin/env python +import sys +import OpenGL.GL as gl +import OpenGL.GLUT as glut + +def display(): + gl.glClear (gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) + glut.glutSwapBuffers() + +def resize(width,height): + gl.glViewport(0, 0, width, height+4) + gl.glMatrixMode(gl.GL_PROJECTION) + gl.glLoadIdentity() + gl.glOrtho(0, width, 0, height+4, -1, 1) + gl.glMatrixMode(gl.GL_MODELVIEW) + + +if glut.glutGetWindow() > 0: + interactive = True + glut.glutInit(sys.argv) + glut.glutInitDisplayMode(glut.GLUT_DOUBLE | + glut.GLUT_RGBA | + glut.GLUT_DEPTH) + glut.glutShowWindow() +else: + glut.glutCreateWindow('gui-glut') + interactive = False + +glut.glutDisplayFunc(display) +glut.glutReshapeFunc(resize) +gl.glClearColor(0,0,0,1) + +if not interactive: + glut.glutMainLoop()