Skip to content
This repository

Fix #481 using custom qt4 input hook #815

Merged
merged 15 commits into from over 2 years ago

4 participants

Christian Boos Thomas Kluyver Fernando Perez Min RK
Christian Boos

This is my tentative fix for issue #481, as discussed in #481 (comment).

This is not meant to be merged as such, as I only could provide limited testing:

  • seems to fix the issue for Windows (at least Win 7 x64)
  • no idea about the impact on Linux, Mac; at best it will also fix the issue for Mac ;-)
  • couldn't check if the refactoring done in the first cset 3d1dca8 didn't break Wx support (I don't have it installed)

Also, I've currently inlined the hook, as it was small enough it didn't feel appropriate to create a IPython/lib/inputhookqt.py file.

Finally, the behavior with Ctrl-C is currently quite catastrophic, though this is not related to my changes, as it's the same behavior with the original 0.11 install, even without Qt involved. But this probably another topic.


See second iteration for the merge candidate.

IPython/lib/inputhook.py
((5 lines not shown))
18 19
 import sys
19 20
 import warnings
20 21
 
  22
+if os.name == 'posix':
  23
+    import select
  24
+elif sys.platform == 'win32':
8
Thomas Kluyver Collaborator

Are there cases where neither of these are true? What would happen if so?

Also, a minor nitpick, is it necessary to use os.name for one test and sys.platform for the other? Can we get the information we need from just one?

Thomas Kluyver Collaborator

(OK, I see now this code is just moved from another file. It's still worth thinking about while we're looking at it, though)

Christian Boos
cboos added a note September 22, 2011

Well, you have the same if/elif test structure in the stdin_ready() function, so it will just work.
Perhaps it would be cleaner to move those imports in the stdin_ready() function directly?
(if that's not against your coding conventions)

Thomas Kluyver Collaborator

But if neither of these conditions were true, stdin_ready will always return None, which looks like it would put the code below into an infinite loop (I could be wrong, I don't do much GUI stuff).

Christian Boos
cboos added a note September 23, 2011

Correct! I don't think there's a supported platform which doesn't fall in either category, but anyway returning True as a fallback seems safer. My next iteration of the function will look like this:

def stdin_ready():
    if os.name == 'posix':
        import select
        infds, outfds, erfds = select.select([sys.stdin],[],[],0)
        if infds:
            return True
        else:
            return False
    elif sys.platform == 'win32':
        import msvcrt
        return msvcrt.kbhit()
    return True # assume there's something so that we won't wait forever

Also, I assume that the original combination of os.name/sys.platform was probably done on purpose: match Linux and Mac with the first test, and pick non-cygwin python on Windows with the second test.

Thomas Kluyver Collaborator

What are os.name and sys.platform inside cygwin? Does the function work properly in this case too?

Christian Boos
cboos added a note September 23, 2011

For cygwin, os.name is 'posix' and sys.platform is 'cygwin', so Cygwin is handled by the first case, environments which have select.
We could replace the second test with simply os.name == 'nt' if you prefer, the semantic will be the same if the tests are done in that order.

(PS: I haven't tested in cygwin, I don't even know if it's a supported platform!)

Thomas Kluyver Collaborator

If testing for os.name == 'nt' will behave as expected, I think that's better than using two different variables to test. I've no idea if IPython can run on Cygwin, but if it can, I don't doubt there'll be someone using it.

FWIW, the Python docs describe sys.platform as having a "finer granularity" than os.name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/lib/inputhook.py
((25 lines not shown))
207 202
         if app is None:
208 203
             app = QtCore.QCoreApplication.instance()
209 204
         if app is None:
210 205
             app = QtGui.QApplication([" "])
  206
+
  207
+        if 'pyreadline' in sys.modules:
2
Thomas Kluyver Collaborator

This looks like we no longer do anything if pyreadline isn't in use (i.e. on any Linux or Mac platforms). Don't we need the pyqtRestoreInputHook() call in somewhere for the other platforms?

Christian Boos
cboos added a note September 22, 2011

Ah right. But actually what needs to be done here is to also activate the inputhook_qt4 for Mac, and probably for Linux as well (I think it wouldn't hurt either but I'll verify).

Without any readline package, the input hook gets called as well, but much less frequently: only once before waiting for a new complete input, not once after every character. That's why the delay induced by the default PyQt4 hook was not so noticeable in that situation.

So maybe we just need to remove that test altogether. If someone could verify the next changeset on a Mac, that would be great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christian Boos

Dealing with CTRL+C proves to be quite uneasy. First on Windows with Python 2.7, I see all the weird effects described in #625. The root cause might well be a Python bug, see e.g. http://bugs.python.org/issue1677 though in my case the python interpreter exits immediately, at the first CTRL+C).

So here, at the very least we need to trap KeyboardInterrupt in the hook, like it's done for other gui backends (6d5890c).

In 0dd489f, I also played with the idea of breaking the event loop processing with a first press to CTRL+C, which lets a second CTRL+C raise a proper KeyboardInterrupt and makes you return to the prompt. At this point the event processing is restored in the inputhook thanks to a pre_prompt_hook. I find this behavior to be quite OK (kind of fix for #122).

But that only works when you start IPython with python.exe ipython.py!
If instead you run ipython.exe, then no matter what, a CTRL+C will kill the ipython.exe process and leaves its python.exe subprocess and the shell to fight for the stdin...
If the shell is the cmd.exe, it will grab all the input until you "exit", at which point the python.exe will regain control.
If the shell is msys bash, then you have the impression that bash and the remaining python.exe get each a bit of stdin...
Hope my report is not too confusing, because the situation in the console really is ;-)

What I ideally would like to achieve is the 0dd489f behavior but also when starting IPython via ipython.exe.
Btw. any hints where to look for the ipython.exe -> python.exe code or design rationale? I suppose there must be a reason why there is a subprocess...

Thomas Kluyver
Collaborator

I'm not a Windows user, so I can't help you with the specifics there. I think ipython.exe is something created by Enthought for EPD, so you might want to get in touch with them for how that's put together.

Fernando Perez
Owner

Hey guys, just a quick note: I haven't followed the PR, but right now it doesn't merge cleanly anymore. Sorry about that, there's been enough churn on master lately that some PRs fell behind.

So this one needs a rebase before we can proceed further, let us know if you need a hand with that.

Unless you guys have decided you want to take a different approach altogether with a new PR, I'll leave that up to you. I just want to give you a heads-up b/c I'm starting to map out the landscape to plan for an 0.12 release, and un-mergeable PRs will be obviously an issue there.

Thomas Kluyver
Collaborator

I think a big PR that I merged carelessly may have caused various conflicts, so I'm happy to lend a hand rebasing if you like.

Christian Boos

I wanted to rework the changesets anyway to take the feedback into account, so I don't mind rebasing. I'll be able to create a new PR this week-end (or is there a way to associate a second branch or a rebased branch on the same PR?).

Thomas Kluyver
Collaborator

It's a bit 'impure', but if you rebase a branch and force-push it (i.e. push the new branch with the old name), it updates the pull request accordingly. We do that quite a bit.

Fernando Perez
Owner
added some commits September 22, 2011
Christian Boos inputhook: make stdin_ready() function reusable
Move it from lib.inputhookwx to lib.inputhook, as the former
module can only be loaded if wx itself is available.
42c877c
Christian Boos inputhook: improve stdin_ready()
 - move select and msvcrt imports in the function itself
   as they're only needed there
 - switch on os.name for platform dependent code
 - for unknow platforms, assume there's something to read
a761793
Christian Boos inputhook: make PyQt4 plays nicer with pyreadline
PyQt4's own input hook is not really appropriate when PyReadline is active
(and possibly other readline packages running the PyOS_InputHook repeatedly).
Arguably this should be fixed in PyQt4 itself, but in the meantime we can
install a local hook which does the Qt event loop processing in a less
aggressive way.
76f9a74
Christian Boos inputhook: improve CTRL+C handling with qt4
Allow a first CTRL+C to interrupt the qt4 event loop
and a second to trigger a KeyboardInterrupt.
6ab38c6
Christian Boos inputhook_qt4: handle KeyboardInterrupt in a way compatible with 'rea…
…dline'.

While the previous code works fine on Windows, on Linux with
'readline' the situation is different: once we disable the Qt4 event
loop, we go back waiting in the select() inside readline.c's
readline_until_enter_or_signal() function. When that select() is
interrupted, the PyOS_InputHook is executed if present and the pending
KeyboardInterrupt is delivered as soon as bytecode execution starts,
even before entering the `try:` clause! As at that point we're in a
ctypes callback, this will lead to an internal error. The only
solution is therefore to disable the PyOS_InputHook temporarily.
fa66348
Christian Boos

Second iteration of the patchset.

The first 3 changesets deal with #481:

  • 42c877c simply moves stdin_ready() from inputhookwx.py to inputhook.py
  • a761793 improves stdin_ready() according to the feedback from takluyver
  • 76f9a74 uses a custom inputhook instead of PyQt's qtcore_input_hook

The last 2 changesets deal with the Qt part of #122:

  • 6ab38c6 allows a first CTRL+C to break the event loop and a second to clear the prompt (at which point event processing resumes)
  • fa66348 obtaining the same behavior on Linux needs a slightly different approach

The last two are not needed to fix #481, but without them the PyQt4 support seems buggy, as explained in #122.

As explained in the changesets themselves, I think there are a few technical limitations in ctypes that prevent a single CTRL+C to work as expected, hence the "two CTRL+C" approach which I believe is intuitive enough, even more so now that I print after the first CTRL+C:

(event loop interrupted - hit CTRL+C again to clear the prompt)

Open points:

  1. not tested on Mac
  2. on Linux, an auto-repeat of CTRL+C pressed down leads to the same non-fatal internal error as the one we get without fa66348; (experiment with signal()?)
  3. on Windows, CTRL+C is only dealt with correctly in a cmd.exe shell when running python ipython.py, all the other combinations are still problematic (as per #625)

I think only 1. prevents merging, the others could correspond to later improvements.

IPython/lib/inputhook.py
((6 lines not shown))
37 38
 #-----------------------------------------------------------------------------
38 39
 
  40
+def stdin_ready():
1
Fernando Perez Owner
fperez added a note October 09, 2011

All functions should have a docstring. The full docstring guidelines follow the numpy standard, but for such a small function probably a single one-liner suffices.

Though in this case, I'd prefer it if the function was split in three: one for posix, one for nt, and an 'others'. The if statement that's currently inside would then instead assign the public name to the right implementation. This raises slightly the import cost, but means we're not doing platform checks on every single call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
IPython/lib/inputhook.py
((27 lines not shown))
207 201
         if app is None:
208 202
             app = QtCore.QCoreApplication.instance()
209 203
         if app is None:
210 204
             app = QtGui.QApplication([" "])
  205
+
  206
+        # Always use a custom input hook instead of PyQt4's default
  207
+        # one, as it interacts better with readline packages (issue
  208
+        # #481).
  209
+
  210
+        # Note that we can't let KeyboardInterrupt escape from that
  211
+        # hook, as no exception can be raised from within a ctypes
  212
+        # python callback. We need to make a compromise: a trapped
  213
+        # KeyboardInterrupt will temporarily disable the input hook
  214
+        # until we start over with a new prompt line with a second
  215
+        # CTRL+C.
  216
+
  217
+        got_kbdint = [False]
  218
+
  219
+        def inputhook_qt4():
1
Fernando Perez Owner
fperez added a note October 09, 2011

Nested functions that are starting to get this long make the code harder to read. I'd suggest moving it to the top-level, make it private with a leading underscore if need be (but please drop in a docstring regardless; we're really trying to ensure there's docstrings everywhere).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Fernando Perez
Owner

Thanks a lot for the updates. I had some inline comments, once those are handled we'll play further with this guy, it looks like we're getting close to being able to merge it, and it should be in time for 0.12.

added some commits October 09, 2011
Christian Boos inputhook: further cleanups for stdin_ready()
 - split stdin_ready() in 3 platform specific functions and choose
   the appropriate one at import time
 - add docstrings for each of these
bbdb0dd
Christian Boos inputhook: move inputhook_qt4 related code in own file aa6d06b
Christian Boos

I agree that enable_qt4() was a bit too long and needed clean-up.

However, I think it makes sense to keep inputhook_qt4() and preprompthook_qt4() as subfunctions, so that they can share a variable from a parent function create_inputhook_qt4(). I extracted that function into a separate file, like there's one already for most other GUIs.

added some commits October 09, 2011
Christian Boos inputhookqt4: forgot to reset KeyboardInterrupt flag c795b61
Christian Boos inputhookqt4: polish the qt4 related hooks
 - make sure we don't install more than one 'pre_prompt_hook'
   for a given ipapi instance
 - clarify the internal documentation and move it from comments
   into docstrings
e3612ec
Christian Boos

I was a bit worried about the use of a 'pre_prompt_hook', as I was not completely sure about the lifetime and scope of the objects involved (is ipapi.get() a singleton?). With e3612ec at least, we can safely have multiple sequences of enable_gui('qt4') / enable_gui(None), should that ever be needed ;-)

Btw, it seems that there's no way to get enable_gui(None) via the %gui. Should we add GUI_NONE = 'none'?

Fernando Perez
Owner
Christian Boos

While working on the open point 2. auto-repeat of CTRL+C leads to an internal error, I found a solution which implies some cooperation between set_inputhook, clear_inputhook and the actual callback (inputhook_qt4). It works fine, I no longer have the internal error, but the problem is that the other GUIs will not see a KeyboardInterrupt anymore. It's not that critical in the sense they were just ignoring it anyway or not handling it at all (cf. #122, #825), but to me this means we can probably do better and extend my two step approach (first CTRL+C to disable the event loop, second to trigger a normal KeyboardInterrupt) to all GUIs, not just Qt...

I think this will lead to a cleaner approach overall.

If in the meantime someone could test the current state on Mac, this would be interesting as well ;-)

Christian Boos inputhook: disable CTRL+C when a hook is active.
On systems with 'readline', it's very likely to intercept a signal
during a select() call. The default SIGINT handler will schedule a
KeyboardInterrupt exception to be raised as soon as possible. If
ctypes is used to install a Python callback for PyOS_InputHook, this
will happen as soon as the bytecode execution starts, so even if the
first instruction of the callback is a
`try: ... except KeyboardInterrupt` clause, it's actually too late.

As ctypes doesn't allow a Python callback to raise an exception, this
ends up with IPython detecting an internal error... not pretty.  We
must therefore ignore the SIGINT signals until we are sure the
exception handler is active, in the Python callback.
0fc80df
Christian Boos

Okay... so here it goes ;-)

The last two commits lead to a result I'm much more satisfied with. It also feels more reusable for other GUIs. They are normally not affected by the change, as the set_inputhook() keeps the current semantic and I've introduced a set_safe_inputhook for the new behavior, so far only used for qt4.

If you look at this PR now, you may want to skip most of the intermediate changes and look at the final inputhook.py and inputhookqt.py files, I think I've documented everything quite clearly ;-)

If you like the approach, I could try to rebase once more and go to this approach directly.

Christian Boos

Sorry, just noticed a missing line in the commit message above, the end of the 2nd paragraph should read:

This should help to address issues #122 and #825 (however some
cooperation from the input hook is needed, in particular it should not

catch KeyboardInterrupt and has to be installed using
set_safe_inputhook).

IPython/lib/inputhook.py
((72 lines not shown))
  176
+            except KeyboardInterrupt:
  177
+                ignore_CTRL_C()
  178
+                self._inputhook_on_hold = safe_callback
  179
+                print("\nKeyboardInterrupt - event loop interrupted!"
  180
+                      "\n  * hit CTRL+C again to return to the prompt"
  181
+                      "\n    (event loop will then be resumed)"
  182
+                      "\n  * use '%%gui none' to disable the event loop"
  183
+                      " permanently"
  184
+                      "\n    and '%%gui %s' to re-enable it later\n" %
  185
+                      (self._current_gui or '...'))
  186
+                self.suspend_inputhook()
  187
+            return 0
  188
+        safe_callback.wrapped = callback
  189
+
  190
+        # register _restore_inputhook() method as a 'pre_prompt_hook' (once)
  191
+        ip = get_ipython()
3
Fernando Perez Owner
fperez added a note October 13, 2011

Note that this doesn't actually work: the get_ipython function is a utility function meant for user code that is only available at the interactive prompt once IPython itself is fully running. You can, however, use this:

from IPython.core.interactiveshell import InteractiveShell
ip = InteractiveShell.instance()

As a general note, for this PR you should make sure that in addition to the automated test suite, you validate things by manually starting ipython with --gui X for all valid values of X, and also run with --pylab and all the valid pylab GUIs, as well as the gui examples in docs/examples/lib:

gui-glut.py*  gui-gtk.py*  gui-pyglet.py  gui-qt.py*  gui-tk.py*  gui-wx.py*

Sorry to ask all this, but since event loop integration is inherently an interactive problem, we need to ensure it's all tested interactively with all the various toolkits, so there's no easy shortcut I know of besides actually running each example with its corresponding toolkit.

Thomas Kluyver Collaborator

Just to ask: is it a good idea to add code using InteractiveShell.instance()? I thought we were trying to move away from InteractiveShell being a singleton.

Fernando Perez Owner
fperez added a note October 17, 2011

Well, but right now I don't think we have an alternate mechanism for accessing the currently running instance, and code such as this that does indeed need to get to it has to use something. get_ipython is something we inject into the user's namespace, so for now I don't see an alternative.

It's true that in the long run we do want to move away from that signleton design, but that's probably going to take deeper refactoring than we can tackle right now. At least the instance() call is a single, well-labeled entry point that will be easy to search for when we refactor things.

@cboos, just a heads-up: we're trying to wrap up the in-flight PRs over the next couple of weeks. Let us know if you think you'll have a chance to work on it over the next few days, so we can get it in for the 0.12 release. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christian Boos

@fperez: I understand that when I'm about to modify the whole logic about lib/inputhook I should also test the different GUI backends... but I see no chance to do that in the coming weeks. I've set up a new development environment where I will hopefully be able to test PySide in addition to PyQt and maybe Tk and Gtk, but that's about it for now.

So what I propose is to leave the more ambitious change 1d6e9a0 for a later version, but finish 0fc80df (the next to last changeset, i.e the first simpler approach which doesn't refactor inputhook.py) by adding a missing ignore_CTRL_C() and fixing the get_ipython() stuff. That sequence of changes only touches the qt4 related code and has no impact on the other GUIs. I should probably rebase that once more for increased clarity.

About fixing get_ipython() fixing, I'm asking again just to be sure: what is the best approach, reverting 350ecdb (i.e. use IPython.core.ipapi.get()) or use IPython.core.interactiveshell.InteractiveShell.instance()?

And still, even by leaving 1d6e9a0 aside, another blocking factor I see for the merge is that someone needs to test this on a Mac, as I can't do that myself.

Fernando Perez
Owner
Min RK
Owner

Looks pretty good on OSX+PySide, except for at exit, I get an infinite crash loop:
The first error:

Do you really want to exit ([y]/n)? 
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "/Users/minrk/dev/py/matplotlib/lib/matplotlib/_pylab_helpers.py", line 82, in destroy_all
    manager.destroy()
  File "/Users/minrk/dev/py/matplotlib/lib/matplotlib/backends/backend_qt4.py", line 388, in destroy
    self._widgetclosed )
RuntimeError: Internal C++ object (PySide.QtGui.QMainWindow) already deleted.
Error in sys.exitfunc:
---------------------------------------------------------------------------
RuntimeError                                  Python 2.7.1: /usr/bin/python
                                                   Fri Oct 21 14:29:14 2011
A problem occured executing Python code.  Here is the sequence of function
calls leading up to the error, with the most recent (innermost) call last.
/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/atexit.pyc in _run_exitfuncs()
      9 
     10 import sys
     11 
     12 _exithandlers = []
     13 def _run_exitfuncs():
     14     """run any registered exit functions
     15 
     16     _exithandlers is traversed in reverse order so functions are executed
     17     last in, first out.
     18     """
     19 
     20     exc_info = None
     21     while _exithandlers:
     22         func, targs, kargs = _exithandlers.pop()
     23         try:
---> 24             func(*targs, **kargs)
        func = 
        global function = undefined
        global to = undefined
        global be = undefined
        global called = undefined
        global at = undefined
        global exit = undefined
     25         except SystemExit:
     26             exc_info = sys.exc_info()
     27         except:
     28             import traceback
     29             print >> sys.stderr, "Error in atexit._run_exitfuncs:"
     30             traceback.print_exc()
     31             exc_info = sys.exc_info()
     32 
     33     if exc_info is not None:
     34         raise exc_info[0], exc_info[1], exc_info[2]
     35 
     36 
     37 def register(func, *targs, **kargs):
     38     """register a function to be executed upon normal program termination
     39 

/Users/minrk/dev/py/matplotlib/lib/matplotlib/_pylab_helpers.pyc in destroy_all()
     67         #print len(Gcf.figs.keys()), len(Gcf._activeQue)
     68         manager.destroy()
     69         gc.collect()
     70 
     71     @staticmethod
     72     def destroy_fig(fig):
     73         "*fig* is a Figure instance"
     74         for manager in Gcf.figs.values():
     75             if manager.canvas.figure == fig:
     76                 Gcf.destroy(manager.num)
     77 
     78     @staticmethod
     79     def destroy_all():
     80         for manager in Gcf.figs.values():
     81             manager.canvas.mpl_disconnect(manager._cidgcf)
---> 82             manager.destroy()
     83 
     84         Gcf._activeQue = []
     85         Gcf.figs.clear()
     86         gc.collect()
     87 
     88     @staticmethod
     89     def has_fignum(num):
     90         """
     91         Return *True* if figure *num* exists.
     92         """
     93         return num in Gcf.figs
     94 
     95     @staticmethod
     96     def get_all_fig_managers():
     97         """

/Users/minrk/dev/py/matplotlib/lib/matplotlib/backends/backend_qt4.pyc in destroy(self=, *args=())
    373         else:
    374             toolbar = None
    375         return toolbar
    376 
    377     def resize(self, width, height):
    378         'set the canvas size in pixels'
    379         self.window.resize(width, height)
    380 
    381     def show(self):
    382         self.window.show()
    383 
    384     def destroy( self, *args ):
    385         if self.window._destroying: return
    386         self.window._destroying = True
    387         QtCore.QObject.disconnect( self.window, QtCore.SIGNAL( 'destroyed()' ),
--> 388                                    self._widgetclosed )
    389         if self.toolbar: self.toolbar.destroy()
    390         if DEBUG: print "destroy figure manager"
    391         self.window.close()
    392 
    393     def set_window_title(self, title):
    394         self.window.setWindowTitle(title)
    395 
    396 class NavigationToolbar2QT( NavigationToolbar2, QtGui.QToolBar ):
    397     def __init__(self, canvas, parent, coordinates=True):
    398         """ coordinates: should we show the coordinates on the right? """
    399         self.canvas = canvas
    400         self.coordinates = coordinates
    401         QtGui.QToolBar.__init__( self, parent )
    402         NavigationToolbar2.__init__( self, canvas )
    403 

RuntimeError: Internal C++ object (PySide.QtGui.QMainWindow) already deleted.

Which may be a PySide (I'm using 1.06) or matplotlib (git master) bug, though it does not come up in IPython master, so something in this PR is relevant.

But the real problem is that this means that QtCore.QCoreApplication.instance() returns None after shutdown, and the crash handler gets itself stuck permanently in this loop:

Error in sys.excepthook:
Traceback (most recent call last):
  File "/Users/minrk/dev/ip/mine/IPython/core/crashhandler.py", line 159, in __call__
    raw_input("Hit  to quit this message (your terminal may close):")
RuntimeError: can't re-enter readline

Original exception was:
Traceback (most recent call last):
  File "_ctypes/callbacks.c", line 313, in 'calling callback function'
  File "/Users/minrk/dev/ip/mine/IPython/lib/inputhookqt4.py", line 84, in inputhook_qt4
    app.processEvents(QtCore.QEventLoop.AllEvents, 300)
AttributeError: 'NoneType' object has no attribute 'processEvents'
---------------------------------------------------------------------------
AttributeError                                Python 2.7.1: /usr/bin/python
                                                   Fri Oct 21 14:29:15 2011
A problem occured executing Python code.  Here is the sequence of function
calls leading up to the error, with the most recent (innermost) call last.
/Users/minrk/dev/ip/mine/docs/_ctypes/callbacks.c in 'calling callback function'()

/Users/minrk/dev/ip/mine/IPython/lib/inputhookqt4.pyc in inputhook_qt4()
     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             app.processEvents(QtCore.QEventLoop.AllEvents, 300)
     85             if not stdin_ready():
     86                 timer = QtCore.QTimer()
     87                 timer.timeout.connect(app.quit)
     88                 while not stdin_ready():
     89                     timer.start(50)
     90                     app.exec_()
     91                     timer.stop()
     92             ignore_CTRL_C()
     93         except KeyboardInterrupt:
     94             ignore_CTRL_C()
     95             got_kbdint[0] = True
     96             print("\nKeyboardInterrupt - qt4 event loop interrupted!"
     97                   "\n  * hit CTRL+C again to clear the prompt"
     98                   "\n  * use '%gui none' to disable the event loop"
     99                   " permanently"

AttributeError: 'NoneType' object has no attribute 'processEvents'

where the inputhook raises an error, and the crash handler invokes the inputhook for its Hit <Enter>... message, repeating forever.

This points to a general issue - when we inject a custom inputhook, they should probably all be wrapped in a simple try/finally that restores the original, so that errors don't repeat infinitely.

Christian Boos

Thanks for testing on Mac!

Both the original error and the one in my hook have a common cause: assuming the qApp is still alive, which for some reason is not the case here. I'll try to reproduce that issue.

I can check for this situation in the qt4 hook and as you suggested make sure that the hook will unregister itself in case of an error.

Also in parallel to this set of changes, I continued the work started in 1d6e9a0 for using the two step CTRL+C approach for all GUIs, and some initial testing has shown that this approach can also work well for gtk and Tk. When this is ready I'll create a second PR in order to keep this one focused on the immediate problem with Qt4 (i.e. fixing #481).

Min RK
Owner

That makes sense. Can you see why the atexit behavior is not the same with the new code?

Christian Boos

I installed PySide on Windows (the latest 1.0.7 installer) and I could reproduce the problem... only when I mistakenly mixed PyQt4 and PySide code! They seem to both share the same instances of Qt libraries and to interfere badly at exit. Are you sure you don't have a from PyQt4 import ... statement somewhere? Or maybe that mixup is in matplotlib, as some other people also noticed a similar issue when using PySide with matplotlib (matplotlib/matplotlib#80 (comment)).

If I only use PySide and don't mix it with PyQt4, then everything works fine (#481 also fixed, no crash at exit), at least on Windows. Next step is to test this in EPD on Windows with matplotlib, and then on Linux...

Christian Boos inputhookqt4: make hook more robust in case of unexpected conditions
 - return early if there's no longer a qApp
 - trap any exception that could be raised from within the hook,
   report it and unregister the hook
da13a62
Min RK
Owner

Good find, but I am sure that I am not importing both PyQt and PySide, because I don't have both on this machine. Base on the MPL discussion, it seems like this might be a PySide bug, but there's clearly a way to avoid it, because it doesn't affect IPython master, it only comes up using this PR.

Christian Boos

Btw, I noticed since that it was already possible to disable the event loop, by just typing %gui alone.

I see however two problems with that:

  • it's indeed documented (%gui?) but that documentation is in IPython/core/magic.py, so I didn't immediately figured out... and being that far away has also the consequence that the documentation is a bit outdated (no mention of the recent GUIs).
  • I would have expected %gui to give me a hint about the currently active GUI, not disabling it! That would also be more consistent in terms of type of return value.
Christian Boos

Ok, which version of matplotlib were you using?

Min RK
Owner

git master

Christian Boos

Ok, I can now also reproduce the above crash on Windows, with matplotlib 1.2.x (2bb91ce).

But even with ipython master (cb6da34) I get a similar crash at exit:

C:\Workspace\src\git\ipython>C:\Dev\Python27-EPD-7.1\python.exe ipython.py --pylab qt
Python 2.7.2 |EPD_free 7.1-2 (32-bit)| (default, Jul  3 2011, 15:13:59) [MSC v.1500 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.

IPython 0.12.dev -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

Welcome to pylab, a matplotlib-based Python environment [backend: Qt4Agg].
For more information, type 'help(pylab)'.
IPython\lib\inputhook.py:195: RuntimeWarning: PyReadline's inputhook can conflict with Qt, causing delays
            in interactive input. If you do see this issue, we recommend using another GUI
            toolkit if you can, or disable readline with the configuration option
            'TerminalInteractiveShell.readline_use=False', specified in a config file or
            at the command-line
  RuntimeWarning)

In [1]: %gui qt
Out[1]: <PySide.QtGui.QApplication at 0x46cfcb0>

In [2]: qApp = _

In [3]: x = randn(10000)

In [4]: hist(x, 100)
Out[4]:
(...
 <a list of 100 Patch objects>)

In [5]: while True: qApp.processEvents()
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
C:\Workspace\src\git\ipython\<ipython-input-5-d0d3849ef433> in <module>()
----> 1 while True: qApp.processEvents()

KeyboardInterrupt:

In [6]:
Do you really want to exit ([y]/n)?
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "C:\Dev\Python27-EPD-7.1\lib\atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "c:\workspace\src\git\matplotlib\lib\matplotlib\_pylab_helpers.py", line 82, in destroy_all
    manager.destroy()
  File "c:\workspace\src\git\matplotlib\lib\matplotlib\backends\backend_qt4.py", line 388, in destroy
    self._widgetclosed )
RuntimeError: Internal C++ object (PySide.QtGui.QMainWindow) already deleted.
Error in sys.exitfunc:
Traceback (most recent call last):
  File "C:\Dev\Python27-EPD-7.1\lib\atexit.py", line 24, in _run_exitfuncs
    func(*targs, **kargs)
  File "c:\workspace\src\git\matplotlib\lib\matplotlib\_pylab_helpers.py", line 82, in destroy_all
    manager.destroy()
  File "c:\workspace\src\git\matplotlib\lib\matplotlib\backends\backend_qt4.py", line 388, in destroy
    self._widgetclosed )
RuntimeError: Internal C++ object (PySide.QtGui.QMainWindow) already deleted.

Besides, with ipython master there's no event loop integration with PySide. You can however do while True: qApp.processEvents() on the command line to check that matplotlib 1.2.x is indeed working fine with PySide (well, until exit that is ;-) ).

I think that having the crash or not depends on the order in which the atexit handlers are run. I use a VisualStudio 2008 build, and perhaps you have a mingw one? In any case, relying on the order of atexit handlers is fragile at best. I see that matplotlib in backend_qt4.py tries to detect a destroy() of the FigureManagerQT.window (the one involved in the crash). But this doesn't work, the signal is not delivered to the _widgetclosed method, quite certainly because the objects involved in signal propagation from C++ to Python objects are already gone at that point. See in particular destroyQCoreApplication, the PySide atexit handler which starts by doing a SignalManager::instance().clear(), then proceeds with a bm.visitAllPyObjects(&destructionVisitor, &data) before finishing with a delete app... I wouldn't dare using anything from the PySide API past that point!

Bottom line: it's a matplotlib issue due to PySide behaving differently than PyQt4, nothing really caused by the changes here. Quite the opposite, PySide is better supported as you get event loop integration and with my last commit da13a62, I handle this crash in a more robust way by unregistering the hook. I get a full crash report however, instead of only the backtrace as above, for some reason.

Suggested fix for matplotlib:

diff --git a/lib/matplotlib/backends/backend_qt4.py b/lib/matplotlib/backends/backend_qt4.py
index c85b4b3..64a737e 100644
--- a/lib/matplotlib/backends/backend_qt4.py
+++ b/lib/matplotlib/backends/backend_qt4.py
@@ -382,6 +382,7 @@ class FigureManagerQT( FigureManagerBase ):
         self.window.show()

     def destroy( self, *args ):
+        if QtGui.QApplication.instance() is None: return
         if self.window._destroying: return
         self.window._destroying = True
         QtCore.QObject.disconnect( self.window, QtCore.SIGNAL( 'destroyed()' ),

Min RK
Owner

Wow, I don't know how I managed that bad report. You are right, master doesn't work at all in PySide.

And I do know the reason that it's a crash in your branch, and a traceback in master. I fixed that bug last week: d5548fa

If you rebase on master (I just did, it went cleanly), you will just get a traceback. I'm fine with this, then, as it is indeed a matplotlib and/or PySide bug, and doesn't crash IPython. I recomend sending your fix upstream to matplotlib, as it seems clean.

Christian Boos

I've been able to test this branch with PyQt, PySide, Tk, Wx, Gtk on Windows and Linux, and also with pylab on both platforms. The test suite passes on both platforms as well. mrink tested pylab with pyside for the Mac. Now all looks good i.e. #481 is fixed for PyQt4 and PySide, no ill side-effects...

Is there anything else I should do? I wonder if I should rework the patchset once more in order to have fewer and only "clean" changesets inside. And one thing I'm unsure about is the impact on py3k support... I don't know exactly what the current status is supposed to be, but I couldn't get master to work with Python 3.2.2.

Of course there are still a lot of improvements that could be made in the IPython/lib/inputhook* area, especially now that I've tested more GUI backends and I see that they could also benefit from this new approach. But I'll save this for another PR (continuing on http://github.com/cboos/ipython/commits/inputhook-refactoring).

Thomas Kluyver
Collaborator

Python 3 support should be working in master. What problems are you seeing? I'll grab a fresh checkout and have a look.

Fernando Perez
Owner

Great, let's wait for @takluyver's report on py3 before we make any final decisions on this one. But it's looking very close to ready for merge, thanks @cboos for the work and patience!

Thomas Kluyver
Collaborator

Master seems to be working fine in Python 3.2.2 (give or take #939, which I've just opened, but that's a minor problem). I don't think I've experienced the problem this PR aims to solve, so I'm not sure if I can usefully test it.

Fernando Perez
Owner

@takluyver, testing this amounts to going to docs/examples/lib and running each of the gui-X.py scripts after starting ipython with --gui X. In each case, you should be able to see a window (possibly just a static one, possibly something with a button or two depending on the example) and retain non-blocking, typing control of the IPython session.

Since I don't know whether we've actually tested this or not with Py3 before, I suggest running those tests first in master and then with @cboos PR merged in. We should only bother this PR if there's a difference after merging it; any problems that may exist in master we'll ignore for the purposes of this PR an deal with separately.

Thomas Kluyver
Collaborator

Qt and Tk appear to be working fine, both in master and this branch. Gtk and Wx aren't on Python 3 - in fact, Gtk is, but I think our integration is for PyGTK, not the new gobject bindings.

Fernando Perez
Owner

OK, I've tested this as much as I can interactively, and given that the rest of us here have also run it through its paces, I'll merge it now. There may be lurking issues (the GUI integration stuff is devilishly hard) but we won't find those until we put it to broader, real-world usage.

@cboos, many thanks for this! I know it was hard work with a lot of back and forth, so I'm glad you stuck with it. It's a necessary hurdle to keep the code quality as high as we can given the small size of our team.

Fernando Perez fperez merged commit 1f7e9d7 into from October 28, 2011
Fernando Perez fperez closed this October 28, 2011
Christian Boos

@all: thanks for testing and merging!

@takluyver: regarding py3k, python ipython.py fails right away with execfile, but even if I can go a bit further by adding an from IPython.utils.py3compat import execfile, it fails a bit later. I see import __builtin__ everywhere, plus some places having Python 2-only syntax (at least in IPython/config/loader.py, IPython/utils/path.py)... Did I miss something obvious (like I should be on some branch instead of master) or should I create a ticket where we could elaborate?

Thomas Kluyver
Collaborator

@cboos: You have to do python setup.py install, rather than running via ipython.py, because 2to3 runs during the build process. I use a virtualenv for development.

Christian Boos

@takluyver: Ok, got it! Thanks for pointing me in the right direction. It's not yet fully working (compat issues with pyreadline 2.0), but at least now it make sense!

Fernando Perez fperez referenced this pull request from a commit January 10, 2012
Commit has since been removed from the repository and is no longer available.
Fernando Perez fperez referenced this pull request from a commit January 10, 2012
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 15 unique commits by 1 author.

Oct 09, 2011
Christian Boos inputhook: make stdin_ready() function reusable
Move it from lib.inputhookwx to lib.inputhook, as the former
module can only be loaded if wx itself is available.
42c877c
Christian Boos inputhook: improve stdin_ready()
 - move select and msvcrt imports in the function itself
   as they're only needed there
 - switch on os.name for platform dependent code
 - for unknow platforms, assume there's something to read
a761793
Christian Boos inputhook: make PyQt4 plays nicer with pyreadline
PyQt4's own input hook is not really appropriate when PyReadline is active
(and possibly other readline packages running the PyOS_InputHook repeatedly).
Arguably this should be fixed in PyQt4 itself, but in the meantime we can
install a local hook which does the Qt event loop processing in a less
aggressive way.
76f9a74
Christian Boos inputhook: improve CTRL+C handling with qt4
Allow a first CTRL+C to interrupt the qt4 event loop
and a second to trigger a KeyboardInterrupt.
6ab38c6
Christian Boos inputhook_qt4: handle KeyboardInterrupt in a way compatible with 'rea…
…dline'.

While the previous code works fine on Windows, on Linux with
'readline' the situation is different: once we disable the Qt4 event
loop, we go back waiting in the select() inside readline.c's
readline_until_enter_or_signal() function. When that select() is
interrupted, the PyOS_InputHook is executed if present and the pending
KeyboardInterrupt is delivered as soon as bytecode execution starts,
even before entering the `try:` clause! As at that point we're in a
ctypes callback, this will lead to an internal error. The only
solution is therefore to disable the PyOS_InputHook temporarily.
fa66348
Christian Boos inputhook: further cleanups for stdin_ready()
 - split stdin_ready() in 3 platform specific functions and choose
   the appropriate one at import time
 - add docstrings for each of these
bbdb0dd
Christian Boos inputhook: move inputhook_qt4 related code in own file aa6d06b
Christian Boos inputhookqt4: forgot to reset KeyboardInterrupt flag c795b61
Oct 10, 2011
Christian Boos inputhookqt4: polish the qt4 related hooks
 - make sure we don't install more than one 'pre_prompt_hook'
   for a given ipapi instance
 - clarify the internal documentation and move it from comments
   into docstrings
e3612ec
Oct 11, 2011
Christian Boos inputhookqt4: use get_ipython() instead of deprecated ipapi.get() 350ecdb
Christian Boos inputhook: use '%gui none' for disabling the input hook. 4826692
Christian Boos inputhook: disable CTRL+C when a hook is active.
On systems with 'readline', it's very likely to intercept a signal
during a select() call. The default SIGINT handler will schedule a
KeyboardInterrupt exception to be raised as soon as possible. If
ctypes is used to install a Python callback for PyOS_InputHook, this
will happen as soon as the bytecode execution starts, so even if the
first instruction of the callback is a
`try: ... except KeyboardInterrupt` clause, it's actually too late.

As ctypes doesn't allow a Python callback to raise an exception, this
ends up with IPython detecting an internal error... not pretty.  We
must therefore ignore the SIGINT signals until we are sure the
exception handler is active, in the Python callback.
0fc80df
Oct 19, 2011
Christian Boos inputhookqt4: need to ignore CTRL+C when exiting input hook a0e0692
Christian Boos inputhookqt4: use InteractiveShell.instance instead of get_ipython
The latter is meant for user code only.
be8bbf1
Oct 23, 2011
Christian Boos inputhookqt4: make hook more robust in case of unexpected conditions
 - return early if there's no longer a qApp
 - trap any exception that could be raised from within the hook,
   report it and unregister the hook
da13a62
This page is out of date. Refresh to see the latest.
89  IPython/lib/inputhook.py
@@ -15,6 +15,7 @@
15 15
 #-----------------------------------------------------------------------------
16 16
 
17 17
 import ctypes
  18
+import os
18 19
 import sys
19 20
 import warnings
20 21
 
@@ -31,11 +32,58 @@
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
@@ -70,6 +118,11 @@ def get_pyos_inputhook_as_func(self):
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()
@@ -93,6 +146,7 @@ def clear_inputhook(self, app=None):
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
 
@@ -181,33 +235,11 @@ def enable_qt4(self, app=None):
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
@@ -416,8 +448,8 @@ def enable_gui(gui=None, app=None):
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
@@ -432,6 +464,7 @@ def enable_gui(gui=None, app=None):
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,
124  IPython/lib/inputhookqt4.py
... ...
@@ -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
17  IPython/lib/inputhookwx.py
@@ -24,26 +24,13 @@
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
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.