inputhookqt4.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. # -*- coding: utf-8 -*-
  2. """
  3. Qt4's inputhook support function
  4. Author: Christian Boos
  5. """
  6. #-----------------------------------------------------------------------------
  7. # Copyright (C) 2011 The IPython Development Team
  8. #
  9. # Distributed under the terms of the BSD License. The full license is in
  10. # the file COPYING, distributed as part of this software.
  11. #-----------------------------------------------------------------------------
  12. #-----------------------------------------------------------------------------
  13. # Imports
  14. #-----------------------------------------------------------------------------
  15. import os
  16. import signal
  17. import threading
  18. from IPython.core.interactiveshell import InteractiveShell
  19. from IPython.external.qt_for_kernel import QtCore, QtGui
  20. from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready
  21. #-----------------------------------------------------------------------------
  22. # Module Globals
  23. #-----------------------------------------------------------------------------
  24. got_kbdint = False
  25. sigint_timer = None
  26. #-----------------------------------------------------------------------------
  27. # Code
  28. #-----------------------------------------------------------------------------
  29. def create_inputhook_qt4(mgr, app=None):
  30. """Create an input hook for running the Qt4 application event loop.
  31. Parameters
  32. ----------
  33. mgr : an InputHookManager
  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. Returns
  38. -------
  39. A pair consisting of a Qt Application (either the one given or the
  40. one found or created) and a inputhook.
  41. Notes
  42. -----
  43. We use a custom input hook instead of PyQt4's default one, as it
  44. interacts better with the readline packages (issue #481).
  45. The inputhook function works in tandem with a 'pre_prompt_hook'
  46. which automatically restores the hook as an inputhook in case the
  47. latter has been temporarily disabled after having intercepted a
  48. KeyboardInterrupt.
  49. """
  50. if app is None:
  51. app = QtCore.QCoreApplication.instance()
  52. if app is None:
  53. app = QtGui.QApplication([" "])
  54. # Re-use previously created inputhook if any
  55. ip = InteractiveShell.instance()
  56. if hasattr(ip, '_inputhook_qt4'):
  57. return app, ip._inputhook_qt4
  58. # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of
  59. # hooks (they both share the got_kbdint flag)
  60. def inputhook_qt4():
  61. """PyOS_InputHook python hook for Qt4.
  62. Process pending Qt events and if there's no pending keyboard
  63. input, spend a short slice of time (50ms) running the Qt event
  64. loop.
  65. As a Python ctypes callback can't raise an exception, we catch
  66. the KeyboardInterrupt and temporarily deactivate the hook,
  67. which will let a *second* CTRL+C be processed normally and go
  68. back to a clean prompt line.
  69. """
  70. try:
  71. allow_CTRL_C()
  72. app = QtCore.QCoreApplication.instance()
  73. if not app: # shouldn't happen, but safer if it happens anyway...
  74. return 0
  75. app.processEvents(QtCore.QEventLoop.AllEvents, 300)
  76. if not stdin_ready():
  77. # Generally a program would run QCoreApplication::exec()
  78. # from main() to enter and process the Qt event loop until
  79. # quit() or exit() is called and the program terminates.
  80. #
  81. # For our input hook integration, we need to repeatedly
  82. # enter and process the Qt event loop for only a short
  83. # amount of time (say 50ms) to ensure that Python stays
  84. # responsive to other user inputs.
  85. #
  86. # A naive approach would be to repeatedly call
  87. # QCoreApplication::exec(), using a timer to quit after a
  88. # short amount of time. Unfortunately, QCoreApplication
  89. # emits an aboutToQuit signal before stopping, which has
  90. # the undesirable effect of closing all modal windows.
  91. #
  92. # To work around this problem, we instead create a
  93. # QEventLoop and call QEventLoop::exec(). Other than
  94. # setting some state variables which do not seem to be
  95. # used anywhere, the only thing QCoreApplication adds is
  96. # the aboutToQuit signal which is precisely what we are
  97. # trying to avoid.
  98. timer = QtCore.QTimer()
  99. event_loop = QtCore.QEventLoop()
  100. timer.timeout.connect(event_loop.quit)
  101. while not stdin_ready():
  102. timer.start(50)
  103. event_loop.exec_()
  104. timer.stop()
  105. except KeyboardInterrupt:
  106. global got_kbdint, sigint_timer
  107. ignore_CTRL_C()
  108. got_kbdint = True
  109. mgr.clear_inputhook()
  110. # This generates a second SIGINT so the user doesn't have to
  111. # press CTRL+C twice to get a clean prompt.
  112. #
  113. # Since we can't catch the resulting KeyboardInterrupt here
  114. # (because this is a ctypes callback), we use a timer to
  115. # generate the SIGINT after we leave this callback.
  116. #
  117. # Unfortunately this doesn't work on Windows (SIGINT kills
  118. # Python and CTRL_C_EVENT doesn't work).
  119. if(os.name == 'posix'):
  120. pid = os.getpid()
  121. if(not sigint_timer):
  122. sigint_timer = threading.Timer(.01, os.kill,
  123. args=[pid, signal.SIGINT] )
  124. sigint_timer.start()
  125. else:
  126. print("\nKeyboardInterrupt - Ctrl-C again for new prompt")
  127. except: # NO exceptions are allowed to escape from a ctypes callback
  128. ignore_CTRL_C()
  129. from traceback import print_exc
  130. print_exc()
  131. print("Got exception from inputhook_qt4, unregistering.")
  132. mgr.clear_inputhook()
  133. finally:
  134. allow_CTRL_C()
  135. return 0
  136. def preprompthook_qt4(ishell):
  137. """'pre_prompt_hook' used to restore the Qt4 input hook
  138. (in case the latter was temporarily deactivated after a
  139. CTRL+C)
  140. """
  141. global got_kbdint, sigint_timer
  142. if(sigint_timer):
  143. sigint_timer.cancel()
  144. sigint_timer = None
  145. if got_kbdint:
  146. mgr.set_inputhook(inputhook_qt4)
  147. got_kbdint = False
  148. ip._inputhook_qt4 = inputhook_qt4
  149. ip.set_hook('pre_prompt_hook', preprompthook_qt4)
  150. return app, inputhook_qt4