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