import sys import os from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper from IPython import get_ipython # If we create a QApplication, QEventLoop, or a QTimer, keep a reference to them # so that they don't get garbage collected or leak memory when created multiple times. _appref = None _eventloop = None _timer = None _already_warned = False def _exec(obj): # exec on PyQt6, exec_ elsewhere. obj.exec() if hasattr(obj, "exec") else obj.exec_() def _reclaim_excepthook(): shell = get_ipython() if shell is not None: sys.excepthook = shell.excepthook def inputhook(context): global _appref, _eventloop, _timer app = QtCore.QCoreApplication.instance() if not app: if sys.platform == 'linux': if not os.environ.get('DISPLAY') \ and not os.environ.get('WAYLAND_DISPLAY'): import warnings global _already_warned if not _already_warned: _already_warned = True warnings.warn( 'The DISPLAY or WAYLAND_DISPLAY environment variable is ' 'not set or empty and Qt5 requires this environment ' 'variable. Deactivate Qt5 code.' ) return try: QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) except AttributeError: # Only for Qt>=5.6, <6. pass try: QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy( QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough ) except AttributeError: # Only for Qt>=5.14. pass _appref = app = QtGui.QApplication([" "]) # "reclaim" IPython sys.excepthook after event loop starts # without this, it defaults back to BaseIPythonApplication.excepthook # and exceptions in the Qt event loop are rendered without traceback # formatting and look like "bug in IPython". QtCore.QTimer.singleShot(0, _reclaim_excepthook) if _eventloop is None: _eventloop = QtCore.QEventLoop(app) if sys.platform == 'win32': # The QSocketNotifier method doesn't appear to work on Windows. # Use polling instead. if _timer is None: _timer = QtCore.QTimer() _timer.timeout.connect(_eventloop.quit) while not context.input_is_ready(): # NOTE: run the event loop, and after 10 ms, call `quit` to exit it. _timer.start(10) # 10 ms _exec(_eventloop) _timer.stop() else: # On POSIX platforms, we can use a file descriptor to quit the event # loop when there is input ready to read. notifier = QtCore.QSocketNotifier( context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read ) try: # connect the callback we care about before we turn it on # lambda is necessary as PyQT inspect the function signature to know # what arguments to pass to. See https://github.com/ipython/ipython/pull/12355 notifier.activated.connect(lambda: _eventloop.exit()) notifier.setEnabled(True) # only start the event loop we are not already flipped if not context.input_is_ready(): _exec(_eventloop) finally: notifier.setEnabled(False)