Show More
@@ -0,0 +1,85 b'' | |||
|
1 | """GUI support for the IPython ZeroMQ kernel - GTK toolkit support. | |
|
2 | """ | |
|
3 | #----------------------------------------------------------------------------- | |
|
4 | # Copyright (C) 2010-2011 The IPython Development Team | |
|
5 | # | |
|
6 | # Distributed under the terms of the BSD License. The full license is in | |
|
7 | # the file COPYING.txt, distributed as part of this software. | |
|
8 | #----------------------------------------------------------------------------- | |
|
9 | ||
|
10 | #----------------------------------------------------------------------------- | |
|
11 | # Imports | |
|
12 | #----------------------------------------------------------------------------- | |
|
13 | # stdlib | |
|
14 | import sys | |
|
15 | ||
|
16 | # Third-party | |
|
17 | from gi.repository import GObject, Gtk | |
|
18 | ||
|
19 | #----------------------------------------------------------------------------- | |
|
20 | # Classes and functions | |
|
21 | #----------------------------------------------------------------------------- | |
|
22 | ||
|
23 | class GTKEmbed(object): | |
|
24 | """A class to embed a kernel into the GTK main event loop. | |
|
25 | """ | |
|
26 | def __init__(self, kernel): | |
|
27 | self.kernel = kernel | |
|
28 | # These two will later store the real gtk functions when we hijack them | |
|
29 | self.gtk_main = None | |
|
30 | self.gtk_main_quit = None | |
|
31 | ||
|
32 | def start(self): | |
|
33 | """Starts the GTK main event loop and sets our kernel startup routine. | |
|
34 | """ | |
|
35 | # Register our function to initiate the kernel and start gtk | |
|
36 | GObject.idle_add(self._wire_kernel) | |
|
37 | Gtk.main() | |
|
38 | ||
|
39 | def _wire_kernel(self): | |
|
40 | """Initializes the kernel inside GTK. | |
|
41 | ||
|
42 | This is meant to run only once at startup, so it does its job and | |
|
43 | returns False to ensure it doesn't get run again by GTK. | |
|
44 | """ | |
|
45 | self.gtk_main, self.gtk_main_quit = self._hijack_gtk() | |
|
46 | GObject.timeout_add(int(1000*self.kernel._poll_interval), | |
|
47 | self.iterate_kernel) | |
|
48 | return False | |
|
49 | ||
|
50 | def iterate_kernel(self): | |
|
51 | """Run one iteration of the kernel and return True. | |
|
52 | ||
|
53 | GTK timer functions must return True to be called again, so we make the | |
|
54 | call to :meth:`do_one_iteration` and then return True for GTK. | |
|
55 | """ | |
|
56 | self.kernel.do_one_iteration() | |
|
57 | return True | |
|
58 | ||
|
59 | def stop(self): | |
|
60 | # FIXME: this one isn't getting called because we have no reliable | |
|
61 | # kernel shutdown. We need to fix that: once the kernel has a | |
|
62 | # shutdown mechanism, it can call this. | |
|
63 | self.gtk_main_quit() | |
|
64 | sys.exit() | |
|
65 | ||
|
66 | def _hijack_gtk(self): | |
|
67 | """Hijack a few key functions in GTK for IPython integration. | |
|
68 | ||
|
69 | Modifies pyGTK's main and main_quit with a dummy so user code does not | |
|
70 | block IPython. This allows us to use %run to run arbitrary pygtk | |
|
71 | scripts from a long-lived IPython session, and when they attempt to | |
|
72 | start or stop | |
|
73 | ||
|
74 | Returns | |
|
75 | ------- | |
|
76 | The original functions that have been hijacked: | |
|
77 | - Gtk.main | |
|
78 | - Gtk.main_quit | |
|
79 | """ | |
|
80 | def dummy(*args, **kw): | |
|
81 | pass | |
|
82 | # save and trap main and main_quit from gtk | |
|
83 | orig_main, Gtk.main = Gtk.main, dummy | |
|
84 | orig_main_quit, Gtk.main_quit = Gtk.main_quit, dummy | |
|
85 | return orig_main, orig_main_quit |
@@ -1,255 +1,264 b'' | |||
|
1 | 1 | # encoding: utf-8 |
|
2 | 2 | """Event loop integration for the ZeroMQ-based kernels. |
|
3 | 3 | """ |
|
4 | 4 | |
|
5 | 5 | #----------------------------------------------------------------------------- |
|
6 | 6 | # Copyright (C) 2011 The IPython Development Team |
|
7 | 7 | |
|
8 | 8 | # Distributed under the terms of the BSD License. The full license is in |
|
9 | 9 | # the file COPYING, distributed as part of this software. |
|
10 | 10 | #----------------------------------------------------------------------------- |
|
11 | 11 | |
|
12 | 12 | |
|
13 | 13 | #----------------------------------------------------------------------------- |
|
14 | 14 | # Imports |
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | |
|
17 | 17 | import sys |
|
18 | 18 | |
|
19 | 19 | # System library imports |
|
20 | 20 | import zmq |
|
21 | 21 | |
|
22 | 22 | # Local imports |
|
23 | 23 | from IPython.config.application import Application |
|
24 | 24 | from IPython.utils import io |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | #------------------------------------------------------------------------------ |
|
28 | 28 | # Eventloops for integrating the Kernel into different GUIs |
|
29 | 29 | #------------------------------------------------------------------------------ |
|
30 | 30 | |
|
31 | 31 | def _on_os_x_10_9(): |
|
32 | 32 | import platform |
|
33 | 33 | from distutils.version import LooseVersion as V |
|
34 | 34 | return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') |
|
35 | 35 | |
|
36 | 36 | def _notify_stream_qt(kernel, stream): |
|
37 | 37 | |
|
38 | 38 | from IPython.external.qt_for_kernel import QtCore |
|
39 | 39 | |
|
40 | 40 | if _on_os_x_10_9() and kernel._darwin_app_nap: |
|
41 | 41 | from IPython.external.appnope import nope_scope as context |
|
42 | 42 | else: |
|
43 | 43 | from IPython.core.interactiveshell import NoOpContext as context |
|
44 | 44 | |
|
45 | 45 | def process_stream_events(): |
|
46 | 46 | while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN: |
|
47 | 47 | with context(): |
|
48 | 48 | kernel.do_one_iteration() |
|
49 | 49 | |
|
50 | 50 | fd = stream.getsockopt(zmq.FD) |
|
51 | 51 | notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) |
|
52 | 52 | notifier.activated.connect(process_stream_events) |
|
53 | 53 | |
|
54 | 54 | def loop_qt4(kernel): |
|
55 | 55 | """Start a kernel with PyQt4 event loop integration.""" |
|
56 | 56 | |
|
57 | 57 | from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 |
|
58 | 58 | |
|
59 | 59 | kernel.app = get_app_qt4([" "]) |
|
60 | 60 | kernel.app.setQuitOnLastWindowClosed(False) |
|
61 | 61 | |
|
62 | 62 | for s in kernel.shell_streams: |
|
63 | 63 | _notify_stream_qt(kernel, s) |
|
64 | 64 | |
|
65 | 65 | start_event_loop_qt4(kernel.app) |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | def loop_wx(kernel): |
|
69 | 69 | """Start a kernel with wx event loop support.""" |
|
70 | 70 | |
|
71 | 71 | import wx |
|
72 | 72 | from IPython.lib.guisupport import start_event_loop_wx |
|
73 | 73 | |
|
74 | 74 | if _on_os_x_10_9() and kernel._darwin_app_nap: |
|
75 | 75 | # we don't hook up App Nap contexts for Wx, |
|
76 | 76 | # just disable it outright. |
|
77 | 77 | from IPython.external.appnope import nope |
|
78 | 78 | nope() |
|
79 | 79 | |
|
80 | 80 | doi = kernel.do_one_iteration |
|
81 | 81 | # Wx uses milliseconds |
|
82 | 82 | poll_interval = int(1000*kernel._poll_interval) |
|
83 | 83 | |
|
84 | 84 | # We have to put the wx.Timer in a wx.Frame for it to fire properly. |
|
85 | 85 | # We make the Frame hidden when we create it in the main app below. |
|
86 | 86 | class TimerFrame(wx.Frame): |
|
87 | 87 | def __init__(self, func): |
|
88 | 88 | wx.Frame.__init__(self, None, -1) |
|
89 | 89 | self.timer = wx.Timer(self) |
|
90 | 90 | # Units for the timer are in milliseconds |
|
91 | 91 | self.timer.Start(poll_interval) |
|
92 | 92 | self.Bind(wx.EVT_TIMER, self.on_timer) |
|
93 | 93 | self.func = func |
|
94 | 94 | |
|
95 | 95 | def on_timer(self, event): |
|
96 | 96 | self.func() |
|
97 | 97 | |
|
98 | 98 | # We need a custom wx.App to create our Frame subclass that has the |
|
99 | 99 | # wx.Timer to drive the ZMQ event loop. |
|
100 | 100 | class IPWxApp(wx.App): |
|
101 | 101 | def OnInit(self): |
|
102 | 102 | self.frame = TimerFrame(doi) |
|
103 | 103 | self.frame.Show(False) |
|
104 | 104 | return True |
|
105 | 105 | |
|
106 | 106 | # The redirect=False here makes sure that wx doesn't replace |
|
107 | 107 | # sys.stdout/stderr with its own classes. |
|
108 | 108 | kernel.app = IPWxApp(redirect=False) |
|
109 | 109 | |
|
110 | 110 | # The import of wx on Linux sets the handler for signal.SIGINT |
|
111 | 111 | # to 0. This is a bug in wx or gtk. We fix by just setting it |
|
112 | 112 | # back to the Python default. |
|
113 | 113 | import signal |
|
114 | 114 | if not callable(signal.getsignal(signal.SIGINT)): |
|
115 | 115 | signal.signal(signal.SIGINT, signal.default_int_handler) |
|
116 | 116 | |
|
117 | 117 | start_event_loop_wx(kernel.app) |
|
118 | 118 | |
|
119 | 119 | |
|
120 | 120 | def loop_tk(kernel): |
|
121 | 121 | """Start a kernel with the Tk event loop.""" |
|
122 | 122 | |
|
123 | 123 | try: |
|
124 | 124 | from tkinter import Tk # Py 3 |
|
125 | 125 | except ImportError: |
|
126 | 126 | from Tkinter import Tk # Py 2 |
|
127 | 127 | doi = kernel.do_one_iteration |
|
128 | 128 | # Tk uses milliseconds |
|
129 | 129 | poll_interval = int(1000*kernel._poll_interval) |
|
130 | 130 | # For Tkinter, we create a Tk object and call its withdraw method. |
|
131 | 131 | class Timer(object): |
|
132 | 132 | def __init__(self, func): |
|
133 | 133 | self.app = Tk() |
|
134 | 134 | self.app.withdraw() |
|
135 | 135 | self.func = func |
|
136 | 136 | |
|
137 | 137 | def on_timer(self): |
|
138 | 138 | self.func() |
|
139 | 139 | self.app.after(poll_interval, self.on_timer) |
|
140 | 140 | |
|
141 | 141 | def start(self): |
|
142 | 142 | self.on_timer() # Call it once to get things going. |
|
143 | 143 | self.app.mainloop() |
|
144 | 144 | |
|
145 | 145 | kernel.timer = Timer(doi) |
|
146 | 146 | kernel.timer.start() |
|
147 | 147 | |
|
148 | 148 | |
|
149 | 149 | def loop_gtk(kernel): |
|
150 | 150 | """Start the kernel, coordinating with the GTK event loop""" |
|
151 | 151 | from .gui.gtkembed import GTKEmbed |
|
152 | 152 | |
|
153 | 153 | gtk_kernel = GTKEmbed(kernel) |
|
154 | 154 | gtk_kernel.start() |
|
155 | 155 | |
|
156 | 156 | |
|
157 | def loop_gtk3(kernel): | |
|
158 | """Start the kernel, coordinating with the GTK event loop""" | |
|
159 | from .gui.gtk3embed import GTKEmbed | |
|
160 | ||
|
161 | gtk_kernel = GTKEmbed(kernel) | |
|
162 | gtk_kernel.start() | |
|
163 | ||
|
164 | ||
|
157 | 165 | def loop_cocoa(kernel): |
|
158 | 166 | """Start the kernel, coordinating with the Cocoa CFRunLoop event loop |
|
159 | 167 | via the matplotlib MacOSX backend. |
|
160 | 168 | """ |
|
161 | 169 | import matplotlib |
|
162 | 170 | if matplotlib.__version__ < '1.1.0': |
|
163 | 171 | kernel.log.warn( |
|
164 | 172 | "MacOSX backend in matplotlib %s doesn't have a Timer, " |
|
165 | 173 | "falling back on Tk for CFRunLoop integration. Note that " |
|
166 | 174 | "even this won't work if Tk is linked against X11 instead of " |
|
167 | 175 | "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, " |
|
168 | 176 | "you must use matplotlib >= 1.1.0, or a native libtk." |
|
169 | 177 | ) |
|
170 | 178 | return loop_tk(kernel) |
|
171 | 179 | |
|
172 | 180 | from matplotlib.backends.backend_macosx import TimerMac, show |
|
173 | 181 | |
|
174 | 182 | # scale interval for sec->ms |
|
175 | 183 | poll_interval = int(1000*kernel._poll_interval) |
|
176 | 184 | |
|
177 | 185 | real_excepthook = sys.excepthook |
|
178 | 186 | def handle_int(etype, value, tb): |
|
179 | 187 | """don't let KeyboardInterrupts look like crashes""" |
|
180 | 188 | if etype is KeyboardInterrupt: |
|
181 | 189 | io.raw_print("KeyboardInterrupt caught in CFRunLoop") |
|
182 | 190 | else: |
|
183 | 191 | real_excepthook(etype, value, tb) |
|
184 | 192 | |
|
185 | 193 | # add doi() as a Timer to the CFRunLoop |
|
186 | 194 | def doi(): |
|
187 | 195 | # restore excepthook during IPython code |
|
188 | 196 | sys.excepthook = real_excepthook |
|
189 | 197 | kernel.do_one_iteration() |
|
190 | 198 | # and back: |
|
191 | 199 | sys.excepthook = handle_int |
|
192 | 200 | |
|
193 | 201 | t = TimerMac(poll_interval) |
|
194 | 202 | t.add_callback(doi) |
|
195 | 203 | t.start() |
|
196 | 204 | |
|
197 | 205 | # but still need a Poller for when there are no active windows, |
|
198 | 206 | # during which time mainloop() returns immediately |
|
199 | 207 | poller = zmq.Poller() |
|
200 | 208 | if kernel.control_stream: |
|
201 | 209 | poller.register(kernel.control_stream.socket, zmq.POLLIN) |
|
202 | 210 | for stream in kernel.shell_streams: |
|
203 | 211 | poller.register(stream.socket, zmq.POLLIN) |
|
204 | 212 | |
|
205 | 213 | while True: |
|
206 | 214 | try: |
|
207 | 215 | # double nested try/except, to properly catch KeyboardInterrupt |
|
208 | 216 | # due to pyzmq Issue #130 |
|
209 | 217 | try: |
|
210 | 218 | # don't let interrupts during mainloop invoke crash_handler: |
|
211 | 219 | sys.excepthook = handle_int |
|
212 | 220 | show.mainloop() |
|
213 | 221 | sys.excepthook = real_excepthook |
|
214 | 222 | # use poller if mainloop returned (no windows) |
|
215 | 223 | # scale by extra factor of 10, since it's a real poll |
|
216 | 224 | poller.poll(10*poll_interval) |
|
217 | 225 | kernel.do_one_iteration() |
|
218 | 226 | except: |
|
219 | 227 | raise |
|
220 | 228 | except KeyboardInterrupt: |
|
221 | 229 | # Ctrl-C shouldn't crash the kernel |
|
222 | 230 | io.raw_print("KeyboardInterrupt caught in kernel") |
|
223 | 231 | finally: |
|
224 | 232 | # ensure excepthook is restored |
|
225 | 233 | sys.excepthook = real_excepthook |
|
226 | 234 | |
|
227 | 235 | # mapping of keys to loop functions |
|
228 | 236 | loop_map = { |
|
229 | 237 | 'qt' : loop_qt4, |
|
230 | 238 | 'qt4': loop_qt4, |
|
231 | 239 | 'inline': None, |
|
232 | 240 | 'osx': loop_cocoa, |
|
233 | 241 | 'wx' : loop_wx, |
|
234 | 242 | 'tk' : loop_tk, |
|
235 | 243 | 'gtk': loop_gtk, |
|
244 | 'gtk3': loop_gtk3, | |
|
236 | 245 | None : None, |
|
237 | 246 | } |
|
238 | 247 | |
|
239 | 248 | |
|
240 | 249 | def enable_gui(gui, kernel=None): |
|
241 | 250 | """Enable integration with a given GUI""" |
|
242 | 251 | if gui not in loop_map: |
|
243 | 252 | e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys()) |
|
244 | 253 | raise ValueError(e) |
|
245 | 254 | if kernel is None: |
|
246 | 255 | if Application.initialized(): |
|
247 | 256 | kernel = getattr(Application.instance(), 'kernel', None) |
|
248 | 257 | if kernel is None: |
|
249 | 258 | raise RuntimeError("You didn't specify a kernel," |
|
250 | 259 | " and no IPython Application with a kernel appears to be running." |
|
251 | 260 | ) |
|
252 | 261 | loop = loop_map[gui] |
|
253 | 262 | if loop and kernel.eventloop is not None and kernel.eventloop is not loop: |
|
254 | 263 | raise RuntimeError("Cannot activate multiple GUI eventloops") |
|
255 | 264 | kernel.eventloop = loop |
General Comments 0
You need to be logged in to leave comments.
Login now