4
4
# Copyright (c) IPython Development Team.
5
5
# Distributed under the terms of the Modified BSD License.
6
6
7
+ from functools import partial
7
8
import os
8
9
import sys
9
10
import platform
14
15
from traitlets .config .application import Application
15
16
from IPython .utils import io
16
17
18
+
17
19
def _use_appnope ():
18
20
"""Should we use appnope for dealing with OS X app nap?
19
21
20
22
Checks if we are on OS X 10.9 or greater.
21
23
"""
22
24
return sys .platform == 'darwin' and V (platform .mac_ver ()[0 ]) >= V ('10.9' )
23
25
26
+
24
27
def _notify_stream_qt (kernel , stream ):
25
28
26
29
from IPython .external .qt_for_kernel import QtCore
@@ -34,9 +37,14 @@ def context():
34
37
yield
35
38
36
39
def process_stream_events ():
37
- while stream .getsockopt (zmq .EVENTS ) & zmq .POLLIN :
38
- with context ():
39
- kernel .do_one_iteration ()
40
+ """fall back to main loop when there's a socket event"""
41
+ # call flush to ensure that the stream doesn't lose events
42
+ # due to our consuming of the edge-triggered FD
43
+ # flush returns the number of events consumed.
44
+ # if there were any, wake it up
45
+ if stream .flush (limit = 1 ):
46
+ notifier .setEnabled (False )
47
+ kernel .app .quit ()
40
48
41
49
fd = stream .getsockopt (zmq .FD )
42
50
notifier = QtCore .QSocketNotifier (fd , QtCore .QSocketNotifier .Read , kernel .app )
@@ -88,6 +96,7 @@ def exit_decorator(exit_func):
88
96
to register a function to be called on exit
89
97
"""
90
98
func .exit_hook = exit_func
99
+ return exit_func
91
100
92
101
func .exit = exit_decorator
93
102
return func
@@ -122,20 +131,17 @@ def loop_qt4(kernel):
122
131
_loop_qt (kernel .app )
123
132
124
133
125
- @loop_qt4 .exit
126
- def loop_qt4_exit (kernel ):
127
- kernel .app .exit ()
128
-
129
-
130
134
@register_integration ('qt' , 'qt5' )
131
135
def loop_qt5 (kernel ):
132
136
"""Start a kernel with PyQt5 event loop integration."""
133
137
os .environ ['QT_API' ] = 'pyqt5'
134
138
return loop_qt4 (kernel )
135
139
136
140
141
+ # exit and watch are the same for qt 4 and 5
142
+ @loop_qt4 .exit
137
143
@loop_qt5 .exit
138
- def loop_qt5_exit (kernel ):
144
+ def loop_qt_exit (kernel ):
139
145
kernel .app .exit ()
140
146
141
147
@@ -163,9 +169,15 @@ def loop_wx(kernel):
163
169
from appnope import nope
164
170
nope ()
165
171
166
- doi = kernel .do_one_iteration
167
172
# Wx uses milliseconds
168
- poll_interval = int (1000 * kernel ._poll_interval )
173
+ poll_interval = int (1000 * kernel ._poll_interval )
174
+
175
+ def wake ():
176
+ """wake from wx"""
177
+ for stream in kernel .shell_streams :
178
+ if stream .flush (limit = 1 ):
179
+ kernel .app .ExitMainLoop ()
180
+ return
169
181
170
182
# We have to put the wx.Timer in a wx.Frame for it to fire properly.
171
183
# We make the Frame hidden when we create it in the main app below.
@@ -182,16 +194,20 @@ def on_timer(self, event):
182
194
self .func ()
183
195
184
196
# We need a custom wx.App to create our Frame subclass that has the
185
- # wx.Timer to drive the ZMQ event loop.
197
+ # wx.Timer to defer back to the tornado event loop.
186
198
class IPWxApp (wx .App ):
187
199
def OnInit (self ):
188
- self .frame = TimerFrame (doi )
200
+ self .frame = TimerFrame (wake )
189
201
self .frame .Show (False )
190
202
return True
191
203
192
204
# The redirect=False here makes sure that wx doesn't replace
193
205
# sys.stdout/stderr with its own classes.
194
- kernel .app = IPWxApp (redirect = False )
206
+ if not (
207
+ getattr (kernel , 'app' , None )
208
+ and isinstance (kernel .app , wx .App )
209
+ ):
210
+ kernel .app = IPWxApp (redirect = False )
195
211
196
212
# The import of wx on Linux sets the handler for signal.SIGINT
197
213
# to 0. This is a bug in wx or gtk. We fix by just setting it
@@ -213,35 +229,31 @@ def loop_wx_exit(kernel):
213
229
def loop_tk (kernel ):
214
230
"""Start a kernel with the Tk event loop."""
215
231
216
- try :
217
- from tkinter import Tk # Py 3
218
- except ImportError :
219
- from Tkinter import Tk # Py 2
220
- doi = kernel .do_one_iteration
221
- # Tk uses milliseconds
222
- poll_interval = int (1000 * kernel ._poll_interval )
223
- # For Tkinter, we create a Tk object and call its withdraw method.
224
- class Timer (object ):
225
- def __init__ (self , func ):
226
- self .app = Tk ()
227
- self .app .withdraw ()
228
- self .func = func
232
+ from tkinter import Tk , READABLE
229
233
230
- def on_timer (self ):
231
- self .func ()
232
- self .app .after (poll_interval , self .on_timer )
234
+ def process_stream_events (stream , * a , ** kw ):
235
+ """fall back to main loop when there's a socket event"""
236
+ if stream .flush (limit = 1 ):
237
+ app .tk .deletefilehandler (stream .getsockopt (zmq .FD ))
238
+ app .quit ()
233
239
234
- def start (self ):
235
- self .on_timer () # Call it once to get things going.
236
- self .app .mainloop ()
240
+ # For Tkinter, we create a Tk object and call its withdraw method.
241
+ kernel .app = app = Tk ()
242
+ kernel .app .withdraw ()
243
+ for stream in kernel .shell_streams :
244
+ notifier = partial (process_stream_events , stream )
245
+ # seems to be needed for tk
246
+ notifier .__name__ = 'notifier'
247
+ app .tk .createfilehandler (stream .getsockopt (zmq .FD ), READABLE , notifier )
248
+ # schedule initial call after start
249
+ app .after (0 , notifier )
237
250
238
- kernel .timer = Timer (doi )
239
- kernel .timer .start ()
251
+ app .mainloop ()
240
252
241
253
242
254
@loop_tk .exit
243
255
def loop_tk_exit (kernel ):
244
- kernel .timer . app .destroy ()
256
+ kernel .app .destroy ()
245
257
246
258
247
259
@register_integration ('gtk' )
@@ -299,8 +311,10 @@ def handle_int(etype, value, tb):
299
311
# don't let interrupts during mainloop invoke crash_handler:
300
312
sys .excepthook = handle_int
301
313
mainloop (kernel ._poll_interval )
302
- sys .excepthook = real_excepthook
303
- kernel .do_one_iteration ()
314
+ for stream in kernel .shell_streams :
315
+ if stream .flush (limit = 1 ):
316
+ # events to process, return control to kernel
317
+ return
304
318
except :
305
319
raise
306
320
except KeyboardInterrupt :
@@ -326,11 +340,24 @@ def loop_asyncio(kernel):
326
340
if loop .is_running ():
327
341
return
328
342
329
- def kernel_handler ():
330
- loop .call_soon (kernel .do_one_iteration )
331
- loop .call_later (kernel ._poll_interval , kernel_handler )
343
+ if loop .is_closed ():
344
+ # main loop is closed, create a new one
345
+ loop = asyncio .new_event_loop ()
346
+ asyncio .set_event_loop (loop )
347
+ loop ._should_close = False
348
+
349
+ # pause eventloop when there's an event on a zmq socket
350
+ def process_stream_events (stream ):
351
+ """fall back to main loop when there's a socket event"""
352
+ if stream .flush (limit = 1 ):
353
+ loop .stop ()
354
+
355
+ for stream in kernel .shell_streams :
356
+ fd = stream .getsockopt (zmq .FD )
357
+ notifier = partial (process_stream_events , stream )
358
+ loop .add_reader (fd , notifier )
359
+ loop .call_soon (notifier )
332
360
333
- loop .call_soon (kernel_handler )
334
361
while True :
335
362
error = None
336
363
try :
@@ -339,9 +366,8 @@ def kernel_handler():
339
366
continue
340
367
except Exception as e :
341
368
error = e
342
- if hasattr (loop , 'shutdown_asyncgens' ):
343
- loop .run_until_complete (loop .shutdown_asyncgens ())
344
- loop .close ()
369
+ if loop ._should_close :
370
+ loop .close ()
345
371
if error is not None :
346
372
raise error
347
373
break
@@ -352,8 +378,20 @@ def loop_asyncio_exit(kernel):
352
378
"""Exit hook for asyncio"""
353
379
import asyncio
354
380
loop = asyncio .get_event_loop ()
381
+
382
+ @asyncio .coroutine
383
+ def close_loop ():
384
+ if hasattr (loop , 'shutdown_asyncgens' ):
385
+ yield from loop .shutdown_asyncgens ()
386
+ loop ._should_close = True
387
+ loop .stop ()
388
+
355
389
if loop .is_running ():
356
- loop .call_soon (loop .stop )
390
+ close_loop ()
391
+
392
+ elif not loop .is_closed ():
393
+ loop .run_until_complete (close_loop )
394
+ loop .close ()
357
395
358
396
359
397
def enable_gui (gui , kernel = None ):
0 commit comments