|
|
import sys
|
|
|
import os
|
|
|
#`qt_for_kernel` will import the "best" qt version
|
|
|
from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper
|
|
|
from IPython import get_ipython
|
|
|
|
|
|
# If we create a QApplication, keep a reference to it so that it doesn't get
|
|
|
# garbage collected.
|
|
|
_appref = 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
|
|
|
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)
|
|
|
|
|
|
event_loop = QtCore.QEventLoop(app)
|
|
|
print(f'`inputhook` running Qt {QtCore.qVersion()} event loop.\r')
|
|
|
if sys.platform == 'win32':
|
|
|
# The QSocketNotifier method doesn't appear to work on Windows.
|
|
|
# Use polling instead.
|
|
|
timer = QtCore.QTimer()
|
|
|
timer.timeout.connect(event_loop.quit)
|
|
|
while not context.input_is_ready():
|
|
|
# NOTE: run the event loop, and after 50 ms, call `quit` to exit it.
|
|
|
timer.start(50) # 50 ms
|
|
|
_exec(event_loop)
|
|
|
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: event_loop.exit())
|
|
|
notifier.setEnabled(True)
|
|
|
# only start the event loop we are not already flipped
|
|
|
if not context.input_is_ready():
|
|
|
_exec(event_loop)
|
|
|
finally:
|
|
|
notifier.setEnabled(False)
|
|
|
|