inputhooks
This commit is contained in:
		| @@ -0,0 +1,49 @@ | |||||||
|  | import importlib | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | aliases = { | ||||||
|  |     'qt4': 'qt', | ||||||
|  |     'gtk2': 'gtk', | ||||||
|  | } | ||||||
|  |  | ||||||
|  | backends = [ | ||||||
|  |     'qt', 'qt4', 'qt5', | ||||||
|  |     'gtk', 'gtk2', 'gtk3', | ||||||
|  |     'tk', | ||||||
|  |     'wx', | ||||||
|  |     'pyglet', 'glut', | ||||||
|  |     'osx', | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | registered = {} | ||||||
|  |  | ||||||
|  | def register(name, inputhook): | ||||||
|  |     """Register the function *inputhook* as an event loop integration.""" | ||||||
|  |     registered[name] = inputhook | ||||||
|  |  | ||||||
|  | class UnknownBackend(KeyError): | ||||||
|  |     def __init__(self, name): | ||||||
|  |         self.name = name | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return ("No event loop integration for {!r}. " | ||||||
|  |                 "Supported event loops are: {}").format(self.name, | ||||||
|  |                                     ', '.join(backends + sorted(registered))) | ||||||
|  |  | ||||||
|  | def get_inputhook_name_and_func(gui): | ||||||
|  |     if gui in registered: | ||||||
|  |         return gui, registered[gui] | ||||||
|  |  | ||||||
|  |     if gui not in backends: | ||||||
|  |         raise UnknownBackend(gui) | ||||||
|  |  | ||||||
|  |     if gui in aliases: | ||||||
|  |         return get_inputhook_name_and_func(aliases[gui]) | ||||||
|  |  | ||||||
|  |     gui_mod = gui | ||||||
|  |     if gui == 'qt5': | ||||||
|  |         os.environ['QT_API'] = 'pyqt5' | ||||||
|  |         gui_mod = 'qt' | ||||||
|  |  | ||||||
|  |     mod = importlib.import_module('yap_ipython.terminal.pt_inputhooks.'+gui_mod) | ||||||
|  |     return gui, mod.inputhook | ||||||
| @@ -0,0 +1,140 @@ | |||||||
|  | """GLUT Input hook for interactive use with prompt_toolkit | ||||||
|  | """ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # GLUT is quite an old library and it is difficult to ensure proper | ||||||
|  | # integration within yap_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 the idle callback to | ||||||
|  | # ensure we got at least one event that will unblock the function. | ||||||
|  | # | ||||||
|  | # Furthermore, it is not possible to install these handlers without a window | ||||||
|  | # being first created. We choose to make this window invisible. 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 yap_ipython options system. | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import time | ||||||
|  | import signal | ||||||
|  | import OpenGL.GLUT as glut | ||||||
|  | import OpenGL.platform as platform | ||||||
|  | from timeit import default_timer as clock | ||||||
|  |  | ||||||
|  | # Frame per second : 60 | ||||||
|  | # Should probably be an yap_ipython option | ||||||
|  | glut_fps = 60 | ||||||
|  |  | ||||||
|  | # Display mode : double buffeed + rgba + depth | ||||||
|  | # Should probably be an yap_ipython option | ||||||
|  | glut_display_mode = (glut.GLUT_DOUBLE | | ||||||
|  |                      glut.GLUT_RGBA   | | ||||||
|  |                      glut.GLUT_DEPTH) | ||||||
|  |  | ||||||
|  | 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 glut_display(): | ||||||
|  |     # Dummy display function | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | def glut_idle(): | ||||||
|  |     # Dummy idle function | ||||||
|  |     pass | ||||||
|  |  | ||||||
|  | def glut_close(): | ||||||
|  |     # Close function only hides the current window | ||||||
|  |     glut.glutHideWindow() | ||||||
|  |     glutMainLoopEvent() | ||||||
|  |  | ||||||
|  | def glut_int_handler(signum, frame): | ||||||
|  |     # Catch sigint and print the defaultipyt   message | ||||||
|  |     signal.signal(signal.SIGINT, signal.default_int_handler) | ||||||
|  |     print('\nKeyboardInterrupt') | ||||||
|  |     # Need to reprint the prompt at this stage | ||||||
|  |  | ||||||
|  | # Initialisation code | ||||||
|  | glut.glutInit( sys.argv ) | ||||||
|  | glut.glutInitDisplayMode( glut_display_mode ) | ||||||
|  | # This is specific to freeglut | ||||||
|  | if bool(glut.glutSetOption): | ||||||
|  |     glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE, | ||||||
|  |                         glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS ) | ||||||
|  | glut.glutCreateWindow( b'ipython' ) | ||||||
|  | glut.glutReshapeWindow( 1, 1 ) | ||||||
|  | glut.glutHideWindow( ) | ||||||
|  | glut.glutWMCloseFunc( glut_close ) | ||||||
|  | glut.glutDisplayFunc( glut_display ) | ||||||
|  | glut.glutIdleFunc( glut_idle ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def inputhook(context): | ||||||
|  |     """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 yap_ipython is | ||||||
|  |     # idle and this is running. We trap KeyboardInterrupt and pass. | ||||||
|  |  | ||||||
|  |     signal.signal(signal.SIGINT, glut_int_handler) | ||||||
|  |  | ||||||
|  |     try: | ||||||
|  |         t = clock() | ||||||
|  |  | ||||||
|  |         # Make sure the default window is set after a window has been closed | ||||||
|  |         if glut.glutGetWindow() == 0: | ||||||
|  |             glut.glutSetWindow( 1 ) | ||||||
|  |             glutMainLoopEvent() | ||||||
|  |             return 0 | ||||||
|  |  | ||||||
|  |         while not context.input_is_ready(): | ||||||
|  |             glutMainLoopEvent() | ||||||
|  |             # 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 > 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 | ||||||
| @@ -0,0 +1,58 @@ | |||||||
|  | # Code borrowed from python-prompt-toolkit examples | ||||||
|  | # https://github.com/jonathanslenders/python-prompt-toolkit/blob/77cdcfbc7f4b4c34a9d2f9a34d422d7152f16209/examples/inputhook.py | ||||||
|  |  | ||||||
|  | # Copyright (c) 2014, Jonathan Slenders | ||||||
|  | # All rights reserved. | ||||||
|  | # | ||||||
|  | # Redistribution and use in source and binary forms, with or without modification, | ||||||
|  | # are permitted provided that the following conditions are met: | ||||||
|  | # | ||||||
|  | # * Redistributions of source code must retain the above copyright notice, this | ||||||
|  | #   list of conditions and the following disclaimer. | ||||||
|  | # | ||||||
|  | # * Redistributions in binary form must reproduce the above copyright notice, this | ||||||
|  | #   list of conditions and the following disclaimer in the documentation and/or | ||||||
|  | #   other materials provided with the distribution. | ||||||
|  | # | ||||||
|  | # * Neither the name of the {organization} nor the names of its | ||||||
|  | #   contributors may be used to endorse or promote products derived from | ||||||
|  | #   this software without specific prior written permission. | ||||||
|  | # | ||||||
|  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||||
|  | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||||
|  | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||||
|  | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | ||||||
|  | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||||
|  | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||||
|  | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||||||
|  | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | PyGTK input hook for prompt_toolkit. | ||||||
|  |  | ||||||
|  | Listens on the pipe prompt_toolkit sets up for a notification that it should | ||||||
|  | return control to the terminal event loop. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import gtk, gobject | ||||||
|  |  | ||||||
|  | # Enable threading in GTK. (Otherwise, GTK will keep the GIL.) | ||||||
|  | gtk.gdk.threads_init() | ||||||
|  |  | ||||||
|  | def inputhook(context): | ||||||
|  |     """ | ||||||
|  |     When the eventloop of prompt-toolkit is idle, call this inputhook. | ||||||
|  |  | ||||||
|  |     This will run the GTK main loop until the file descriptor | ||||||
|  |     `context.fileno()` becomes ready. | ||||||
|  |  | ||||||
|  |     :param context: An `InputHookContext` instance. | ||||||
|  |     """ | ||||||
|  |     def _main_quit(*a, **kw): | ||||||
|  |         gtk.main_quit() | ||||||
|  |         return False | ||||||
|  |  | ||||||
|  |     gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit) | ||||||
|  |     gtk.main() | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | """prompt_toolkit input hook for GTK 3 | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | from gi.repository import Gtk, GLib | ||||||
|  |  | ||||||
|  | def _main_quit(*args, **kwargs): | ||||||
|  |     Gtk.main_quit() | ||||||
|  |     return False | ||||||
|  |  | ||||||
|  | def inputhook(context): | ||||||
|  |     GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) | ||||||
|  |     Gtk.main() | ||||||
| @@ -0,0 +1,137 @@ | |||||||
|  | """Inputhook for OS X | ||||||
|  |  | ||||||
|  | Calls NSApp / CoreFoundation APIs via ctypes. | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | # obj-c boilerplate from appnope, used under BSD 2-clause | ||||||
|  |  | ||||||
|  | import ctypes | ||||||
|  | import ctypes.util | ||||||
|  | from threading import Event | ||||||
|  |  | ||||||
|  | objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) | ||||||
|  |  | ||||||
|  | void_p = ctypes.c_void_p | ||||||
|  |  | ||||||
|  | objc.objc_getClass.restype = void_p | ||||||
|  | objc.sel_registerName.restype = void_p | ||||||
|  | objc.objc_msgSend.restype = void_p | ||||||
|  | objc.objc_msgSend.argtypes = [void_p, void_p] | ||||||
|  |  | ||||||
|  | msg = objc.objc_msgSend | ||||||
|  |  | ||||||
|  | def _utf8(s): | ||||||
|  |     """ensure utf8 bytes""" | ||||||
|  |     if not isinstance(s, bytes): | ||||||
|  |         s = s.encode('utf8') | ||||||
|  |     return s | ||||||
|  |  | ||||||
|  | def n(name): | ||||||
|  |     """create a selector name (for ObjC methods)""" | ||||||
|  |     return objc.sel_registerName(_utf8(name)) | ||||||
|  |  | ||||||
|  | def C(classname): | ||||||
|  |     """get an ObjC Class by name""" | ||||||
|  |     return objc.objc_getClass(_utf8(classname)) | ||||||
|  |  | ||||||
|  | # end obj-c boilerplate from appnope | ||||||
|  |  | ||||||
|  | # CoreFoundation C-API calls we will use: | ||||||
|  | CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) | ||||||
|  |  | ||||||
|  | CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate | ||||||
|  | CFFileDescriptorCreate.restype = void_p | ||||||
|  | CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p] | ||||||
|  |  | ||||||
|  | CFFileDescriptorGetNativeDescriptor = CoreFoundation.CFFileDescriptorGetNativeDescriptor | ||||||
|  | CFFileDescriptorGetNativeDescriptor.restype = ctypes.c_int | ||||||
|  | CFFileDescriptorGetNativeDescriptor.argtypes = [void_p] | ||||||
|  |  | ||||||
|  | CFFileDescriptorEnableCallBacks = CoreFoundation.CFFileDescriptorEnableCallBacks | ||||||
|  | CFFileDescriptorEnableCallBacks.restype = None | ||||||
|  | CFFileDescriptorEnableCallBacks.argtypes = [void_p, ctypes.c_ulong] | ||||||
|  |  | ||||||
|  | CFFileDescriptorCreateRunLoopSource = CoreFoundation.CFFileDescriptorCreateRunLoopSource | ||||||
|  | CFFileDescriptorCreateRunLoopSource.restype = void_p | ||||||
|  | CFFileDescriptorCreateRunLoopSource.argtypes = [void_p, void_p, void_p] | ||||||
|  |  | ||||||
|  | CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent | ||||||
|  | CFRunLoopGetCurrent.restype = void_p | ||||||
|  |  | ||||||
|  | CFRunLoopAddSource = CoreFoundation.CFRunLoopAddSource | ||||||
|  | CFRunLoopAddSource.restype = None | ||||||
|  | CFRunLoopAddSource.argtypes = [void_p, void_p, void_p] | ||||||
|  |  | ||||||
|  | CFRelease = CoreFoundation.CFRelease | ||||||
|  | CFRelease.restype = None | ||||||
|  | CFRelease.argtypes = [void_p] | ||||||
|  |  | ||||||
|  | CFFileDescriptorInvalidate = CoreFoundation.CFFileDescriptorInvalidate | ||||||
|  | CFFileDescriptorInvalidate.restype = None | ||||||
|  | CFFileDescriptorInvalidate.argtypes = [void_p] | ||||||
|  |  | ||||||
|  | # From CFFileDescriptor.h | ||||||
|  | kCFFileDescriptorReadCallBack = 1 | ||||||
|  | kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _NSApp(): | ||||||
|  |     """Return the global NSApplication instance (NSApp)""" | ||||||
|  |     return msg(C('NSApplication'), n('sharedApplication')) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _wake(NSApp): | ||||||
|  |     """Wake the Application""" | ||||||
|  |     event = msg(C('NSEvent'), | ||||||
|  |         n('otherEventWithType:location:modifierFlags:' | ||||||
|  |           'timestamp:windowNumber:context:subtype:data1:data2:'), | ||||||
|  |         15, # Type | ||||||
|  |         0, # location | ||||||
|  |         0, # flags | ||||||
|  |         0, # timestamp | ||||||
|  |         0, # window | ||||||
|  |         None, # context | ||||||
|  |         0, # subtype | ||||||
|  |         0, # data1 | ||||||
|  |         0, # data2 | ||||||
|  |     ) | ||||||
|  |     msg(NSApp, n('postEvent:atStart:'), void_p(event), True) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | _triggered = Event() | ||||||
|  |  | ||||||
|  | def _input_callback(fdref, flags, info): | ||||||
|  |     """Callback to fire when there's input to be read""" | ||||||
|  |     _triggered.set() | ||||||
|  |     CFFileDescriptorInvalidate(fdref) | ||||||
|  |     CFRelease(fdref) | ||||||
|  |     NSApp = _NSApp() | ||||||
|  |     msg(NSApp, n('stop:'), NSApp) | ||||||
|  |     _wake(NSApp) | ||||||
|  |  | ||||||
|  | _c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p) | ||||||
|  | _c_input_callback = _c_callback_func_type(_input_callback) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _stop_on_read(fd): | ||||||
|  |     """Register callback to stop eventloop when there's data on fd""" | ||||||
|  |     _triggered.clear() | ||||||
|  |     fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None) | ||||||
|  |     CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack) | ||||||
|  |     source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0) | ||||||
|  |     loop = CFRunLoopGetCurrent() | ||||||
|  |     CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes) | ||||||
|  |     CFRelease(source) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def inputhook(context): | ||||||
|  |     """Inputhook for Cocoa (NSApp)""" | ||||||
|  |     NSApp = _NSApp() | ||||||
|  |     _stop_on_read(context.fileno()) | ||||||
|  |     msg(NSApp, n('run')) | ||||||
|  |     if not _triggered.is_set(): | ||||||
|  |         # app closed without firing callback, | ||||||
|  |         # probably due to last window being closed. | ||||||
|  |         # Run the loop manually in this case, | ||||||
|  |         # since there may be events still to process (#9734) | ||||||
|  |         CoreFoundation.CFRunLoopRun() | ||||||
| @@ -0,0 +1,66 @@ | |||||||
|  | """Enable pyglet to be used interacively with prompt_toolkit | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import time | ||||||
|  | from timeit import default_timer as clock | ||||||
|  | import pyglet | ||||||
|  |  | ||||||
|  | # 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() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def inputhook(context): | ||||||
|  |     """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 yap_ipython is | ||||||
|  |     # idle and this is running. We trap KeyboardInterrupt and pass. | ||||||
|  |     try: | ||||||
|  |         t = clock() | ||||||
|  |         while not context.input_is_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 > 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 | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | import sys | ||||||
|  | from yap_ipython.external.qt_for_kernel import QtCore, QtGui | ||||||
|  |  | ||||||
|  | # If we create a QApplication, keep a reference to it so that it doesn't get | ||||||
|  | # garbage collected. | ||||||
|  | _appref = None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def inputhook(context): | ||||||
|  |     global _appref | ||||||
|  |     app = QtCore.QCoreApplication.instance() | ||||||
|  |     if not app: | ||||||
|  |         _appref = app = QtGui.QApplication([" "]) | ||||||
|  |     event_loop = QtCore.QEventLoop(app) | ||||||
|  |  | ||||||
|  |     if sys.platform == 'win32': | ||||||
|  |         # The QSocketNotifier method doesn't appear to work on Windows. | ||||||
|  |         # Use polling instead. | ||||||
|  |         timer = QtCore.QTimer() | ||||||
|  |         timer.timeout.connect(event_loop.quit) | ||||||
|  |         while not context.input_is_ready(): | ||||||
|  |             timer.start(50)  # 50 ms | ||||||
|  |             event_loop.exec_() | ||||||
|  |             timer.stop() | ||||||
|  |     else: | ||||||
|  |         # On POSIX platforms, we can use a file descriptor to quit the event | ||||||
|  |         # loop when there is input ready to read. | ||||||
|  |         notifier = QtCore.QSocketNotifier(context.fileno(), | ||||||
|  |                                           QtCore.QSocketNotifier.Read) | ||||||
|  |         # connect the callback we care about before we turn it on | ||||||
|  |         notifier.activated.connect(event_loop.exit) | ||||||
|  |         notifier.setEnabled(True) | ||||||
|  |         # only start the event loop we are not already flipped | ||||||
|  |         if not context.input_is_ready(): | ||||||
|  |             event_loop.exec_() | ||||||
| @@ -0,0 +1,90 @@ | |||||||
|  | # Code borrowed from ptpython | ||||||
|  | # https://github.com/jonathanslenders/ptpython/blob/86b71a89626114b18898a0af463978bdb32eeb70/ptpython/eventloop.py | ||||||
|  |  | ||||||
|  | # Copyright (c) 2015, Jonathan Slenders | ||||||
|  | # All rights reserved. | ||||||
|  | # | ||||||
|  | # Redistribution and use in source and binary forms, with or without modification, | ||||||
|  | # are permitted provided that the following conditions are met: | ||||||
|  | # | ||||||
|  | # * Redistributions of source code must retain the above copyright notice, this | ||||||
|  | #   list of conditions and the following disclaimer. | ||||||
|  | # | ||||||
|  | # * Redistributions in binary form must reproduce the above copyright notice, this | ||||||
|  | #   list of conditions and the following disclaimer in the documentation and/or | ||||||
|  | #   other materials provided with the distribution. | ||||||
|  | # | ||||||
|  | # * Neither the name of the {organization} nor the names of its | ||||||
|  | #   contributors may be used to endorse or promote products derived from | ||||||
|  | #   this software without specific prior written permission. | ||||||
|  | # | ||||||
|  | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||||||
|  | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||||||
|  | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||||
|  | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | ||||||
|  | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||||||
|  | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||||||
|  | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||||||
|  | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||
|  | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||||||
|  | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | Wrapper around the eventloop that gives some time to the Tkinter GUI to process | ||||||
|  | events when it's loaded and while we are waiting for input at the REPL. This | ||||||
|  | way we don't block the UI of for instance ``turtle`` and other Tk libraries. | ||||||
|  |  | ||||||
|  | (Normally Tkinter registers it's callbacks in ``PyOS_InputHook`` to integrate | ||||||
|  | in readline. ``prompt-toolkit`` doesn't understand that input hook, but this | ||||||
|  | will fix it for Tk.) | ||||||
|  | """ | ||||||
|  | import time | ||||||
|  |  | ||||||
|  | import _tkinter | ||||||
|  | import tkinter | ||||||
|  |  | ||||||
|  | def inputhook(inputhook_context): | ||||||
|  |     """ | ||||||
|  |     Inputhook for Tk. | ||||||
|  |     Run the Tk eventloop until prompt-toolkit needs to process the next input. | ||||||
|  |     """ | ||||||
|  |     # Get the current TK application. | ||||||
|  |     root = tkinter._default_root | ||||||
|  |  | ||||||
|  |     def wait_using_filehandler(): | ||||||
|  |         """ | ||||||
|  |         Run the TK eventloop until the file handler that we got from the | ||||||
|  |         inputhook becomes readable. | ||||||
|  |         """ | ||||||
|  |         # Add a handler that sets the stop flag when `prompt-toolkit` has input | ||||||
|  |         # to process. | ||||||
|  |         stop = [False] | ||||||
|  |         def done(*a): | ||||||
|  |             stop[0] = True | ||||||
|  |  | ||||||
|  |         root.createfilehandler(inputhook_context.fileno(), _tkinter.READABLE, done) | ||||||
|  |  | ||||||
|  |         # Run the TK event loop as long as we don't receive input. | ||||||
|  |         while root.dooneevent(_tkinter.ALL_EVENTS): | ||||||
|  |             if stop[0]: | ||||||
|  |                 break | ||||||
|  |  | ||||||
|  |         root.deletefilehandler(inputhook_context.fileno()) | ||||||
|  |  | ||||||
|  |     def wait_using_polling(): | ||||||
|  |         """ | ||||||
|  |         Windows TK doesn't support 'createfilehandler'. | ||||||
|  |         So, run the TK eventloop and poll until input is ready. | ||||||
|  |         """ | ||||||
|  |         while not inputhook_context.input_is_ready(): | ||||||
|  |             while root.dooneevent(_tkinter.ALL_EVENTS | _tkinter.DONT_WAIT): | ||||||
|  |                  pass | ||||||
|  |             # Sleep to make the CPU idle, but not too long, so that the UI | ||||||
|  |             # stays responsive. | ||||||
|  |             time.sleep(.01) | ||||||
|  |  | ||||||
|  |     if root is not None: | ||||||
|  |         if hasattr(root, 'createfilehandler'): | ||||||
|  |             wait_using_filehandler() | ||||||
|  |         else: | ||||||
|  |             wait_using_polling() | ||||||
| @@ -0,0 +1,147 @@ | |||||||
|  | """Enable wxPython to be used interacively in prompt_toolkit | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import sys | ||||||
|  | import signal | ||||||
|  | import time | ||||||
|  | from timeit import default_timer as clock | ||||||
|  | import wx | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def inputhook_wx1(context): | ||||||
|  |     """Run the wx event loop by processing pending events only. | ||||||
|  |  | ||||||
|  |     This approach seems to work, but its performance is not great as it | ||||||
|  |     relies on having PyOS_InputHook called regularly. | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         app = wx.GetApp() | ||||||
|  |         if app is not None: | ||||||
|  |             assert wx.Thread_IsMain() | ||||||
|  |  | ||||||
|  |             # Make a temporary event loop and process system events until | ||||||
|  |             # there are no more waiting, then allow idle events (which | ||||||
|  |             # will also deal with pending or posted wx events.) | ||||||
|  |             evtloop = wx.EventLoop() | ||||||
|  |             ea = wx.EventLoopActivator(evtloop) | ||||||
|  |             while evtloop.Pending(): | ||||||
|  |                 evtloop.Dispatch() | ||||||
|  |             app.ProcessIdle() | ||||||
|  |             del ea | ||||||
|  |     except KeyboardInterrupt: | ||||||
|  |         pass | ||||||
|  |     return 0 | ||||||
|  |  | ||||||
|  | class EventLoopTimer(wx.Timer): | ||||||
|  |  | ||||||
|  |     def __init__(self, func): | ||||||
|  |         self.func = func | ||||||
|  |         wx.Timer.__init__(self) | ||||||
|  |  | ||||||
|  |     def Notify(self): | ||||||
|  |         self.func() | ||||||
|  |  | ||||||
|  | class EventLoopRunner(object): | ||||||
|  |  | ||||||
|  |     def Run(self, time, input_is_ready): | ||||||
|  |         self.input_is_ready = input_is_ready | ||||||
|  |         self.evtloop = wx.EventLoop() | ||||||
|  |         self.timer = EventLoopTimer(self.check_stdin) | ||||||
|  |         self.timer.Start(time) | ||||||
|  |         self.evtloop.Run() | ||||||
|  |  | ||||||
|  |     def check_stdin(self): | ||||||
|  |         if self.input_is_ready(): | ||||||
|  |             self.timer.Stop() | ||||||
|  |             self.evtloop.Exit() | ||||||
|  |  | ||||||
|  | def inputhook_wx2(context): | ||||||
|  |     """Run the wx event loop, polling for stdin. | ||||||
|  |  | ||||||
|  |     This version runs the wx eventloop for an undetermined amount of time, | ||||||
|  |     during which it periodically checks to see if anything is ready on | ||||||
|  |     stdin.  If anything is ready on stdin, the event loop exits. | ||||||
|  |  | ||||||
|  |     The argument to elr.Run controls how often the event loop looks at stdin. | ||||||
|  |     This determines the responsiveness at the keyboard.  A setting of 1000 | ||||||
|  |     enables a user to type at most 1 char per second.  I have found that a | ||||||
|  |     setting of 10 gives good keyboard response.  We can shorten it further, | ||||||
|  |     but eventually performance would suffer from calling select/kbhit too | ||||||
|  |     often. | ||||||
|  |     """ | ||||||
|  |     try: | ||||||
|  |         app = wx.GetApp() | ||||||
|  |         if app is not None: | ||||||
|  |             assert wx.Thread_IsMain() | ||||||
|  |             elr = EventLoopRunner() | ||||||
|  |             # As this time is made shorter, keyboard response improves, but idle | ||||||
|  |             # CPU load goes up.  10 ms seems like a good compromise. | ||||||
|  |             elr.Run(time=10,  # CHANGE time here to control polling interval | ||||||
|  |                     input_is_ready=context.input_is_ready) | ||||||
|  |     except KeyboardInterrupt: | ||||||
|  |         pass | ||||||
|  |     return 0 | ||||||
|  |  | ||||||
|  | def inputhook_wx3(context): | ||||||
|  |     """Run the wx event loop by processing pending events only. | ||||||
|  |  | ||||||
|  |     This is like inputhook_wx1, but it 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 yap_ipython is | ||||||
|  |     # idle and this is running. We trap KeyboardInterrupt and pass. | ||||||
|  |     try: | ||||||
|  |         app = wx.GetApp() | ||||||
|  |         if app is not None: | ||||||
|  |             assert wx.Thread_IsMain() | ||||||
|  |  | ||||||
|  |             # The import of wx on Linux sets the handler for signal.SIGINT | ||||||
|  |             # to 0.  This is a bug in wx or gtk.  We fix by just setting it | ||||||
|  |             # back to the Python default. | ||||||
|  |             if not callable(signal.getsignal(signal.SIGINT)): | ||||||
|  |                 signal.signal(signal.SIGINT, signal.default_int_handler) | ||||||
|  |  | ||||||
|  |             evtloop = wx.EventLoop() | ||||||
|  |             ea = wx.EventLoopActivator(evtloop) | ||||||
|  |             t = clock() | ||||||
|  |             while not context.input_is_ready(): | ||||||
|  |                 while evtloop.Pending(): | ||||||
|  |                     t = clock() | ||||||
|  |                     evtloop.Dispatch() | ||||||
|  |                 app.ProcessIdle() | ||||||
|  |                 # 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 > 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) | ||||||
|  |             del ea | ||||||
|  |     except KeyboardInterrupt: | ||||||
|  |         pass | ||||||
|  |     return 0 | ||||||
|  |  | ||||||
|  | if sys.platform == 'darwin': | ||||||
|  |     # On OSX, evtloop.Pending() always returns True, regardless of there being | ||||||
|  |     # any events pending. As such we can't use implementations 1 or 3 of the | ||||||
|  |     # inputhook as those depend on a pending/dispatch loop. | ||||||
|  |     inputhook = inputhook_wx2 | ||||||
|  | else: | ||||||
|  |     # This is our default implementation | ||||||
|  |     inputhook = inputhook_wx3 | ||||||
		Reference in New Issue
	
	Block a user