##// END OF EJS Templates
Merge pull request #815 from cboos/issue481-qt4-input-hook...
Fernando Perez -
r5215:1f7e9d75 merge
parent child Browse files
Show More
@@ -0,0 +1,124
1 # -*- coding: utf-8 -*-
2 """
3 Qt4's inputhook support function
4
5 Author: Christian Boos
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 from IPython.core.interactiveshell import InteractiveShell
20 from IPython.external.qt_for_kernel import QtCore, QtGui
21 from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
22
23 #-----------------------------------------------------------------------------
24 # Code
25 #-----------------------------------------------------------------------------
26
27 def create_inputhook_qt4(mgr, app=None):
28 """Create an input hook for running the Qt4 application event loop.
29
30 Parameters
31 ----------
32 mgr : an InputHookManager
33
34 app : Qt Application, optional.
35 Running application to use. If not given, we probe Qt for an
36 existing application object, and create a new one if none is found.
37
38 Returns
39 -------
40 A pair consisting of a Qt Application (either the one given or the
41 one found or created) and a inputhook.
42
43 Notes
44 -----
45 We use a custom input hook instead of PyQt4's default one, as it
46 interacts better with the readline packages (issue #481).
47
48 The inputhook function works in tandem with a 'pre_prompt_hook'
49 which automatically restores the hook as an inputhook in case the
50 latter has been temporarily disabled after having intercepted a
51 KeyboardInterrupt.
52 """
53
54 if app is None:
55 app = QtCore.QCoreApplication.instance()
56 if app is None:
57 app = QtGui.QApplication([" "])
58
59 # Re-use previously created inputhook if any
60 ip = InteractiveShell.instance()
61 if hasattr(ip, '_inputhook_qt4'):
62 return app, ip._inputhook_qt4
63
64 # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of
65 # hooks (they both share the got_kbdint flag)
66
67 got_kbdint = [False]
68
69 def inputhook_qt4():
70 """PyOS_InputHook python hook for Qt4.
71
72 Process pending Qt events and if there's no pending keyboard
73 input, spend a short slice of time (50ms) running the Qt event
74 loop.
75
76 As a Python ctypes callback can't raise an exception, we catch
77 the KeyboardInterrupt and temporarily deactivate the hook,
78 which will let a *second* CTRL+C be processed normally and go
79 back to a clean prompt line.
80 """
81 try:
82 allow_CTRL_C()
83 app = QtCore.QCoreApplication.instance()
84 if not app: # shouldn't happen, but safer if it happens anyway...
85 return 0
86 app.processEvents(QtCore.QEventLoop.AllEvents, 300)
87 if not stdin_ready():
88 timer = QtCore.QTimer()
89 timer.timeout.connect(app.quit)
90 while not stdin_ready():
91 timer.start(50)
92 app.exec_()
93 timer.stop()
94 ignore_CTRL_C()
95 except KeyboardInterrupt:
96 ignore_CTRL_C()
97 got_kbdint[0] = True
98 print("\nKeyboardInterrupt - qt4 event loop interrupted!"
99 "\n * hit CTRL+C again to clear the prompt"
100 "\n * use '%gui none' to disable the event loop"
101 " permanently"
102 "\n and '%gui qt4' to re-enable it later")
103 mgr.clear_inputhook()
104 except: # NO exceptions are allowed to escape from a ctypes callback
105 mgr.clear_inputhook()
106 from traceback import print_exc
107 print_exc()
108 print("Got exception from inputhook_qt4, unregistering.")
109 return 0
110
111 def preprompthook_qt4(ishell):
112 """'pre_prompt_hook' used to restore the Qt4 input hook
113
114 (in case the latter was temporarily deactivated after a
115 CTRL+C)
116 """
117 if got_kbdint[0]:
118 mgr.set_inputhook(inputhook_qt4)
119 got_kbdint[0] = False
120
121 ip._inputhook_qt4 = inputhook_qt4
122 ip.set_hook('pre_prompt_hook', preprompthook_qt4)
123
124 return app, inputhook_qt4
@@ -1,450 +1,483
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
20 21
21 22 #-----------------------------------------------------------------------------
22 23 # Constants
23 24 #-----------------------------------------------------------------------------
24 25
25 26 # Constants for identifying the GUI toolkits.
26 27 GUI_WX = 'wx'
27 28 GUI_QT = 'qt'
28 29 GUI_QT4 = 'qt4'
29 30 GUI_GTK = 'gtk'
30 31 GUI_TK = 'tk'
31 32 GUI_OSX = 'osx'
32 33 GUI_GLUT = 'glut'
33 34 GUI_PYGLET = 'pyglet'
35 GUI_NONE = 'none' # i.e. disable
34 36
35 37 #-----------------------------------------------------------------------------
36 # Utility classes
38 # Utilities
37 39 #-----------------------------------------------------------------------------
38 40
41 def _stdin_ready_posix():
42 """Return True if there's something to read on stdin (posix version)."""
43 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
44 return bool(infds)
45
46 def _stdin_ready_nt():
47 """Return True if there's something to read on stdin (nt version)."""
48 return msvcrt.kbhit()
49
50 def _stdin_ready_other():
51 """Return True, assuming there's something to read on stdin."""
52 return True #
53
54
55 def _ignore_CTRL_C_posix():
56 """Ignore CTRL+C (SIGINT)."""
57 signal.signal(signal.SIGINT, signal.SIG_IGN)
58
59 def _allow_CTRL_C_posix():
60 """Take CTRL+C into account (SIGINT)."""
61 signal.signal(signal.SIGINT, signal.default_int_handler)
62
63 def _ignore_CTRL_C_other():
64 """Ignore CTRL+C (not implemented)."""
65 pass
66
67 def _allow_CTRL_C_other():
68 """Take CTRL+C into account (not implemented)."""
69 pass
70
71 if os.name == 'posix':
72 import select
73 import signal
74 stdin_ready = _stdin_ready_posix
75 ignore_CTRL_C = _ignore_CTRL_C_posix
76 allow_CTRL_C = _allow_CTRL_C_posix
77 elif os.name == 'nt':
78 import msvcrt
79 stdin_ready = _stdin_ready_nt
80 ignore_CTRL_C = _ignore_CTRL_C_other
81 allow_CTRL_C = _allow_CTRL_C_other
82 else:
83 stdin_ready = _stdin_ready_other
84 ignore_CTRL_C = _ignore_CTRL_C_other
85 allow_CTRL_C = _allow_CTRL_C_other
86
39 87
40 88 #-----------------------------------------------------------------------------
41 89 # Main InputHookManager class
42 90 #-----------------------------------------------------------------------------
43 91
44 92
45 93 class InputHookManager(object):
46 94 """Manage PyOS_InputHook for different GUI toolkits.
47 95
48 96 This class installs various hooks under ``PyOSInputHook`` to handle
49 97 GUI event loop integration.
50 98 """
51 99
52 100 def __init__(self):
53 101 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
54 102 self._apps = {}
55 103 self._reset()
56 104
57 105 def _reset(self):
58 106 self._callback_pyfunctype = None
59 107 self._callback = None
60 108 self._installed = False
61 109 self._current_gui = None
62 110
63 111 def get_pyos_inputhook(self):
64 112 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
65 113 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
66 114
67 115 def get_pyos_inputhook_as_func(self):
68 116 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
69 117 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
70 118
71 119 def set_inputhook(self, callback):
72 120 """Set PyOS_InputHook to callback and return the previous one."""
121 # On platforms with 'readline' support, it's all too likely to
122 # have a KeyboardInterrupt signal delivered *even before* an
123 # initial ``try:`` clause in the callback can be executed, so
124 # we need to disable CTRL+C in this situation.
125 ignore_CTRL_C()
73 126 self._callback = callback
74 127 self._callback_pyfunctype = self.PYFUNC(callback)
75 128 pyos_inputhook_ptr = self.get_pyos_inputhook()
76 129 original = self.get_pyos_inputhook_as_func()
77 130 pyos_inputhook_ptr.value = \
78 131 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
79 132 self._installed = True
80 133 return original
81 134
82 135 def clear_inputhook(self, app=None):
83 136 """Set PyOS_InputHook to NULL and return the previous one.
84 137
85 138 Parameters
86 139 ----------
87 140 app : optional, ignored
88 141 This parameter is allowed only so that clear_inputhook() can be
89 142 called with a similar interface as all the ``enable_*`` methods. But
90 143 the actual value of the parameter is ignored. This uniform interface
91 144 makes it easier to have user-level entry points in the main IPython
92 145 app like :meth:`enable_gui`."""
93 146 pyos_inputhook_ptr = self.get_pyos_inputhook()
94 147 original = self.get_pyos_inputhook_as_func()
95 148 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
149 allow_CTRL_C()
96 150 self._reset()
97 151 return original
98 152
99 153 def clear_app_refs(self, gui=None):
100 154 """Clear IPython's internal reference to an application instance.
101 155
102 156 Whenever we create an app for a user on qt4 or wx, we hold a
103 157 reference to the app. This is needed because in some cases bad things
104 158 can happen if a user doesn't hold a reference themselves. This
105 159 method is provided to clear the references we are holding.
106 160
107 161 Parameters
108 162 ----------
109 163 gui : None or str
110 164 If None, clear all app references. If ('wx', 'qt4') clear
111 165 the app for that toolkit. References are not held for gtk or tk
112 166 as those toolkits don't have the notion of an app.
113 167 """
114 168 if gui is None:
115 169 self._apps = {}
116 170 elif self._apps.has_key(gui):
117 171 del self._apps[gui]
118 172
119 173 def enable_wx(self, app=None):
120 174 """Enable event loop integration with wxPython.
121 175
122 176 Parameters
123 177 ----------
124 178 app : WX Application, optional.
125 179 Running application to use. If not given, we probe WX for an
126 180 existing application object, and create a new one if none is found.
127 181
128 182 Notes
129 183 -----
130 184 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
131 185 the wxPython to integrate with terminal based applications like
132 186 IPython.
133 187
134 188 If ``app`` is not given we probe for an existing one, and return it if
135 189 found. If no existing app is found, we create an :class:`wx.App` as
136 190 follows::
137 191
138 192 import wx
139 193 app = wx.App(redirect=False, clearSigInt=False)
140 194 """
141 195 from IPython.lib.inputhookwx import inputhook_wx
142 196 self.set_inputhook(inputhook_wx)
143 197 self._current_gui = GUI_WX
144 198 import wx
145 199 if app is None:
146 200 app = wx.GetApp()
147 201 if app is None:
148 202 app = wx.App(redirect=False, clearSigInt=False)
149 203 app._in_event_loop = True
150 204 self._apps[GUI_WX] = app
151 205 return app
152 206
153 207 def disable_wx(self):
154 208 """Disable event loop integration with wxPython.
155 209
156 210 This merely sets PyOS_InputHook to NULL.
157 211 """
158 212 if self._apps.has_key(GUI_WX):
159 213 self._apps[GUI_WX]._in_event_loop = False
160 214 self.clear_inputhook()
161 215
162 216 def enable_qt4(self, app=None):
163 217 """Enable event loop integration with PyQt4.
164 218
165 219 Parameters
166 220 ----------
167 221 app : Qt Application, optional.
168 222 Running application to use. If not given, we probe Qt for an
169 223 existing application object, and create a new one if none is found.
170 224
171 225 Notes
172 226 -----
173 227 This methods sets the PyOS_InputHook for PyQt4, which allows
174 228 the PyQt4 to integrate with terminal based applications like
175 229 IPython.
176 230
177 231 If ``app`` is not given we probe for an existing one, and return it if
178 232 found. If no existing app is found, we create an :class:`QApplication`
179 233 as follows::
180 234
181 235 from PyQt4 import QtCore
182 236 app = QtGui.QApplication(sys.argv)
183 237 """
184 from IPython.external.qt_for_kernel import QtCore, QtGui
185
186 if 'pyreadline' in sys.modules:
187 # see IPython GitHub Issue #281 for more info on this issue
188 # Similar intermittent behavior has been reported on OSX,
189 # but not consistently reproducible
190 warnings.warn("""PyReadline's inputhook can conflict with Qt, causing delays
191 in interactive input. If you do see this issue, we recommend using another GUI
192 toolkit if you can, or disable readline with the configuration option
193 'TerminalInteractiveShell.readline_use=False', specified in a config file or
194 at the command-line""",
195 RuntimeWarning)
196
197 # PyQt4 has had this since 4.3.1. In version 4.2, PyOS_InputHook
198 # was set when QtCore was imported, but if it ever got removed,
199 # you couldn't reset it. For earlier versions we can
200 # probably implement a ctypes version.
201 try:
202 QtCore.pyqtRestoreInputHook()
203 except AttributeError:
204 pass
238 from IPython.lib.inputhookqt4 import create_inputhook_qt4
239 app, inputhook_qt4 = create_inputhook_qt4(self, app)
240 self.set_inputhook(inputhook_qt4)
205 241
206 242 self._current_gui = GUI_QT4
207 if app is None:
208 app = QtCore.QCoreApplication.instance()
209 if app is None:
210 app = QtGui.QApplication([" "])
211 243 app._in_event_loop = True
212 244 self._apps[GUI_QT4] = app
213 245 return app
214 246
215 247 def disable_qt4(self):
216 248 """Disable event loop integration with PyQt4.
217 249
218 250 This merely sets PyOS_InputHook to NULL.
219 251 """
220 252 if self._apps.has_key(GUI_QT4):
221 253 self._apps[GUI_QT4]._in_event_loop = False
222 254 self.clear_inputhook()
223 255
224 256 def enable_gtk(self, app=None):
225 257 """Enable event loop integration with PyGTK.
226 258
227 259 Parameters
228 260 ----------
229 261 app : ignored
230 262 Ignored, it's only a placeholder to keep the call signature of all
231 263 gui activation methods consistent, which simplifies the logic of
232 264 supporting magics.
233 265
234 266 Notes
235 267 -----
236 268 This methods sets the PyOS_InputHook for PyGTK, which allows
237 269 the PyGTK to integrate with terminal based applications like
238 270 IPython.
239 271 """
240 272 import gtk
241 273 try:
242 274 gtk.set_interactive(True)
243 275 self._current_gui = GUI_GTK
244 276 except AttributeError:
245 277 # For older versions of gtk, use our own ctypes version
246 278 from IPython.lib.inputhookgtk import inputhook_gtk
247 279 self.set_inputhook(inputhook_gtk)
248 280 self._current_gui = GUI_GTK
249 281
250 282 def disable_gtk(self):
251 283 """Disable event loop integration with PyGTK.
252 284
253 285 This merely sets PyOS_InputHook to NULL.
254 286 """
255 287 self.clear_inputhook()
256 288
257 289 def enable_tk(self, app=None):
258 290 """Enable event loop integration with Tk.
259 291
260 292 Parameters
261 293 ----------
262 294 app : toplevel :class:`Tkinter.Tk` widget, optional.
263 295 Running toplevel widget to use. If not given, we probe Tk for an
264 296 existing one, and create a new one if none is found.
265 297
266 298 Notes
267 299 -----
268 300 If you have already created a :class:`Tkinter.Tk` object, the only
269 301 thing done by this method is to register with the
270 302 :class:`InputHookManager`, since creating that object automatically
271 303 sets ``PyOS_InputHook``.
272 304 """
273 305 self._current_gui = GUI_TK
274 306 if app is None:
275 307 import Tkinter
276 308 app = Tkinter.Tk()
277 309 app.withdraw()
278 310 self._apps[GUI_TK] = app
279 311 return app
280 312
281 313 def disable_tk(self):
282 314 """Disable event loop integration with Tkinter.
283 315
284 316 This merely sets PyOS_InputHook to NULL.
285 317 """
286 318 self.clear_inputhook()
287 319
288 320
289 321 def enable_glut(self, app=None):
290 322 """ Enable event loop integration with GLUT.
291 323
292 324 Parameters
293 325 ----------
294 326
295 327 app : ignored
296 328 Ignored, it's only a placeholder to keep the call signature of all
297 329 gui activation methods consistent, which simplifies the logic of
298 330 supporting magics.
299 331
300 332 Notes
301 333 -----
302 334
303 335 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
304 336 integrate with terminal based applications like IPython. Due to GLUT
305 337 limitations, it is currently not possible to start the event loop
306 338 without first creating a window. You should thus not create another
307 339 window but use instead the created one. See 'gui-glut.py' in the
308 340 docs/examples/lib directory.
309 341
310 342 The default screen mode is set to:
311 343 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
312 344 """
313 345
314 346 import OpenGL.GLUT as glut
315 347 from IPython.lib.inputhookglut import glut_display_mode, \
316 348 glut_close, glut_display, \
317 349 glut_idle, inputhook_glut
318 350
319 351 if not self._apps.has_key( GUI_GLUT ):
320 352 glut.glutInit( sys.argv )
321 353 glut.glutInitDisplayMode( glut_display_mode )
322 354 # This is specific to freeglut
323 355 if bool(glut.glutSetOption):
324 356 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
325 357 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
326 358 glut.glutCreateWindow( sys.argv[0] )
327 359 glut.glutReshapeWindow( 1, 1 )
328 360 glut.glutHideWindow( )
329 361 glut.glutWMCloseFunc( glut_close )
330 362 glut.glutDisplayFunc( glut_display )
331 363 glut.glutIdleFunc( glut_idle )
332 364 else:
333 365 glut.glutWMCloseFunc( glut_close )
334 366 glut.glutDisplayFunc( glut_display )
335 367 glut.glutIdleFunc( glut_idle)
336 368 self.set_inputhook( inputhook_glut )
337 369 self._current_gui = GUI_GLUT
338 370 self._apps[GUI_GLUT] = True
339 371
340 372
341 373 def disable_glut(self):
342 374 """Disable event loop integration with glut.
343 375
344 376 This sets PyOS_InputHook to NULL and set the display function to a
345 377 dummy one and set the timer to a dummy timer that will be triggered
346 378 very far in the future.
347 379 """
348 380 import OpenGL.GLUT as glut
349 381 from glut_support import glutMainLoopEvent
350 382
351 383 glut.glutHideWindow() # This is an event to be processed below
352 384 glutMainLoopEvent()
353 385 self.clear_inputhook()
354 386
355 387 def enable_pyglet(self, app=None):
356 388 """Enable event loop integration with pyglet.
357 389
358 390 Parameters
359 391 ----------
360 392 app : ignored
361 393 Ignored, it's only a placeholder to keep the call signature of all
362 394 gui activation methods consistent, which simplifies the logic of
363 395 supporting magics.
364 396
365 397 Notes
366 398 -----
367 399 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
368 400 pyglet to integrate with terminal based applications like
369 401 IPython.
370 402
371 403 """
372 404 import pyglet
373 405 from IPython.lib.inputhookpyglet import inputhook_pyglet
374 406 self.set_inputhook(inputhook_pyglet)
375 407 self._current_gui = GUI_PYGLET
376 408 return app
377 409
378 410 def disable_pyglet(self):
379 411 """Disable event loop integration with pyglet.
380 412
381 413 This merely sets PyOS_InputHook to NULL.
382 414 """
383 415 self.clear_inputhook()
384 416
385 417 def current_gui(self):
386 418 """Return a string indicating the currently active GUI or None."""
387 419 return self._current_gui
388 420
389 421 inputhook_manager = InputHookManager()
390 422
391 423 enable_wx = inputhook_manager.enable_wx
392 424 disable_wx = inputhook_manager.disable_wx
393 425 enable_qt4 = inputhook_manager.enable_qt4
394 426 disable_qt4 = inputhook_manager.disable_qt4
395 427 enable_gtk = inputhook_manager.enable_gtk
396 428 disable_gtk = inputhook_manager.disable_gtk
397 429 enable_tk = inputhook_manager.enable_tk
398 430 disable_tk = inputhook_manager.disable_tk
399 431 enable_glut = inputhook_manager.enable_glut
400 432 disable_glut = inputhook_manager.disable_glut
401 433 enable_pyglet = inputhook_manager.enable_pyglet
402 434 disable_pyglet = inputhook_manager.disable_pyglet
403 435 clear_inputhook = inputhook_manager.clear_inputhook
404 436 set_inputhook = inputhook_manager.set_inputhook
405 437 current_gui = inputhook_manager.current_gui
406 438 clear_app_refs = inputhook_manager.clear_app_refs
407 439
408 440
409 441 # Convenience function to switch amongst them
410 442 def enable_gui(gui=None, app=None):
411 443 """Switch amongst GUI input hooks by name.
412 444
413 445 This is just a utility wrapper around the methods of the InputHookManager
414 446 object.
415 447
416 448 Parameters
417 449 ----------
418 450 gui : optional, string or None
419 If None, clears input hook, otherwise it must be one of the recognized
420 GUI names (see ``GUI_*`` constants in module).
451 If None (or 'none'), clears input hook, otherwise it must be one
452 of the recognized GUI names (see ``GUI_*`` constants in module).
421 453
422 454 app : optional, existing application object.
423 455 For toolkits that have the concept of a global app, you can supply an
424 456 existing one. If not given, the toolkit will be probed for one, and if
425 457 none is found, a new one will be created. Note that GTK does not have
426 458 this concept, and passing an app if `gui`=="GTK" will raise an error.
427 459
428 460 Returns
429 461 -------
430 462 The output of the underlying gui switch routine, typically the actual
431 463 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
432 464 one.
433 465 """
434 466 guis = {None: clear_inputhook,
467 GUI_NONE: clear_inputhook,
435 468 GUI_OSX: lambda app=False: None,
436 469 GUI_TK: enable_tk,
437 470 GUI_GTK: enable_gtk,
438 471 GUI_WX: enable_wx,
439 472 GUI_QT: enable_qt4, # qt3 not supported
440 473 GUI_QT4: enable_qt4,
441 474 GUI_GLUT: enable_glut,
442 475 GUI_PYGLET: enable_pyglet,
443 476 }
444 477 try:
445 478 gui_hook = guis[gui]
446 479 except KeyError:
447 480 e = "Invalid GUI request %r, valid ones are:%s" % (gui, guis.keys())
448 481 raise ValueError(e)
449 482 return gui_hook(app)
450 483
@@ -1,178 +1,165
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