##// END OF EJS Templates
inputhook: make stdin_ready() function reusable...
Christian Boos -
Show More
@@ -1,450 +1,465 b''
1 1 # coding: utf-8
2 2 """
3 3 Inputhook management for GUI event loop integration.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2009 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import ctypes
18 import os
18 19 import sys
19 20 import warnings
21 if os.name == 'posix':
22 import select
23 elif sys.platform == 'win32':
24 import msvcrt
20 25
21 26 #-----------------------------------------------------------------------------
22 27 # Constants
23 28 #-----------------------------------------------------------------------------
24 29
25 30 # Constants for identifying the GUI toolkits.
26 31 GUI_WX = 'wx'
27 32 GUI_QT = 'qt'
28 33 GUI_QT4 = 'qt4'
29 34 GUI_GTK = 'gtk'
30 35 GUI_TK = 'tk'
31 36 GUI_OSX = 'osx'
32 37 GUI_GLUT = 'glut'
33 38 GUI_PYGLET = 'pyglet'
34 39
35 40 #-----------------------------------------------------------------------------
36 # Utility classes
41 # Utilities
37 42 #-----------------------------------------------------------------------------
38 43
44 def stdin_ready():
45 if os.name == 'posix':
46 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
47 if infds:
48 return True
49 else:
50 return False
51 elif sys.platform == 'win32':
52 return msvcrt.kbhit()
53
39 54
40 55 #-----------------------------------------------------------------------------
41 56 # Main InputHookManager class
42 57 #-----------------------------------------------------------------------------
43 58
44 59
45 60 class InputHookManager(object):
46 61 """Manage PyOS_InputHook for different GUI toolkits.
47 62
48 63 This class installs various hooks under ``PyOSInputHook`` to handle
49 64 GUI event loop integration.
50 65 """
51 66
52 67 def __init__(self):
53 68 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
54 69 self._apps = {}
55 70 self._reset()
56 71
57 72 def _reset(self):
58 73 self._callback_pyfunctype = None
59 74 self._callback = None
60 75 self._installed = False
61 76 self._current_gui = None
62 77
63 78 def get_pyos_inputhook(self):
64 79 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
65 80 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
66 81
67 82 def get_pyos_inputhook_as_func(self):
68 83 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
69 84 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
70 85
71 86 def set_inputhook(self, callback):
72 87 """Set PyOS_InputHook to callback and return the previous one."""
73 88 self._callback = callback
74 89 self._callback_pyfunctype = self.PYFUNC(callback)
75 90 pyos_inputhook_ptr = self.get_pyos_inputhook()
76 91 original = self.get_pyos_inputhook_as_func()
77 92 pyos_inputhook_ptr.value = \
78 93 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
79 94 self._installed = True
80 95 return original
81 96
82 97 def clear_inputhook(self, app=None):
83 98 """Set PyOS_InputHook to NULL and return the previous one.
84 99
85 100 Parameters
86 101 ----------
87 102 app : optional, ignored
88 103 This parameter is allowed only so that clear_inputhook() can be
89 104 called with a similar interface as all the ``enable_*`` methods. But
90 105 the actual value of the parameter is ignored. This uniform interface
91 106 makes it easier to have user-level entry points in the main IPython
92 107 app like :meth:`enable_gui`."""
93 108 pyos_inputhook_ptr = self.get_pyos_inputhook()
94 109 original = self.get_pyos_inputhook_as_func()
95 110 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
96 111 self._reset()
97 112 return original
98 113
99 114 def clear_app_refs(self, gui=None):
100 115 """Clear IPython's internal reference to an application instance.
101 116
102 117 Whenever we create an app for a user on qt4 or wx, we hold a
103 118 reference to the app. This is needed because in some cases bad things
104 119 can happen if a user doesn't hold a reference themselves. This
105 120 method is provided to clear the references we are holding.
106 121
107 122 Parameters
108 123 ----------
109 124 gui : None or str
110 125 If None, clear all app references. If ('wx', 'qt4') clear
111 126 the app for that toolkit. References are not held for gtk or tk
112 127 as those toolkits don't have the notion of an app.
113 128 """
114 129 if gui is None:
115 130 self._apps = {}
116 131 elif self._apps.has_key(gui):
117 132 del self._apps[gui]
118 133
119 134 def enable_wx(self, app=None):
120 135 """Enable event loop integration with wxPython.
121 136
122 137 Parameters
123 138 ----------
124 139 app : WX Application, optional.
125 140 Running application to use. If not given, we probe WX for an
126 141 existing application object, and create a new one if none is found.
127 142
128 143 Notes
129 144 -----
130 145 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
131 146 the wxPython to integrate with terminal based applications like
132 147 IPython.
133 148
134 149 If ``app`` is not given we probe for an existing one, and return it if
135 150 found. If no existing app is found, we create an :class:`wx.App` as
136 151 follows::
137 152
138 153 import wx
139 154 app = wx.App(redirect=False, clearSigInt=False)
140 155 """
141 156 from IPython.lib.inputhookwx import inputhook_wx
142 157 self.set_inputhook(inputhook_wx)
143 158 self._current_gui = GUI_WX
144 159 import wx
145 160 if app is None:
146 161 app = wx.GetApp()
147 162 if app is None:
148 163 app = wx.App(redirect=False, clearSigInt=False)
149 164 app._in_event_loop = True
150 165 self._apps[GUI_WX] = app
151 166 return app
152 167
153 168 def disable_wx(self):
154 169 """Disable event loop integration with wxPython.
155 170
156 171 This merely sets PyOS_InputHook to NULL.
157 172 """
158 173 if self._apps.has_key(GUI_WX):
159 174 self._apps[GUI_WX]._in_event_loop = False
160 175 self.clear_inputhook()
161 176
162 177 def enable_qt4(self, app=None):
163 178 """Enable event loop integration with PyQt4.
164 179
165 180 Parameters
166 181 ----------
167 182 app : Qt Application, optional.
168 183 Running application to use. If not given, we probe Qt for an
169 184 existing application object, and create a new one if none is found.
170 185
171 186 Notes
172 187 -----
173 188 This methods sets the PyOS_InputHook for PyQt4, which allows
174 189 the PyQt4 to integrate with terminal based applications like
175 190 IPython.
176 191
177 192 If ``app`` is not given we probe for an existing one, and return it if
178 193 found. If no existing app is found, we create an :class:`QApplication`
179 194 as follows::
180 195
181 196 from PyQt4 import QtCore
182 197 app = QtGui.QApplication(sys.argv)
183 198 """
184 199 from IPython.external.qt_for_kernel import QtCore, QtGui
185 200
186 201 if 'pyreadline' in sys.modules:
187 202 # see IPython GitHub Issue #281 for more info on this issue
188 203 # Similar intermittent behavior has been reported on OSX,
189 204 # but not consistently reproducible
190 205 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
191 206 in interactive input. If you do see this issue, we recommend using another GUI
192 207 toolkit if you can, or disable readline with the configuration option
193 208 'TerminalInteractiveShell.readline_use=False', specified in a config file or
194 209 at the command-line""",
195 210 RuntimeWarning)
196 211
197 212 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
198 213 # was set when QtCore was imported, but if it ever got removed,
199 214 # you couldn't reset it. For earlier versions we can
200 215 # probably implement a ctypes version.
201 216 try:
202 217 QtCore.pyqtRestoreInputHook()
203 218 except AttributeError:
204 219 pass
205 220
206 221 self._current_gui = GUI_QT4
207 222 if app is None:
208 223 app = QtCore.QCoreApplication.instance()
209 224 if app is None:
210 225 app = QtGui.QApplication([" "])
211 226 app._in_event_loop = True
212 227 self._apps[GUI_QT4] = app
213 228 return app
214 229
215 230 def disable_qt4(self):
216 231 """Disable event loop integration with PyQt4.
217 232
218 233 This merely sets PyOS_InputHook to NULL.
219 234 """
220 235 if self._apps.has_key(GUI_QT4):
221 236 self._apps[GUI_QT4]._in_event_loop = False
222 237 self.clear_inputhook()
223 238
224 239 def enable_gtk(self, app=None):
225 240 """Enable event loop integration with PyGTK.
226 241
227 242 Parameters
228 243 ----------
229 244 app : ignored
230 245 Ignored, it's only a placeholder to keep the call signature of all
231 246 gui activation methods consistent, which simplifies the logic of
232 247 supporting magics.
233 248
234 249 Notes
235 250 -----
236 251 This methods sets the PyOS_InputHook for PyGTK, which allows
237 252 the PyGTK to integrate with terminal based applications like
238 253 IPython.
239 254 """
240 255 import gtk
241 256 try:
242 257 gtk.set_interactive(True)
243 258 self._current_gui = GUI_GTK
244 259 except AttributeError:
245 260 # For older versions of gtk, use our own ctypes version
246 261 from IPython.lib.inputhookgtk import inputhook_gtk
247 262 self.set_inputhook(inputhook_gtk)
248 263 self._current_gui = GUI_GTK
249 264
250 265 def disable_gtk(self):
251 266 """Disable event loop integration with PyGTK.
252 267
253 268 This merely sets PyOS_InputHook to NULL.
254 269 """
255 270 self.clear_inputhook()
256 271
257 272 def enable_tk(self, app=None):
258 273 """Enable event loop integration with Tk.
259 274
260 275 Parameters
261 276 ----------
262 277 app : toplevel :class:`Tkinter.Tk` widget, optional.
263 278 Running toplevel widget to use. If not given, we probe Tk for an
264 279 existing one, and create a new one if none is found.
265 280
266 281 Notes
267 282 -----
268 283 If you have already created a :class:`Tkinter.Tk` object, the only
269 284 thing done by this method is to register with the
270 285 :class:`InputHookManager`, since creating that object automatically
271 286 sets ``PyOS_InputHook``.
272 287 """
273 288 self._current_gui = GUI_TK
274 289 if app is None:
275 290 import Tkinter
276 291 app = Tkinter.Tk()
277 292 app.withdraw()
278 293 self._apps[GUI_TK] = app
279 294 return app
280 295
281 296 def disable_tk(self):
282 297 """Disable event loop integration with Tkinter.
283 298
284 299 This merely sets PyOS_InputHook to NULL.
285 300 """
286 301 self.clear_inputhook()
287 302
288 303
289 304 def enable_glut(self, app=None):
290 305 """ Enable event loop integration with GLUT.
291 306
292 307 Parameters
293 308 ----------
294 309
295 310 app : ignored
296 311 Ignored, it's only a placeholder to keep the call signature of all
297 312 gui activation methods consistent, which simplifies the logic of
298 313 supporting magics.
299 314
300 315 Notes
301 316 -----
302 317
303 318 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
304 319 integrate with terminal based applications like IPython. Due to GLUT
305 320 limitations, it is currently not possible to start the event loop
306 321 without first creating a window. You should thus not create another
307 322 window but use instead the created one. See 'gui-glut.py' in the
308 323 docs/examples/lib directory.
309 324
310 325 The default screen mode is set to:
311 326 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
312 327 """
313 328
314 329 import OpenGL.GLUT as glut
315 330 from IPython.lib.inputhookglut import glut_display_mode, \
316 331 glut_close, glut_display, \
317 332 glut_idle, inputhook_glut
318 333
319 334 if not self._apps.has_key( GUI_GLUT ):
320 335 glut.glutInit( sys.argv )
321 336 glut.glutInitDisplayMode( glut_display_mode )
322 337 # This is specific to freeglut
323 338 if bool(glut.glutSetOption):
324 339 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
325 340 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
326 341 glut.glutCreateWindow( sys.argv[0] )
327 342 glut.glutReshapeWindow( 1, 1 )
328 343 glut.glutHideWindow( )
329 344 glut.glutWMCloseFunc( glut_close )
330 345 glut.glutDisplayFunc( glut_display )
331 346 glut.glutIdleFunc( glut_idle )
332 347 else:
333 348 glut.glutWMCloseFunc( glut_close )
334 349 glut.glutDisplayFunc( glut_display )
335 350 glut.glutIdleFunc( glut_idle)
336 351 self.set_inputhook( inputhook_glut )
337 352 self._current_gui = GUI_GLUT
338 353 self._apps[GUI_GLUT] = True
339 354
340 355
341 356 def disable_glut(self):
342 357 """Disable event loop integration with glut.
343 358
344 359 This sets PyOS_InputHook to NULL and set the display function to a
345 360 dummy one and set the timer to a dummy timer that will be triggered
346 361 very far in the future.
347 362 """
348 363 import OpenGL.GLUT as glut
349 364 from glut_support import glutMainLoopEvent
350 365
351 366 glut.glutHideWindow() # This is an event to be processed below
352 367 glutMainLoopEvent()
353 368 self.clear_inputhook()
354 369
355 370 def enable_pyglet(self, app=None):
356 371 """Enable event loop integration with pyglet.
357 372
358 373 Parameters
359 374 ----------
360 375 app : ignored
361 376 Ignored, it's only a placeholder to keep the call signature of all
362 377 gui activation methods consistent, which simplifies the logic of
363 378 supporting magics.
364 379
365 380 Notes
366 381 -----
367 382 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
368 383 pyglet to integrate with terminal based applications like
369 384 IPython.
370 385
371 386 """
372 387 import pyglet
373 388 from IPython.lib.inputhookpyglet import inputhook_pyglet
374 389 self.set_inputhook(inputhook_pyglet)
375 390 self._current_gui = GUI_PYGLET
376 391 return app
377 392
378 393 def disable_pyglet(self):
379 394 """Disable event loop integration with pyglet.
380 395
381 396 This merely sets PyOS_InputHook to NULL.
382 397 """
383 398 self.clear_inputhook()
384 399
385 400 def current_gui(self):
386 401 """Return a string indicating the currently active GUI or None."""
387 402 return self._current_gui
388 403
389 404 inputhook_manager = InputHookManager()
390 405
391 406 enable_wx = inputhook_manager.enable_wx
392 407 disable_wx = inputhook_manager.disable_wx
393 408 enable_qt4 = inputhook_manager.enable_qt4
394 409 disable_qt4 = inputhook_manager.disable_qt4
395 410 enable_gtk = inputhook_manager.enable_gtk
396 411 disable_gtk = inputhook_manager.disable_gtk
397 412 enable_tk = inputhook_manager.enable_tk
398 413 disable_tk = inputhook_manager.disable_tk
399 414 enable_glut = inputhook_manager.enable_glut
400 415 disable_glut = inputhook_manager.disable_glut
401 416 enable_pyglet = inputhook_manager.enable_pyglet
402 417 disable_pyglet = inputhook_manager.disable_pyglet
403 418 clear_inputhook = inputhook_manager.clear_inputhook
404 419 set_inputhook = inputhook_manager.set_inputhook
405 420 current_gui = inputhook_manager.current_gui
406 421 clear_app_refs = inputhook_manager.clear_app_refs
407 422
408 423
409 424 # Convenience function to switch amongst them
410 425 def enable_gui(gui=None, app=None):
411 426 """Switch amongst GUI input hooks by name.
412 427
413 428 This is just a utility wrapper around the methods of the InputHookManager
414 429 object.
415 430
416 431 Parameters
417 432 ----------
418 433 gui : optional, string or None
419 434 If None, clears input hook, otherwise it must be one of the recognized
420 435 GUI names (see ``GUI_*`` constants in module).
421 436
422 437 app : optional, existing application object.
423 438 For toolkits that have the concept of a global app, you can supply an
424 439 existing one. If not given, the toolkit will be probed for one, and if
425 440 none is found, a new one will be created. Note that GTK does not have
426 441 this concept, and passing an app if `gui`=="GTK" will raise an error.
427 442
428 443 Returns
429 444 -------
430 445 The output of the underlying gui switch routine, typically the actual
431 446 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
432 447 one.
433 448 """
434 449 guis = {None: clear_inputhook,
435 450 GUI_OSX: lambda app=False: None,
436 451 GUI_TK: enable_tk,
437 452 GUI_GTK: enable_gtk,
438 453 GUI_WX: enable_wx,
439 454 GUI_QT: enable_qt4, # qt3 not supported
440 455 GUI_QT4: enable_qt4,
441 456 GUI_GLUT: enable_glut,
442 457 GUI_PYGLET: enable_pyglet,
443 458 }
444 459 try:
445 460 gui_hook = guis[gui]
446 461 except KeyError:
447 462 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
448 463 raise ValueError(e)
449 464 return gui_hook(app)
450 465
@@ -1,178 +1,165 b''
1 1 # encoding: utf-8
2 2
3 3 """
4 4 Enable wxPython to be used interacive by setting PyOS_InputHook.
5 5
6 6 Authors: Robin Dunn, Brian Granger, Ondrej Certik
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2009 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import os
21 21 import signal
22 22 import sys
23 23 import time
24 24 from timeit import default_timer as clock
25 25 import wx
26 26
27 if os.name == 'posix':
28 import select
29 elif sys.platform == 'win32':
30 import msvcrt
27 from IPython.lib.inputhook import stdin_ready
28
31 29
32 30 #-----------------------------------------------------------------------------
33 31 # Code
34 32 #-----------------------------------------------------------------------------
35 33
36 def stdin_ready():
37 if os.name == 'posix':
38 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
39 if infds:
40 return True
41 else:
42 return False
43 elif sys.platform == 'win32':
44 return msvcrt.kbhit()
45
46
47 34 def inputhook_wx1():
48 35 """Run the wx event loop by processing pending events only.
49 36
50 37 This approach seems to work, but its performance is not great as it
51 38 relies on having PyOS_InputHook called regularly.
52 39 """
53 40 try:
54 41 app = wx.GetApp()
55 42 if app is not None:
56 43 assert wx.Thread_IsMain()
57 44
58 45 # Make a temporary event loop and process system events until
59 46 # there are no more waiting, then allow idle events (which
60 47 # will also deal with pending or posted wx events.)
61 48 evtloop = wx.EventLoop()
62 49 ea = wx.EventLoopActivator(evtloop)
63 50 while evtloop.Pending():
64 51 evtloop.Dispatch()
65 52 app.ProcessIdle()
66 53 del ea
67 54 except KeyboardInterrupt:
68 55 pass
69 56 return 0
70 57
71 58 class EventLoopTimer(wx.Timer):
72 59
73 60 def __init__(self, func):
74 61 self.func = func
75 62 wx.Timer.__init__(self)
76 63
77 64 def Notify(self):
78 65 self.func()
79 66
80 67 class EventLoopRunner(object):
81 68
82 69 def Run(self, time):
83 70 self.evtloop = wx.EventLoop()
84 71 self.timer = EventLoopTimer(self.check_stdin)
85 72 self.timer.Start(time)
86 73 self.evtloop.Run()
87 74
88 75 def check_stdin(self):
89 76 if stdin_ready():
90 77 self.timer.Stop()
91 78 self.evtloop.Exit()
92 79
93 80 def inputhook_wx2():
94 81 """Run the wx event loop, polling for stdin.
95 82
96 83 This version runs the wx eventloop for an undetermined amount of time,
97 84 during which it periodically checks to see if anything is ready on
98 85 stdin. If anything is ready on stdin, the event loop exits.
99 86
100 87 The argument to elr.Run controls how often the event loop looks at stdin.
101 88 This determines the responsiveness at the keyboard. A setting of 1000
102 89 enables a user to type at most 1 char per second. I have found that a
103 90 setting of 10 gives good keyboard response. We can shorten it further,
104 91 but eventually performance would suffer from calling select/kbhit too
105 92 often.
106 93 """
107 94 try:
108 95 app = wx.GetApp()
109 96 if app is not None:
110 97 assert wx.Thread_IsMain()
111 98 elr = EventLoopRunner()
112 99 # As this time is made shorter, keyboard response improves, but idle
113 100 # CPU load goes up. 10 ms seems like a good compromise.
114 101 elr.Run(time=10) # CHANGE time here to control polling interval
115 102 except KeyboardInterrupt:
116 103 pass
117 104 return 0
118 105
119 106 def inputhook_wx3():
120 107 """Run the wx event loop by processing pending events only.
121 108
122 109 This is like inputhook_wx1, but it keeps processing pending events
123 110 until stdin is ready. After processing all pending events, a call to
124 111 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
125 112 This sleep time should be tuned though for best performance.
126 113 """
127 114 # We need to protect against a user pressing Control-C when IPython is
128 115 # idle and this is running. We trap KeyboardInterrupt and pass.
129 116 try:
130 117 app = wx.GetApp()
131 118 if app is not None:
132 119 assert wx.Thread_IsMain()
133 120
134 121 # The import of wx on Linux sets the handler for signal.SIGINT
135 122 # to 0. This is a bug in wx or gtk. We fix by just setting it
136 123 # back to the Python default.
137 124 if not callable(signal.getsignal(signal.SIGINT)):
138 125 signal.signal(signal.SIGINT, signal.default_int_handler)
139 126
140 127 evtloop = wx.EventLoop()
141 128 ea = wx.EventLoopActivator(evtloop)
142 129 t = clock()
143 130 while not stdin_ready():
144 131 while evtloop.Pending():
145 132 t = clock()
146 133 evtloop.Dispatch()
147 134 app.ProcessIdle()
148 135 # We need to sleep at this point to keep the idle CPU load
149 136 # low. However, if sleep to long, GUI response is poor. As
150 137 # a compromise, we watch how often GUI events are being processed
151 138 # and switch between a short and long sleep time. Here are some
152 139 # stats useful in helping to tune this.
153 140 # time CPU load
154 141 # 0.001 13%
155 142 # 0.005 3%
156 143 # 0.01 1.5%
157 144 # 0.05 0.5%
158 145 used_time = clock() - t
159 146 if used_time > 5*60.0:
160 147 # print 'Sleep for 5 s' # dbg
161 148 time.sleep(5.0)
162 149 elif used_time > 10.0:
163 150 # print 'Sleep for 1 s' # dbg
164 151 time.sleep(1.0)
165 152 elif used_time > 0.1:
166 153 # Few GUI events coming in, so we can sleep longer
167 154 # print 'Sleep for 0.05 s' # dbg
168 155 time.sleep(0.05)
169 156 else:
170 157 # Many GUI events coming in, so sleep only very little
171 158 time.sleep(0.001)
172 159 del ea
173 160 except KeyboardInterrupt:
174 161 pass
175 162 return 0
176 163
177 164 # This is our default implementation
178 165 inputhook_wx = inputhook_wx3
General Comments 0
You need to be logged in to leave comments. Login now