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