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