##// END OF EJS Templates
Work on inputhook....
Brian Granger -
Show More
@@ -1,233 +1,286 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Inputhook management for GUI event loop integration.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2009 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 import ctypes
19 19 import sys
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Code
23 23 #-----------------------------------------------------------------------------
24 24
25 def _dummy_mainloop(*args, **kw):
26 pass
27
25 28
26 29 class InputHookManager(object):
27 30 """Manage PyOS_InputHook for different GUI toolkits.
28 31
29 32 This class installs various hooks under ``PyOSInputHook`` to handle
30 33 GUI event loop integration.
31 34 """
32 35
33 36 def __init__(self):
34 37 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
38 self._apps = {}
35 39 self._reset()
36 40
37 41 def _reset(self):
38 42 self._callback_pyfunctype = None
39 43 self._callback = None
40 44 self._installed = False
41 45 self._current_gui = None
42 46
47 def _hijack_wx(self):
48 import wx
49 if hasattr(wx, '_core_'): core = getattr(wx, '_core_')
50 elif hasattr(wx, '_core'): core = getattr(wx, '_core')
51 else: raise AttributeError('Could not find wx core module')
52 orig_mainloop = core.PyApp_MainLoop
53 core.PyApp_MainLoop = _dummy_mainloop
54 return orig_mainloop
55
56 def _hijack_qt4(self):
57 from PyQt4 import QtGui, QtCore
58 orig_mainloop = QtGui.qApp.exec_
59 QtGui.qApp.exec_ = _dummy_mainloop
60 QtGui.QApplication.exec_ = _dummy_mainloop
61 QtCore.QCoreApplication.exec_ = _dummy_mainloop
62 return orig_mainloop
63
64 def _hijack_gtk(self):
65 import gtk
66 orig_mainloop = gtk.main
67 gtk.mainloop = _dummy_mainloop
68 gtk.main = _dummy_mainloop
69 return orig_mainloop
70
71 def _hijack_tk(self):
72 import Tkinter
73 Tkinter.Misc.mainloop = _dummy_mainloop
74 Tkinter.mainloop = _dummy_mainloop
75
43 76 def get_pyos_inputhook(self):
44 77 """Return the current PyOS_InputHook as a ctypes.c_void_p.
45 78 """
46 79 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
47 80
48 81 def get_pyos_inputhook_as_func(self):
49 82 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE.
50 83 """
51 84 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
52 85
53 86 def set_inputhook(self, callback):
54 87 """Set PyOS_InputHook to callback and return the previous one.
55 88 """
56 89 self._callback = callback
57 90 self._callback_pyfunctype = self.PYFUNC(callback)
58 91 pyos_inputhook_ptr = self.get_pyos_inputhook()
59 92 original = self.get_pyos_inputhook_as_func()
60 93 pyos_inputhook_ptr.value = \
61 94 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
62 95 self._installed = True
63 96 return original
64 97
65 98 def clear_inputhook(self):
66 99 """Set PyOS_InputHook to NULL and return the previous one.
67 100 """
68 101 pyos_inputhook_ptr = self.get_pyos_inputhook()
69 102 original = self.get_pyos_inputhook_as_func()
70 103 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
71 104 self._reset()
72 105 return original
73 106
107 def clear_app_refs(self, gui=None):
108 """Clear IPython's internal reference to an application instance.
109
110 Parameters
111 ----------
112 gui : None or str
113 If None, clear all app references. If ('wx', 'qt4') clear
114 the app for that toolkit. References are not held for gtk or tk
115 as those toolkits don't have the notion of an app.
116 """
117 if gui is None:
118 self._apps = {}
119 elif self._apps.has_key(gui):
120 del self._apps[gui]
121
74 122 def enable_wx(self, app=False):
75 123 """Enable event loop integration with wxPython.
76 124
77 125 Parameters
78 126 ----------
79 127 app : bool
80 128 Create a running application object or not.
81 129
82 130 Notes
83 131 -----
84 132 This methods sets the PyOS_InputHook for wxPython, which allows
85 133 the wxPython to integrate with terminal based applications like
86 134 IPython.
87 135
88 136 Once this has been called, you can use wx interactively by doing::
89 137
90 138 >>> import wx
91 139 >>> app = wx.App(redirect=False, clearSigInt=False)
92 140
93 141 Both options this constructor are important for things to work
94 142 properly in an interactive context.
95 143
96 144 But, *don't start the event loop*. That is handled automatically by
97 145 PyOS_InputHook.
98 146 """
99 147 from IPython.lib.inputhookwx import inputhook_wx
100 148 self.set_inputhook(inputhook_wx)
101 149 self._current_gui = 'wx'
150 self._hijack_wx()
102 151 if app:
103 152 import wx
104 app = wx.App(redirect=False, clearSigInt=False)
105 # The import of wx on Linux sets the handler for signal.SIGINT
106 # to 0. This is a bug in wx or gtk. We fix by just setting it
107 # back to the Python default.
108 import signal
109 if not callable(signal.getsignal(signal.SIGINT)):
110 signal.signal(signal.SIGINT, signal.default_int_handler)
153 app = wx.GetApp()
154 if app is None:
155 app = wx.App(redirect=False, clearSigInt=False)
156 self._apps['wx'] = app
111 157 return app
112 158
113 159 def disable_wx(self):
114 160 """Disable event loop integration with wxPython.
115 161
116 162 This merely sets PyOS_InputHook to NULL.
117 163 """
118 164 self.clear_inputhook()
119 165
120 166 def enable_qt4(self, app=False):
121 167 """Enable event loop integration with PyQt4.
122 168
123 169 Parameters
124 170 ----------
125 171 app : bool
126 172 Create a running application object or not.
127 173
128 174 Notes
129 175 -----
130 176 This methods sets the PyOS_InputHook for wxPython, which allows
131 177 the PyQt4 to integrate with terminal based applications like
132 178 IPython.
133 179
134 180 Once this has been called, you can simply create a QApplication and
135 181 use it. But, *don't start the event loop*. That is handled
136 182 automatically by PyOS_InputHook.
137 183 """
138 184 from PyQt4 import QtCore
139 185 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
140 186 # was set when QtCore was imported, but if it ever got removed,
141 187 # you couldn't reset it. For earlier versions we can
142 188 # probably implement a ctypes version.
143 189 try:
144 190 QtCore.pyqtRestoreInputHook()
145 191 except AttributeError:
146 192 pass
147 193 self._current_gui = 'qt4'
194 self._hijack_qt4()
148 195 if app:
149 196 from PyQt4 import QtGui
150 app = QtGui.QApplication(sys.argv)
197 app = QtGui.QApplication.instance()
198 if app is None:
199 app = QtGui.QApplication(sys.argv)
200 self._apps['qt4'] = app
151 201 return app
152 202
153 203 def disable_qt4(self):
154 204 """Disable event loop integration with PyQt4.
155 205
156 206 This merely sets PyOS_InputHook to NULL.
157 207 """
158 208 self.clear_inputhook()
159 209
160 210 def enable_gtk(self, app=False):
161 211 """Enable event loop integration with PyGTK.
162 212
163 213 Parameters
164 214 ----------
165 215 app : bool
166 216 Create a running application object or not.
167 217
168 218 Notes
169 219 -----
170 220 This methods sets the PyOS_InputHook for PyGTK, which allows
171 221 the PyGTK to integrate with terminal based applications like
172 222 IPython.
173 223
174 224 Once this has been called, you can simple create PyGTK objects and
175 225 use them. But, *don't start the event loop*. That is handled
176 226 automatically by PyOS_InputHook.
177 227 """
178 228 import gtk
179 229 try:
180 230 gtk.set_interactive(True)
181 231 self._current_gui = 'gtk'
182 232 except AttributeError:
183 233 # For older versions of gtk, use our own ctypes version
184 234 from IPython.lib.inputhookgtk import inputhook_gtk
185 235 self.set_inputhook(inputhook_gtk)
186 236 self._current_gui = 'gtk'
237 self._hijack_gtk()
187 238
188 239 def disable_gtk(self):
189 240 """Disable event loop integration with PyGTK.
190 241
191 242 This merely sets PyOS_InputHook to NULL.
192 243 """
193 244 self.clear_inputhook()
194 245
195 246 def enable_tk(self, app=False):
196 247 """Enable event loop integration with Tk.
197 248
198 249 Parameters
199 250 ----------
200 251 app : bool
201 252 Create a running application object or not.
202 253
203 254 Notes
204 255 -----
205 256 Currently this is a no-op as creating a :class:`Tkinter.Tk` object
206 257 sets ``PyOS_InputHook``.
207 258 """
208 259 self._current_gui = 'tk'
260 self._hijack_tk()
209 261
210 262 def disable_tk(self):
211 263 """Disable event loop integration with Tkinter.
212 264
213 265 This merely sets PyOS_InputHook to NULL.
214 266 """
215 267 self.clear_inputhook()
216 268
217 269 def current_gui(self):
218 270 """Return a string indicating the currently active GUI or None."""
219 271 return self._current_gui
220 272
221 273 inputhook_manager = InputHookManager()
222 274
223 275 enable_wx = inputhook_manager.enable_wx
224 276 disable_wx = inputhook_manager.disable_wx
225 277 enable_qt4 = inputhook_manager.enable_qt4
226 278 disable_qt4 = inputhook_manager.disable_qt4
227 279 enable_gtk = inputhook_manager.enable_gtk
228 280 disable_gtk = inputhook_manager.disable_gtk
229 281 enable_tk = inputhook_manager.enable_tk
230 282 disable_tk = inputhook_manager.disable_tk
231 283 clear_inputhook = inputhook_manager.clear_inputhook
232 284 set_inputhook = inputhook_manager.set_inputhook
233 current_gui = inputhook_manager.current_gui No newline at end of file
285 current_gui = inputhook_manager.current_gui
286 clear_app_refs = inputhook_manager.clear_app_refs
@@ -1,153 +1,162 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3
4 4 """
5 5 Enable wxPython to be used interacive by setting PyOS_InputHook.
6 6
7 7 Authors: Robin Dunn, Brian Granger, Ondrej Certik
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2009 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import os
22 import signal
22 23 import sys
23 24 import time
24 25 from timeit import default_timer as clock
25 26 import wx
26 27
27 28 if os.name == 'posix':
28 29 import select
29 30 elif sys.platform == 'win32':
30 31 import msvcrt
31 32
32 33 #-----------------------------------------------------------------------------
33 34 # Code
34 35 #-----------------------------------------------------------------------------
35 36
36 37 def stdin_ready():
37 38 if os.name == 'posix':
38 39 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
39 40 if infds:
40 41 return True
41 42 else:
42 43 return False
43 44 elif sys.platform == 'win32':
44 45 return msvcrt.kbhit()
45 46
46 47
47 48 def inputhook_wx1():
48 49 """Run the wx event loop by processing pending events only.
49 50
50 51 This approach seems to work, but its performance is not great as it
51 52 relies on having PyOS_InputHook called regularly.
52 53 """
53 54 app = wx.GetApp()
54 55 if app is not None:
55 56 assert wx.Thread_IsMain()
56 57
57 58 # Make a temporary event loop and process system events until
58 59 # there are no more waiting, then allow idle events (which
59 60 # will also deal with pending or posted wx events.)
60 61 evtloop = wx.EventLoop()
61 62 ea = wx.EventLoopActivator(evtloop)
62 63 while evtloop.Pending():
63 64 evtloop.Dispatch()
64 65 app.ProcessIdle()
65 66 del ea
66 67 return 0
67 68
68 69 class EventLoopTimer(wx.Timer):
69 70
70 71 def __init__(self, func):
71 72 self.func = func
72 73 wx.Timer.__init__(self)
73 74
74 75 def Notify(self):
75 76 self.func()
76 77
77 78 class EventLoopRunner(object):
78 79
79 80 def Run(self, time):
80 81 self.evtloop = wx.EventLoop()
81 82 self.timer = EventLoopTimer(self.check_stdin)
82 83 self.timer.Start(time)
83 84 self.evtloop.Run()
84 85
85 86 def check_stdin(self):
86 87 if stdin_ready():
87 88 self.timer.Stop()
88 89 self.evtloop.Exit()
89 90
90 91 def inputhook_wx2():
91 92 """Run the wx event loop, polling for stdin.
92 93
93 94 This version runs the wx eventloop for an undetermined amount of time,
94 95 during which it periodically checks to see if anything is ready on
95 96 stdin. If anything is ready on stdin, the event loop exits.
96 97
97 98 The argument to elr.Run controls how often the event loop looks at stdin.
98 99 This determines the responsiveness at the keyboard. A setting of 1000
99 100 enables a user to type at most 1 char per second. I have found that a
100 101 setting of 10 gives good keyboard response. We can shorten it further,
101 102 but eventually performance would suffer from calling select/kbhit too
102 103 often.
103 104 """
104 105 app = wx.GetApp()
105 106 if app is not None:
106 107 assert wx.Thread_IsMain()
107 108 elr = EventLoopRunner()
108 109 # As this time is made shorter, keyboard response improves, but idle
109 110 # CPU load goes up. 10 ms seems like a good compromise.
110 111 elr.Run(time=10) # CHANGE time here to control polling interval
111 112 return 0
112 113
113 114 def inputhook_wx3():
114 115 """Run the wx event loop by processing pending events only.
115 116
116 117 This is like inputhook_wx1, but it keeps processing pending events
117 118 until stdin is ready. After processing all pending events, a call to
118 119 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
119 120 This sleep time should be tuned though for best performance.
120 121 """
121 122 app = wx.GetApp()
122 123 if app is not None:
123 124 assert wx.Thread_IsMain()
124 125
126 # The import of wx on Linux sets the handler for signal.SIGINT
127 # to 0. This is a bug in wx or gtk. We fix by just setting it
128 # back to the Python default.
129 if not callable(signal.getsignal(signal.SIGINT)):
130 signal.signal(signal.SIGINT, signal.default_int_handler)
131
125 132 evtloop = wx.EventLoop()
126 133 ea = wx.EventLoopActivator(evtloop)
127 134 t = clock()
128 135 while not stdin_ready():
129 136 while evtloop.Pending():
130 137 t = clock()
131 138 evtloop.Dispatch()
132 139 app.ProcessIdle()
133 140 # We need to sleep at this point to keep the idle CPU load
134 141 # low. However, if sleep to long, GUI response is poor. As
135 142 # a compromise, we watch how often GUI events are being processed
136 143 # and switch between a short and long sleep time. Here are some
137 144 # stats useful in helping to tune this.
138 145 # time CPU load
139 146 # 0.001 13%
140 147 # 0.005 3%
141 148 # 0.01 1.5%
142 # 0.05 0.5%
149 # 0.05 0.5%
150 if clock()-t > 1.0:
151 time.sleep(1.0)
143 152 if clock()-t > 0.1:
144 153 # Few GUI events coming in, so we can sleep longer
145 154 time.sleep(0.05)
146 155 else:
147 156 # Many GUI events coming in, so sleep only very little
148 157 time.sleep(0.001)
149 158 del ea
150 159 return 0
151 160
152 161 # This is our default implementation
153 162 inputhook_wx = inputhook_wx3 No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now