inputhooks
This commit is contained in:
parent
9c862c21bc
commit
1cff18d1c0
@ -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