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