From a7fa9d77a4b0dfd537b53830b3dbbbc6e5dc0da9 2009-07-21 17:40:51 From: Brian Granger Date: 2009-07-21 17:40:51 Subject: [PATCH] First draft of full inputhook management. --- diff --git a/IPython/lib/guiloop/__init__.py b/IPython/lib/guiloop/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/IPython/lib/guiloop/__init__.py diff --git a/IPython/lib/guiloop/inputhook.py b/IPython/lib/guiloop/inputhook.py new file mode 100644 index 0000000..a75fdd2 --- /dev/null +++ b/IPython/lib/guiloop/inputhook.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Inputhook management for GUI event loop integration. +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import ctypes + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + + +class InputHookManager(object): + + def __init__(self): + self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int) + self._reset() + + def _reset(self): + self._callback_pyfunctype = None + self._callback = None + self._installed = False + + def get_pyos_inputhook(self): + return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook") + + def get_pyos_inputhook_as_func(self): + return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook") + + def set_inputhook(callback): + """Set PyOS_InputHook to callback and return the previous one. + """ + self._callback = callback + self._callback_pyfunctype = self.PYFUNC(callback) + pyos_inputhook_ptr = self.get_pyos_inputhook() + original = self.get_pyos_inputhook_as_func() + pyos_inputhook_ptr.value = \ + ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value + self._installed = True + return original + + def clear_inputhook(self): + """Set PyOS_InputHook to NULL and return the previous one.""" + pyos_inputhook_ptr = self.get_pyos_inputhook() + original = self.get_pyos_inputhook_as_func() + pyos_inputhook_ptr.value = ctypes.c_void_p(None).value + self._reset() + return original + + def enable_wx(self): + from IPython.lib.guiloop.inputhookwx import inputhook_wx + self.set_inputhook(inputhook_wx) + + def disable_wx(self): + self.clear_inputhook() + + def enable_qt4(self): + from PyQt4 import QtCore + # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook + # was set when QtCore was imported, but if it ever got removed, + # you couldn't reset it. For earlier versions we can + # probably implement a ctypes version. + try: + QtCore.pyqtRestoreInputHook() + except AttributeError: + pass + + def disable_qt4(self): + self.clear_inputhook() + + def enable_gtk(self): + import gtk + try: + gtk.set_interactive(True) + except AttributeError: + # For older versions of gtk, use our own ctypes version + from IPython.lib.guiloop.inputhookgtk import inputhook_gtk + add_inputhook(inputhook_gtk) + + def disable_gtk(self): + self.clear_inputhook() + + def enable_tk(self): + # Creating a Tkinter.Tk object sets PyOS_InputHook() + pass + + def disable_tk(self): + self.clear_inputhook() + +inputhook_manager = InputHookManager() + +enable_wx = inputhook_manager.enable_wx +disable_wx = inputhook_manager.disable_wx +enable_qt4 = inputhook_manager.enable_qt4 +disable_qt4 = inputhook_manager.disable_qt4 +enable_gtk = inputhook_manager.enable_gtk +disable_gtk = inputhook_manager.disable_gtk diff --git a/IPython/lib/guiloop/inputhookgtk.py b/IPython/lib/guiloop/inputhookgtk.py new file mode 100644 index 0000000..f81d385 --- /dev/null +++ b/IPython/lib/guiloop/inputhookgtk.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +Enable pygtk to be used interacive by setting PyOS_InputHook. + +Authors: Brian Granger +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +import sys +import gtk, gobject + + +def _main_quit(*args, **kwargs): + gtk.main_quit() + return False + +def inputhook_gtk(): + gobject.io_add_watch(sys.stdin, gobject.IO_IN, _main_quit) + gtk.main() + return 0 + diff --git a/IPython/lib/guiloop/inputhookwx.py b/IPython/lib/guiloop/inputhookwx.py new file mode 100644 index 0000000..46db247 --- /dev/null +++ b/IPython/lib/guiloop/inputhookwx.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +Enable wxPython to be used interacive by setting PyOS_InputHook. + +Authors: Robin Dunn, Brian Granger, Ondrej Certik +""" + +#----------------------------------------------------------------------------- +# Copyright (C) 2008-2009 The IPython Development Team +# +# Distributed under the terms of the BSD License. The full license is in +# the file COPYING, distributed as part of this software. +#----------------------------------------------------------------------------- + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- + +import os +import sys +import time +from timeit import default_timer as clock +import wx + +if os.name == 'posix': + import select +elif sys.platform == 'win32': + import msvcrt + +#----------------------------------------------------------------------------- +# Code +#----------------------------------------------------------------------------- + +def stdin_ready(): + if os.name == 'posix': + infds, outfds, erfds = select.select([sys.stdin],[],[],0) + if infds: + return True + else: + return False + elif sys.platform == 'win32': + return msvcrt.kbhit() + + +def inputhook_wx1(): + """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. + """ + 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 + 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): + self.evtloop = wx.EventLoop() + self.timer = EventLoopTimer(self.check_stdin) + self.timer.Start(time) + self.evtloop.Run() + + def check_stdin(self): + if stdin_ready(): + self.timer.Stop() + self.evtloop.Exit() + +def inputhook_wx2(): + """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. + """ + 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 + return 0 + +def inputhook_wx3(): + """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. + """ + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + + evtloop = wx.EventLoop() + ea = wx.EventLoopActivator(evtloop) + t = clock() + while not stdin_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% + if clock()-t > 0.1: + # Few GUI events coming in, so we can sleep longer + time.sleep(0.05) + else: + # Many GUI events coming in, so sleep only very little + time.sleep(0.001) + del ea + return 0 + +# This is our default implementation +inputhook_wx = inputhook_wx3 \ No newline at end of file