qt.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import sys
  2. import os
  3. from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper
  4. from IPython import get_ipython
  5. # If we create a QApplication, keep a reference to it so that it doesn't get
  6. # garbage collected.
  7. _appref = None
  8. _already_warned = False
  9. def _exec(obj):
  10. # exec on PyQt6, exec_ elsewhere.
  11. obj.exec() if hasattr(obj, "exec") else obj.exec_()
  12. def _reclaim_excepthook():
  13. shell = get_ipython()
  14. if shell is not None:
  15. sys.excepthook = shell.excepthook
  16. def inputhook(context):
  17. global _appref
  18. app = QtCore.QCoreApplication.instance()
  19. if not app:
  20. if sys.platform == 'linux':
  21. if not os.environ.get('DISPLAY') \
  22. and not os.environ.get('WAYLAND_DISPLAY'):
  23. import warnings
  24. global _already_warned
  25. if not _already_warned:
  26. _already_warned = True
  27. warnings.warn(
  28. 'The DISPLAY or WAYLAND_DISPLAY environment variable is '
  29. 'not set or empty and Qt5 requires this environment '
  30. 'variable. Deactivate Qt5 code.'
  31. )
  32. return
  33. try:
  34. QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
  35. except AttributeError: # Only for Qt>=5.6, <6.
  36. pass
  37. try:
  38. QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy(
  39. QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
  40. )
  41. except AttributeError: # Only for Qt>=5.14.
  42. pass
  43. _appref = app = QtGui.QApplication([" "])
  44. # "reclaim" IPython sys.excepthook after event loop starts
  45. # without this, it defaults back to BaseIPythonApplication.excepthook
  46. # and exceptions in the Qt event loop are rendered without traceback
  47. # formatting and look like "bug in IPython".
  48. QtCore.QTimer.singleShot(0, _reclaim_excepthook)
  49. event_loop = QtCore.QEventLoop(app)
  50. if sys.platform == 'win32':
  51. # The QSocketNotifier method doesn't appear to work on Windows.
  52. # Use polling instead.
  53. timer = QtCore.QTimer()
  54. timer.timeout.connect(event_loop.quit)
  55. while not context.input_is_ready():
  56. # NOTE: run the event loop, and after 50 ms, call `quit` to exit it.
  57. timer.start(50) # 50 ms
  58. _exec(event_loop)
  59. timer.stop()
  60. else:
  61. # On POSIX platforms, we can use a file descriptor to quit the event
  62. # loop when there is input ready to read.
  63. notifier = QtCore.QSocketNotifier(
  64. context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read
  65. )
  66. try:
  67. # connect the callback we care about before we turn it on
  68. # lambda is necessary as PyQT inspect the function signature to know
  69. # what arguments to pass to. See https://github.com/ipython/ipython/pull/12355
  70. notifier.activated.connect(lambda: event_loop.exit())
  71. notifier.setEnabled(True)
  72. # only start the event loop we are not already flipped
  73. if not context.input_is_ready():
  74. _exec(event_loop)
  75. finally:
  76. notifier.setEnabled(False)
  77. # This makes sure that the event loop is garbage collected.
  78. # See issue 14240.
  79. event_loop.setParent(None)