##// END OF EJS Templates
BUG: Ctrl+C crashes wx pylab kernel in qtconsole....
Pankaj Pandey -
Show More
@@ -1,206 +1,214 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Event loop integration for the ZeroMQ-based kernels.
2 """Event loop integration for the ZeroMQ-based kernels.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2011 The IPython Development Team
6 # Copyright (C) 2011 The IPython Development Team
7
7
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import sys
17 import sys
18
18
19 # System library imports.
19 # System library imports.
20 import zmq
20 import zmq
21
21
22 # Local imports.
22 # Local imports.
23 from IPython.utils import io
23 from IPython.utils import io
24
24
25 #------------------------------------------------------------------------------
25 #------------------------------------------------------------------------------
26 # Eventloops for integrating the Kernel into different GUIs
26 # Eventloops for integrating the Kernel into different GUIs
27 #------------------------------------------------------------------------------
27 #------------------------------------------------------------------------------
28
28
29 def loop_qt4(kernel):
29 def loop_qt4(kernel):
30 """Start a kernel with PyQt4 event loop integration."""
30 """Start a kernel with PyQt4 event loop integration."""
31
31
32 from IPython.external.qt_for_kernel import QtCore
32 from IPython.external.qt_for_kernel import QtCore
33 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
33 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
34
34
35 kernel.app = get_app_qt4([" "])
35 kernel.app = get_app_qt4([" "])
36 kernel.app.setQuitOnLastWindowClosed(False)
36 kernel.app.setQuitOnLastWindowClosed(False)
37 kernel.timer = QtCore.QTimer()
37 kernel.timer = QtCore.QTimer()
38 kernel.timer.timeout.connect(kernel.do_one_iteration)
38 kernel.timer.timeout.connect(kernel.do_one_iteration)
39 # Units for the timer are in milliseconds
39 # Units for the timer are in milliseconds
40 kernel.timer.start(1000*kernel._poll_interval)
40 kernel.timer.start(1000*kernel._poll_interval)
41 start_event_loop_qt4(kernel.app)
41 start_event_loop_qt4(kernel.app)
42
42
43
43
44 def loop_wx(kernel):
44 def loop_wx(kernel):
45 """Start a kernel with wx event loop support."""
45 """Start a kernel with wx event loop support."""
46
46
47 import wx
47 import wx
48 from IPython.lib.guisupport import start_event_loop_wx
48 from IPython.lib.guisupport import start_event_loop_wx
49
49
50 doi = kernel.do_one_iteration
50 doi = kernel.do_one_iteration
51 # Wx uses milliseconds
51 # Wx uses milliseconds
52 poll_interval = int(1000*kernel._poll_interval)
52 poll_interval = int(1000*kernel._poll_interval)
53
53
54 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
54 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
55 # We make the Frame hidden when we create it in the main app below.
55 # We make the Frame hidden when we create it in the main app below.
56 class TimerFrame(wx.Frame):
56 class TimerFrame(wx.Frame):
57 def __init__(self, func):
57 def __init__(self, func):
58 wx.Frame.__init__(self, None, -1)
58 wx.Frame.__init__(self, None, -1)
59 self.timer = wx.Timer(self)
59 self.timer = wx.Timer(self)
60 # Units for the timer are in milliseconds
60 # Units for the timer are in milliseconds
61 self.timer.Start(poll_interval)
61 self.timer.Start(poll_interval)
62 self.Bind(wx.EVT_TIMER, self.on_timer)
62 self.Bind(wx.EVT_TIMER, self.on_timer)
63 self.func = func
63 self.func = func
64
64
65 def on_timer(self, event):
65 def on_timer(self, event):
66 self.func()
66 self.func()
67
67
68 # We need a custom wx.App to create our Frame subclass that has the
68 # We need a custom wx.App to create our Frame subclass that has the
69 # wx.Timer to drive the ZMQ event loop.
69 # wx.Timer to drive the ZMQ event loop.
70 class IPWxApp(wx.App):
70 class IPWxApp(wx.App):
71 def OnInit(self):
71 def OnInit(self):
72 self.frame = TimerFrame(doi)
72 self.frame = TimerFrame(doi)
73 self.frame.Show(False)
73 self.frame.Show(False)
74 return True
74 return True
75
75
76 # The redirect=False here makes sure that wx doesn't replace
76 # The redirect=False here makes sure that wx doesn't replace
77 # sys.stdout/stderr with its own classes.
77 # sys.stdout/stderr with its own classes.
78 kernel.app = IPWxApp(redirect=False)
78 kernel.app = IPWxApp(redirect=False)
79
80 # The import of wx on Linux sets the handler for signal.SIGINT
81 # to 0. This is a bug in wx or gtk. We fix by just setting it
82 # back to the Python default.
83 import signal
84 if not callable(signal.getsignal(signal.SIGINT)):
85 signal.signal(signal.SIGINT, signal.default_int_handler)
86
79 start_event_loop_wx(kernel.app)
87 start_event_loop_wx(kernel.app)
80
88
81
89
82 def loop_tk(kernel):
90 def loop_tk(kernel):
83 """Start a kernel with the Tk event loop."""
91 """Start a kernel with the Tk event loop."""
84
92
85 import Tkinter
93 import Tkinter
86 doi = kernel.do_one_iteration
94 doi = kernel.do_one_iteration
87 # Tk uses milliseconds
95 # Tk uses milliseconds
88 poll_interval = int(1000*kernel._poll_interval)
96 poll_interval = int(1000*kernel._poll_interval)
89 # For Tkinter, we create a Tk object and call its withdraw method.
97 # For Tkinter, we create a Tk object and call its withdraw method.
90 class Timer(object):
98 class Timer(object):
91 def __init__(self, func):
99 def __init__(self, func):
92 self.app = Tkinter.Tk()
100 self.app = Tkinter.Tk()
93 self.app.withdraw()
101 self.app.withdraw()
94 self.func = func
102 self.func = func
95
103
96 def on_timer(self):
104 def on_timer(self):
97 self.func()
105 self.func()
98 self.app.after(poll_interval, self.on_timer)
106 self.app.after(poll_interval, self.on_timer)
99
107
100 def start(self):
108 def start(self):
101 self.on_timer() # Call it once to get things going.
109 self.on_timer() # Call it once to get things going.
102 self.app.mainloop()
110 self.app.mainloop()
103
111
104 kernel.timer = Timer(doi)
112 kernel.timer = Timer(doi)
105 kernel.timer.start()
113 kernel.timer.start()
106
114
107
115
108 def loop_gtk(kernel):
116 def loop_gtk(kernel):
109 """Start the kernel, coordinating with the GTK event loop"""
117 """Start the kernel, coordinating with the GTK event loop"""
110 from .gui.gtkembed import GTKEmbed
118 from .gui.gtkembed import GTKEmbed
111
119
112 gtk_kernel = GTKEmbed(kernel)
120 gtk_kernel = GTKEmbed(kernel)
113 gtk_kernel.start()
121 gtk_kernel.start()
114
122
115
123
116 def loop_cocoa(kernel):
124 def loop_cocoa(kernel):
117 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
125 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
118 via the matplotlib MacOSX backend.
126 via the matplotlib MacOSX backend.
119 """
127 """
120 import matplotlib
128 import matplotlib
121 if matplotlib.__version__ < '1.1.0':
129 if matplotlib.__version__ < '1.1.0':
122 kernel.log.warn(
130 kernel.log.warn(
123 "MacOSX backend in matplotlib %s doesn't have a Timer, "
131 "MacOSX backend in matplotlib %s doesn't have a Timer, "
124 "falling back on Tk for CFRunLoop integration. Note that "
132 "falling back on Tk for CFRunLoop integration. Note that "
125 "even this won't work if Tk is linked against X11 instead of "
133 "even this won't work if Tk is linked against X11 instead of "
126 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
134 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
127 "you must use matplotlib >= 1.1.0, or a native libtk."
135 "you must use matplotlib >= 1.1.0, or a native libtk."
128 )
136 )
129 return loop_tk(kernel)
137 return loop_tk(kernel)
130
138
131 from matplotlib.backends.backend_macosx import TimerMac, show
139 from matplotlib.backends.backend_macosx import TimerMac, show
132
140
133 # scale interval for sec->ms
141 # scale interval for sec->ms
134 poll_interval = int(1000*kernel._poll_interval)
142 poll_interval = int(1000*kernel._poll_interval)
135
143
136 real_excepthook = sys.excepthook
144 real_excepthook = sys.excepthook
137 def handle_int(etype, value, tb):
145 def handle_int(etype, value, tb):
138 """don't let KeyboardInterrupts look like crashes"""
146 """don't let KeyboardInterrupts look like crashes"""
139 if etype is KeyboardInterrupt:
147 if etype is KeyboardInterrupt:
140 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
148 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
141 else:
149 else:
142 real_excepthook(etype, value, tb)
150 real_excepthook(etype, value, tb)
143
151
144 # add doi() as a Timer to the CFRunLoop
152 # add doi() as a Timer to the CFRunLoop
145 def doi():
153 def doi():
146 # restore excepthook during IPython code
154 # restore excepthook during IPython code
147 sys.excepthook = real_excepthook
155 sys.excepthook = real_excepthook
148 kernel.do_one_iteration()
156 kernel.do_one_iteration()
149 # and back:
157 # and back:
150 sys.excepthook = handle_int
158 sys.excepthook = handle_int
151
159
152 t = TimerMac(poll_interval)
160 t = TimerMac(poll_interval)
153 t.add_callback(doi)
161 t.add_callback(doi)
154 t.start()
162 t.start()
155
163
156 # but still need a Poller for when there are no active windows,
164 # but still need a Poller for when there are no active windows,
157 # during which time mainloop() returns immediately
165 # during which time mainloop() returns immediately
158 poller = zmq.Poller()
166 poller = zmq.Poller()
159 poller.register(kernel.shell_socket, zmq.POLLIN)
167 poller.register(kernel.shell_socket, zmq.POLLIN)
160
168
161 while True:
169 while True:
162 try:
170 try:
163 # double nested try/except, to properly catch KeyboardInterrupt
171 # double nested try/except, to properly catch KeyboardInterrupt
164 # due to pyzmq Issue #130
172 # due to pyzmq Issue #130
165 try:
173 try:
166 # don't let interrupts during mainloop invoke crash_handler:
174 # don't let interrupts during mainloop invoke crash_handler:
167 sys.excepthook = handle_int
175 sys.excepthook = handle_int
168 show.mainloop()
176 show.mainloop()
169 sys.excepthook = real_excepthook
177 sys.excepthook = real_excepthook
170 # use poller if mainloop returned (no windows)
178 # use poller if mainloop returned (no windows)
171 # scale by extra factor of 10, since it's a real poll
179 # scale by extra factor of 10, since it's a real poll
172 poller.poll(10*poll_interval)
180 poller.poll(10*poll_interval)
173 kernel.do_one_iteration()
181 kernel.do_one_iteration()
174 except:
182 except:
175 raise
183 raise
176 except KeyboardInterrupt:
184 except KeyboardInterrupt:
177 # Ctrl-C shouldn't crash the kernel
185 # Ctrl-C shouldn't crash the kernel
178 io.raw_print("KeyboardInterrupt caught in kernel")
186 io.raw_print("KeyboardInterrupt caught in kernel")
179 finally:
187 finally:
180 # ensure excepthook is restored
188 # ensure excepthook is restored
181 sys.excepthook = real_excepthook
189 sys.excepthook = real_excepthook
182
190
183 # mapping of keys to loop functions
191 # mapping of keys to loop functions
184 loop_map = {
192 loop_map = {
185 'qt' : loop_qt4,
193 'qt' : loop_qt4,
186 'qt4': loop_qt4,
194 'qt4': loop_qt4,
187 'inline': None,
195 'inline': None,
188 'osx': loop_cocoa,
196 'osx': loop_cocoa,
189 'wx' : loop_wx,
197 'wx' : loop_wx,
190 'tk' : loop_tk,
198 'tk' : loop_tk,
191 'gtk': loop_gtk,
199 'gtk': loop_gtk,
192 None : None,
200 None : None,
193 }
201 }
194
202
195
203
196 def enable_gui(gui, kernel=None):
204 def enable_gui(gui, kernel=None):
197 """Enable integration with a given GUI"""
205 """Enable integration with a given GUI"""
198 if kernel is None:
206 if kernel is None:
199 from .ipkernel import IPKernelApp
207 from .ipkernel import IPKernelApp
200 kernel = IPKernelApp.instance().kernel
208 kernel = IPKernelApp.instance().kernel
201 if gui not in loop_map:
209 if gui not in loop_map:
202 raise ValueError("GUI %r not supported" % gui)
210 raise ValueError("GUI %r not supported" % gui)
203 loop = loop_map[gui]
211 loop = loop_map[gui]
204 if kernel.eventloop is not None and kernel.eventloop is not loop:
212 if kernel.eventloop is not None and kernel.eventloop is not loop:
205 raise RuntimeError("Cannot activate multiple GUI eventloops")
213 raise RuntimeError("Cannot activate multiple GUI eventloops")
206 kernel.eventloop = loop
214 kernel.eventloop = loop
General Comments 0
You need to be logged in to leave comments. Login now