1"""GLUT Input hook for interactive use with prompt_toolkit 2""" 3from __future__ import print_function 4 5 6# GLUT is quite an old library and it is difficult to ensure proper 7# integration within IPython since original GLUT does not allow to handle 8# events one by one. Instead, it requires for the mainloop to be entered 9# and never returned (there is not even a function to exit he 10# mainloop). Fortunately, there are alternatives such as freeglut 11# (available for linux and windows) and the OSX implementation gives 12# access to a glutCheckLoop() function that blocks itself until a new 13# event is received. This means we have to setup the idle callback to 14# ensure we got at least one event that will unblock the function. 15# 16# Furthermore, it is not possible to install these handlers without a window 17# being first created. We choose to make this window invisible. This means that 18# display mode options are set at this level and user won't be able to change 19# them later without modifying the code. This should probably be made available 20# via IPython options system. 21 22import sys 23import time 24import signal 25import OpenGL.GLUT as glut 26import OpenGL.platform as platform 27from timeit import default_timer as clock 28 29# Frame per second : 60 30# Should probably be an IPython option 31glut_fps = 60 32 33# Display mode : double buffeed + rgba + depth 34# Should probably be an IPython option 35glut_display_mode = (glut.GLUT_DOUBLE | 36 glut.GLUT_RGBA | 37 glut.GLUT_DEPTH) 38 39glutMainLoopEvent = None 40if sys.platform == 'darwin': 41 try: 42 glutCheckLoop = platform.createBaseFunction( 43 'glutCheckLoop', dll=platform.GLUT, resultType=None, 44 argTypes=[], 45 doc='glutCheckLoop( ) -> None', 46 argNames=(), 47 ) 48 except AttributeError: 49 raise RuntimeError( 50 '''Your glut implementation does not allow interactive sessions''' 51 '''Consider installing freeglut.''') 52 glutMainLoopEvent = glutCheckLoop 53elif glut.HAVE_FREEGLUT: 54 glutMainLoopEvent = glut.glutMainLoopEvent 55else: 56 raise RuntimeError( 57 '''Your glut implementation does not allow interactive sessions. ''' 58 '''Consider installing freeglut.''') 59 60 61def glut_display(): 62 # Dummy display function 63 pass 64 65def glut_idle(): 66 # Dummy idle function 67 pass 68 69def glut_close(): 70 # Close function only hides the current window 71 glut.glutHideWindow() 72 glutMainLoopEvent() 73 74def glut_int_handler(signum, frame): 75 # Catch sigint and print the defaultipyt message 76 signal.signal(signal.SIGINT, signal.default_int_handler) 77 print('\nKeyboardInterrupt') 78 # Need to reprint the prompt at this stage 79 80# Initialisation code 81glut.glutInit( sys.argv ) 82glut.glutInitDisplayMode( glut_display_mode ) 83# This is specific to freeglut 84if bool(glut.glutSetOption): 85 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE, 86 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS ) 87glut.glutCreateWindow( b'ipython' ) 88glut.glutReshapeWindow( 1, 1 ) 89glut.glutHideWindow( ) 90glut.glutWMCloseFunc( glut_close ) 91glut.glutDisplayFunc( glut_display ) 92glut.glutIdleFunc( glut_idle ) 93 94 95def inputhook(context): 96 """Run the pyglet event loop by processing pending events only. 97 98 This keeps processing pending events until stdin is ready. After 99 processing all pending events, a call to time.sleep is inserted. This is 100 needed, otherwise, CPU usage is at 100%. This sleep time should be tuned 101 though for best performance. 102 """ 103 # We need to protect against a user pressing Control-C when IPython is 104 # idle and this is running. We trap KeyboardInterrupt and pass. 105 106 signal.signal(signal.SIGINT, glut_int_handler) 107 108 try: 109 t = clock() 110 111 # Make sure the default window is set after a window has been closed 112 if glut.glutGetWindow() == 0: 113 glut.glutSetWindow( 1 ) 114 glutMainLoopEvent() 115 return 0 116 117 while not context.input_is_ready(): 118 glutMainLoopEvent() 119 # We need to sleep at this point to keep the idle CPU load 120 # low. However, if sleep to long, GUI response is poor. As 121 # a compromise, we watch how often GUI events are being processed 122 # and switch between a short and long sleep time. Here are some 123 # stats useful in helping to tune this. 124 # time CPU load 125 # 0.001 13% 126 # 0.005 3% 127 # 0.01 1.5% 128 # 0.05 0.5% 129 used_time = clock() - t 130 if used_time > 10.0: 131 # print 'Sleep for 1 s' # dbg 132 time.sleep(1.0) 133 elif used_time > 0.1: 134 # Few GUI events coming in, so we can sleep longer 135 # print 'Sleep for 0.05 s' # dbg 136 time.sleep(0.05) 137 else: 138 # Many GUI events coming in, so sleep only very little 139 time.sleep(0.001) 140 except KeyboardInterrupt: 141 pass 142