| 
									
										
										
										
											2018-01-05 16:57:38 +00:00
										 |  |  | # -*- coding: utf-8 -*- | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | Qt4's inputhook support function | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Author: Christian Boos | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							| 
									
										
										
										
											2018-10-16 14:33:16 +01:00
										 |  |  | #  Copyright (C) 2011  The IPython Development Team | 
					
						
							| 
									
										
										
										
											2018-01-05 16:57:38 +00:00
										 |  |  | # | 
					
						
							|  |  |  | #  Distributed under the terms of the BSD License.  The full license is in | 
					
						
							|  |  |  | #  the file COPYING, distributed as part of this software. | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | # Imports | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | import signal | 
					
						
							|  |  |  | import threading | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from yap_ipython.core.interactiveshell import InteractiveShell | 
					
						
							|  |  |  | from yap_ipython.external.qt_for_kernel import QtCore, QtGui | 
					
						
							|  |  |  | from yap_ipython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | # Module Globals | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | got_kbdint = False | 
					
						
							|  |  |  | sigint_timer = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | # Code | 
					
						
							|  |  |  | #----------------------------------------------------------------------------- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def create_inputhook_qt4(mgr, app=None): | 
					
						
							|  |  |  |     """Create an input hook for running the Qt4 application event loop.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Parameters | 
					
						
							|  |  |  |     ---------- | 
					
						
							|  |  |  |     mgr : an InputHookManager | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     app : Qt Application, optional. | 
					
						
							|  |  |  |         Running application to use.  If not given, we probe Qt for an | 
					
						
							|  |  |  |         existing application object, and create a new one if none is found. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Returns | 
					
						
							|  |  |  |     ------- | 
					
						
							|  |  |  |     A pair consisting of a Qt Application (either the one given or the | 
					
						
							|  |  |  |     one found or created) and a inputhook. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Notes | 
					
						
							|  |  |  |     ----- | 
					
						
							|  |  |  |     We use a custom input hook instead of PyQt4's default one, as it | 
					
						
							|  |  |  |     interacts better with the readline packages (issue #481). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     The inputhook function works in tandem with a 'pre_prompt_hook' | 
					
						
							|  |  |  |     which automatically restores the hook as an inputhook in case the | 
					
						
							|  |  |  |     latter has been temporarily disabled after having intercepted a | 
					
						
							|  |  |  |     KeyboardInterrupt. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if app is None: | 
					
						
							|  |  |  |         app = QtCore.QCoreApplication.instance() | 
					
						
							|  |  |  |         if app is None: | 
					
						
							|  |  |  |             app = QtGui.QApplication([" "]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Re-use previously created inputhook if any | 
					
						
							|  |  |  |     ip = InteractiveShell.instance() | 
					
						
							|  |  |  |     if hasattr(ip, '_inputhook_qt4'): | 
					
						
							|  |  |  |         return app, ip._inputhook_qt4 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of | 
					
						
							|  |  |  |     # hooks (they both share the got_kbdint flag) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def inputhook_qt4(): | 
					
						
							|  |  |  |         """PyOS_InputHook python hook for Qt4.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Process pending Qt events and if there's no pending keyboard | 
					
						
							|  |  |  |         input, spend a short slice of time (50ms) running the Qt event | 
					
						
							|  |  |  |         loop. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         As a Python ctypes callback can't raise an exception, we catch | 
					
						
							|  |  |  |         the KeyboardInterrupt and temporarily deactivate the hook, | 
					
						
							|  |  |  |         which will let a *second* CTRL+C be processed normally and go | 
					
						
							|  |  |  |         back to a clean prompt line. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             allow_CTRL_C() | 
					
						
							|  |  |  |             app = QtCore.QCoreApplication.instance() | 
					
						
							|  |  |  |             if not app: # shouldn't happen, but safer if it happens anyway... | 
					
						
							|  |  |  |                 return 0 | 
					
						
							|  |  |  |             app.processEvents(QtCore.QEventLoop.AllEvents, 300) | 
					
						
							|  |  |  |             if not stdin_ready(): | 
					
						
							|  |  |  |                 # Generally a program would run QCoreApplication::exec() | 
					
						
							|  |  |  |                 # from main() to enter and process the Qt event loop until | 
					
						
							|  |  |  |                 # quit() or exit() is called and the program terminates. | 
					
						
							|  |  |  |                 # | 
					
						
							|  |  |  |                 # For our input hook integration, we need to repeatedly | 
					
						
							|  |  |  |                 # enter and process the Qt event loop for only a short | 
					
						
							|  |  |  |                 # amount of time (say 50ms) to ensure that Python stays | 
					
						
							|  |  |  |                 # responsive to other user inputs. | 
					
						
							|  |  |  |                 # | 
					
						
							|  |  |  |                 # A naive approach would be to repeatedly call | 
					
						
							|  |  |  |                 # QCoreApplication::exec(), using a timer to quit after a | 
					
						
							|  |  |  |                 # short amount of time. Unfortunately, QCoreApplication | 
					
						
							|  |  |  |                 # emits an aboutToQuit signal before stopping, which has | 
					
						
							|  |  |  |                 # the undesirable effect of closing all modal windows. | 
					
						
							|  |  |  |                 # | 
					
						
							|  |  |  |                 # To work around this problem, we instead create a | 
					
						
							|  |  |  |                 # QEventLoop and call QEventLoop::exec(). Other than | 
					
						
							|  |  |  |                 # setting some state variables which do not seem to be | 
					
						
							|  |  |  |                 # used anywhere, the only thing QCoreApplication adds is | 
					
						
							|  |  |  |                 # the aboutToQuit signal which is precisely what we are | 
					
						
							|  |  |  |                 # trying to avoid. | 
					
						
							|  |  |  |                 timer = QtCore.QTimer() | 
					
						
							|  |  |  |                 event_loop = QtCore.QEventLoop() | 
					
						
							|  |  |  |                 timer.timeout.connect(event_loop.quit) | 
					
						
							|  |  |  |                 while not stdin_ready(): | 
					
						
							|  |  |  |                     timer.start(50) | 
					
						
							|  |  |  |                     event_loop.exec_() | 
					
						
							|  |  |  |                     timer.stop() | 
					
						
							|  |  |  |         except KeyboardInterrupt: | 
					
						
							|  |  |  |             global got_kbdint, sigint_timer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ignore_CTRL_C() | 
					
						
							|  |  |  |             got_kbdint = True | 
					
						
							|  |  |  |             mgr.clear_inputhook() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # This generates a second SIGINT so the user doesn't have to | 
					
						
							|  |  |  |             # press CTRL+C twice to get a clean prompt. | 
					
						
							|  |  |  |             # | 
					
						
							|  |  |  |             # Since we can't catch the resulting KeyboardInterrupt here | 
					
						
							|  |  |  |             # (because this is a ctypes callback), we use a timer to | 
					
						
							|  |  |  |             # generate the SIGINT after we leave this callback. | 
					
						
							|  |  |  |             # | 
					
						
							|  |  |  |             # Unfortunately this doesn't work on Windows (SIGINT kills | 
					
						
							|  |  |  |             # Python and CTRL_C_EVENT doesn't work). | 
					
						
							|  |  |  |             if(os.name == 'posix'): | 
					
						
							|  |  |  |                 pid = os.getpid() | 
					
						
							|  |  |  |                 if(not sigint_timer): | 
					
						
							|  |  |  |                     sigint_timer = threading.Timer(.01, os.kill, | 
					
						
							|  |  |  |                                          args=[pid, signal.SIGINT] ) | 
					
						
							|  |  |  |                     sigint_timer.start() | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 print("\nKeyboardInterrupt - Ctrl-C again for new prompt") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         except: # NO exceptions are allowed to escape from a ctypes callback | 
					
						
							|  |  |  |             ignore_CTRL_C() | 
					
						
							|  |  |  |             from traceback import print_exc | 
					
						
							|  |  |  |             print_exc() | 
					
						
							|  |  |  |             print("Got exception from inputhook_qt4, unregistering.") | 
					
						
							|  |  |  |             mgr.clear_inputhook() | 
					
						
							|  |  |  |         finally: | 
					
						
							|  |  |  |             allow_CTRL_C() | 
					
						
							|  |  |  |         return 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def preprompthook_qt4(ishell): | 
					
						
							|  |  |  |         """'pre_prompt_hook' used to restore the Qt4 input hook
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         (in case the latter was temporarily deactivated after a | 
					
						
							|  |  |  |         CTRL+C) | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         global got_kbdint, sigint_timer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if(sigint_timer): | 
					
						
							|  |  |  |             sigint_timer.cancel() | 
					
						
							|  |  |  |             sigint_timer = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if got_kbdint: | 
					
						
							|  |  |  |             mgr.set_inputhook(inputhook_qt4) | 
					
						
							|  |  |  |         got_kbdint = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ip._inputhook_qt4 = inputhook_qt4 | 
					
						
							|  |  |  |     ip.set_hook('pre_prompt_hook', preprompthook_qt4) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return app, inputhook_qt4 |