@@ -82,15 +82,19 @@ def _notify_stream_qt(kernel):
82
82
def enum_helper (name ):
83
83
return operator .attrgetter (name .rpartition ("." )[0 ])(sys .modules [QtCore .__package__ ])
84
84
85
+ def exit_loop ():
86
+ """fall back to main loop"""
87
+ kernel ._qt_notifier .setEnabled (False )
88
+ kernel .app .qt_event_loop .quit ()
89
+
85
90
def process_stream_events ():
86
91
"""fall back to main loop when there's a socket event"""
87
92
# call flush to ensure that the stream doesn't lose events
88
93
# due to our consuming of the edge-triggered FD
89
94
# flush returns the number of events consumed.
90
95
# if there were any, wake it up
91
96
if kernel .shell_stream .flush (limit = 1 ):
92
- kernel ._qt_notifier .setEnabled (False )
93
- kernel .app .qt_event_loop .quit ()
97
+ exit_loop ()
94
98
95
99
if not hasattr (kernel , "_qt_notifier" ):
96
100
fd = kernel .shell_stream .getsockopt (zmq .FD )
@@ -101,18 +105,31 @@ def process_stream_events():
101
105
else :
102
106
kernel ._qt_notifier .setEnabled (True )
103
107
108
+ # allow for scheduling exits from the loop in case a timeout needs to
109
+ # be set from the kernel level
110
+ def _schedule_exit (delay ):
111
+ """schedule fall back to main loop in [delay] seconds"""
112
+ # The signatures of QtCore.QTimer.singleShot are inconsistent between PySide and PyQt
113
+ # if setting the TimerType, so we create a timer explicitly and store it
114
+ # to avoid a memory leak.
115
+ # PreciseTimer is needed so we exit after _at least_ the specified delay, not within 5% of it
116
+ if not hasattr (kernel , "_qt_timer" ):
117
+ kernel ._qt_timer = QtCore .QTimer (kernel .app )
118
+ kernel ._qt_timer .setSingleShot (True )
119
+ kernel ._qt_timer .setTimerType (enum_helper ("QtCore.Qt.TimerType" ).PreciseTimer )
120
+ kernel ._qt_timer .timeout .connect (exit_loop )
121
+ kernel ._qt_timer .start (int (1000 * delay ))
122
+
123
+ loop_qt ._schedule_exit = _schedule_exit
124
+
104
125
# there may already be unprocessed events waiting.
105
126
# these events will not wake zmq's edge-triggered FD
106
127
# since edge-triggered notification only occurs on new i/o activity.
107
128
# process all the waiting events immediately
108
129
# so we start in a clean state ensuring that any new i/o events will notify.
109
130
# schedule first call on the eventloop as soon as it's running,
110
131
# so we don't block here processing events
111
- if not hasattr (kernel , "_qt_timer" ):
112
- kernel ._qt_timer = QtCore .QTimer (kernel .app )
113
- kernel ._qt_timer .setSingleShot (True )
114
- kernel ._qt_timer .timeout .connect (process_stream_events )
115
- kernel ._qt_timer .start (0 )
132
+ QtCore .QTimer .singleShot (0 , process_stream_events )
116
133
117
134
118
135
@register_integration ("qt" , "qt5" , "qt6" )
@@ -229,23 +246,33 @@ def __init__(self, app):
229
246
self .app = app
230
247
self .app .withdraw ()
231
248
232
- def process_stream_events (stream , * a , ** kw ):
249
+ def exit_loop ():
250
+ """fall back to main loop"""
251
+ app .tk .deletefilehandler (kernel .shell_stream .getsockopt (zmq .FD ))
252
+ app .quit ()
253
+ app .destroy ()
254
+ del kernel .app_wrapper
255
+
256
+ def process_stream_events (* a , ** kw ):
233
257
"""fall back to main loop when there's a socket event"""
234
- if stream .flush (limit = 1 ):
235
- app .tk .deletefilehandler (stream .getsockopt (zmq .FD ))
236
- app .quit ()
237
- app .destroy ()
238
- del kernel .app_wrapper
258
+ if kernel .shell_stream .flush (limit = 1 ):
259
+ exit_loop ()
260
+
261
+ # allow for scheduling exits from the loop in case a timeout needs to
262
+ # be set from the kernel level
263
+ def _schedule_exit (delay ):
264
+ """schedule fall back to main loop in [delay] seconds"""
265
+ app .after (int (1000 * delay ), exit_loop )
266
+
267
+ loop_tk ._schedule_exit = _schedule_exit
239
268
240
269
# For Tkinter, we create a Tk object and call its withdraw method.
241
270
kernel .app_wrapper = BasicAppWrapper (app )
242
-
243
- notifier = partial (process_stream_events , kernel .shell_stream )
244
- # seems to be needed for tk
245
- notifier .__name__ = "notifier" # type:ignore[attr-defined]
246
- app .tk .createfilehandler (kernel .shell_stream .getsockopt (zmq .FD ), READABLE , notifier )
271
+ app .tk .createfilehandler (
272
+ kernel .shell_stream .getsockopt (zmq .FD ), READABLE , process_stream_events
273
+ )
247
274
# schedule initial call after start
248
- app .after (0 , notifier )
275
+ app .after (0 , process_stream_events )
249
276
250
277
app .mainloop ()
251
278
@@ -560,6 +587,10 @@ def enable_gui(gui, kernel=None):
560
587
# User wants to turn off integration; clear any evidence if Qt was the last one.
561
588
if hasattr (kernel , "app" ):
562
589
delattr (kernel , "app" )
590
+ if hasattr (kernel , "_qt_notifier" ):
591
+ delattr (kernel , "_qt_notifier" )
592
+ if hasattr (kernel , "_qt_timer" ):
593
+ delattr (kernel , "_qt_timer" )
563
594
else :
564
595
if gui .startswith ("qt" ):
565
596
# Prepare the kernel here so any exceptions are displayed in the client.
0 commit comments