##// END OF EJS Templates
fix `--existing` with non-localhost IP...
MinRK -
Show More
@@ -1,392 +1,395
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6
6
7 Authors:
7 Authors:
8
8
9 * Evan Patterson
9 * Evan Patterson
10 * Min RK
10 * Min RK
11 * Erik Tollerud
11 * Erik Tollerud
12 * Fernando Perez
12 * Fernando Perez
13 * Bussonnier Matthias
13 * Bussonnier Matthias
14 * Thomas Kluyver
14 * Thomas Kluyver
15 * Paul Ivanov
15 * Paul Ivanov
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # stdlib imports
23 # stdlib imports
24 import atexit
24 import atexit
25 import json
25 import json
26 import os
26 import os
27 import shutil
27 import shutil
28 import signal
28 import signal
29 import sys
29 import sys
30 import uuid
30 import uuid
31
31
32
32
33 # Local imports
33 # Local imports
34 from IPython.config.application import boolean_flag
34 from IPython.config.application import boolean_flag
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.core.profiledir import ProfileDir
36 from IPython.core.profiledir import ProfileDir
37 from IPython.kernel.blocking import BlockingKernelClient
37 from IPython.kernel.blocking import BlockingKernelClient
38 from IPython.kernel import KernelManager
38 from IPython.kernel import KernelManager
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 from IPython.utils.path import filefind
40 from IPython.utils.path import filefind
41 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.traitlets import (
42 from IPython.utils.traitlets import (
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 )
44 )
45 from IPython.kernel.zmq.kernelapp import (
45 from IPython.kernel.zmq.kernelapp import (
46 kernel_flags,
46 kernel_flags,
47 kernel_aliases,
47 kernel_aliases,
48 IPKernelApp
48 IPKernelApp
49 )
49 )
50 from IPython.kernel.zmq.session import Session, default_secure
50 from IPython.kernel.zmq.session import Session, default_secure
51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
52 from IPython.kernel.connect import ConnectionFileMixin
52
53
53 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
54 # Network Constants
55 # Network Constants
55 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
56
57
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58
59
59 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
60 # Globals
61 # Globals
61 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
62
63
63
64
64 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
65 # Aliases and Flags
66 # Aliases and Flags
66 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
67
68
68 flags = dict(kernel_flags)
69 flags = dict(kernel_flags)
69
70
70 # the flags that are specific to the frontend
71 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
72 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
73 # or it will raise an error on unrecognized flags
73 app_flags = {
74 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
77 }
77 app_flags.update(boolean_flag(
78 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
81 to force a direct exit without any confirmation.
81 """,
82 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
83 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
84 if it is owned by the frontend, and leave it alive if it is external.
84 """
85 """
85 ))
86 ))
86 flags.update(app_flags)
87 flags.update(app_flags)
87
88
88 aliases = dict(kernel_aliases)
89 aliases = dict(kernel_aliases)
89
90
90 # also scrub aliases from the frontend
91 # also scrub aliases from the frontend
91 app_aliases = dict(
92 app_aliases = dict(
92 ip = 'KernelManager.ip',
93 ip = 'IPythonConsoleApp.ip',
93 transport = 'KernelManager.transport',
94 transport = 'IPythonConsoleApp.transport',
94 hb = 'IPythonConsoleApp.hb_port',
95 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
96 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
97 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
98 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
99 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
100 f = 'IPythonConsoleApp.connection_file',
100
101
101
102
102 ssh = 'IPythonConsoleApp.sshserver',
103 ssh = 'IPythonConsoleApp.sshserver',
103 )
104 )
104 aliases.update(app_aliases)
105 aliases.update(app_aliases)
105
106
106 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
107 # Classes
108 # Classes
108 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
109
110
110 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
111 # IPythonConsole
112 # IPythonConsole
112 #-----------------------------------------------------------------------------
113 #-----------------------------------------------------------------------------
113
114
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115
116
116 try:
117 try:
117 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
118 from IPython.kernel.zmq.pylab.backend_inline import InlineBackend
118 except ImportError:
119 except ImportError:
119 pass
120 pass
120 else:
121 else:
121 classes.append(InlineBackend)
122 classes.append(InlineBackend)
122
123
123 class IPythonConsoleApp(Configurable):
124 class IPythonConsoleApp(ConnectionFileMixin):
124 name = 'ipython-console-mixin'
125 name = 'ipython-console-mixin'
125
126
126 description = """
127 description = """
127 The IPython Mixin Console.
128 The IPython Mixin Console.
128
129
129 This class contains the common portions of console client (QtConsole,
130 This class contains the common portions of console client (QtConsole,
130 ZMQ-based terminal console, etc). It is not a full console, in that
131 ZMQ-based terminal console, etc). It is not a full console, in that
131 launched terminal subprocesses will not be able to accept input.
132 launched terminal subprocesses will not be able to accept input.
132
133
133 The Console using this mixing supports various extra features beyond
134 The Console using this mixing supports various extra features beyond
134 the single-process Terminal IPython shell, such as connecting to
135 the single-process Terminal IPython shell, such as connecting to
135 existing kernel, via:
136 existing kernel, via:
136
137
137 ipython <appname> --existing
138 ipython <appname> --existing
138
139
139 as well as tunnel via SSH
140 as well as tunnel via SSH
140
141
141 """
142 """
142
143
143 classes = classes
144 classes = classes
144 flags = Dict(flags)
145 flags = Dict(flags)
145 aliases = Dict(aliases)
146 aliases = Dict(aliases)
146 kernel_manager_class = KernelManager
147 kernel_manager_class = KernelManager
147 kernel_client_class = BlockingKernelClient
148 kernel_client_class = BlockingKernelClient
148
149
149 kernel_argv = List(Unicode)
150 kernel_argv = List(Unicode)
150 # frontend flags&aliases to be stripped when building kernel_argv
151 # frontend flags&aliases to be stripped when building kernel_argv
151 frontend_flags = Any(app_flags)
152 frontend_flags = Any(app_flags)
152 frontend_aliases = Any(app_aliases)
153 frontend_aliases = Any(app_aliases)
153
154
154 # create requested profiles by default, if they don't exist:
155 # create requested profiles by default, if they don't exist:
155 auto_create = CBool(True)
156 auto_create = CBool(True)
156 # connection info:
157 # connection info:
157
158
158 sshserver = Unicode('', config=True,
159 sshserver = Unicode('', config=True,
159 help="""The SSH server to use to connect to the kernel.""")
160 help="""The SSH server to use to connect to the kernel.""")
160 sshkey = Unicode('', config=True,
161 sshkey = Unicode('', config=True,
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
162 help="""Path to the ssh key to use for logging in to the ssh server.""")
162
163
163 hb_port = Int(0, config=True,
164 hb_port = Int(0, config=True,
164 help="set the heartbeat port [default: random]")
165 help="set the heartbeat port [default: random]")
165 shell_port = Int(0, config=True,
166 shell_port = Int(0, config=True,
166 help="set the shell (ROUTER) port [default: random]")
167 help="set the shell (ROUTER) port [default: random]")
167 iopub_port = Int(0, config=True,
168 iopub_port = Int(0, config=True,
168 help="set the iopub (PUB) port [default: random]")
169 help="set the iopub (PUB) port [default: random]")
169 stdin_port = Int(0, config=True,
170 stdin_port = Int(0, config=True,
170 help="set the stdin (DEALER) port [default: random]")
171 help="set the stdin (DEALER) port [default: random]")
171 connection_file = Unicode('', config=True,
172 connection_file = Unicode('', config=True,
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173
174
174 This file will contain the IP, ports, and authentication key needed to connect
175 This file will contain the IP, ports, and authentication key needed to connect
175 clients to this kernel. By default, this file will be created in the security-dir
176 clients to this kernel. By default, this file will be created in the security-dir
176 of the current profile, but can be specified by absolute path.
177 of the current profile, but can be specified by absolute path.
177 """)
178 """)
178 def _connection_file_default(self):
179 def _connection_file_default(self):
179 return 'kernel-%i.json' % os.getpid()
180 return 'kernel-%i.json' % os.getpid()
180
181
181 existing = CUnicode('', config=True,
182 existing = CUnicode('', config=True,
182 help="""Connect to an already running kernel""")
183 help="""Connect to an already running kernel""")
183
184
184 confirm_exit = CBool(True, config=True,
185 confirm_exit = CBool(True, config=True,
185 help="""
186 help="""
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.""",
188 to force a direct exit without any confirmation.""",
188 )
189 )
189
190
190
191
191 def build_kernel_argv(self, argv=None):
192 def build_kernel_argv(self, argv=None):
192 """build argv to be passed to kernel subprocess"""
193 """build argv to be passed to kernel subprocess"""
193 if argv is None:
194 if argv is None:
194 argv = sys.argv[1:]
195 argv = sys.argv[1:]
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 # kernel should inherit default config file from frontend
197 # kernel should inherit default config file from frontend
197 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
198 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
198
199
199 def init_connection_file(self):
200 def init_connection_file(self):
200 """find the connection file, and load the info if found.
201 """find the connection file, and load the info if found.
201
202
202 The current working directory and the current profile's security
203 The current working directory and the current profile's security
203 directory will be searched for the file if it is not given by
204 directory will be searched for the file if it is not given by
204 absolute path.
205 absolute path.
205
206
206 When attempting to connect to an existing kernel and the `--existing`
207 When attempting to connect to an existing kernel and the `--existing`
207 argument does not match an existing file, it will be interpreted as a
208 argument does not match an existing file, it will be interpreted as a
208 fileglob, and the matching file in the current profile's security dir
209 fileglob, and the matching file in the current profile's security dir
209 with the latest access time will be used.
210 with the latest access time will be used.
210
211
211 After this method is called, self.connection_file contains the *full path*
212 After this method is called, self.connection_file contains the *full path*
212 to the connection file, never just its name.
213 to the connection file, never just its name.
213 """
214 """
214 if self.existing:
215 if self.existing:
215 try:
216 try:
216 cf = find_connection_file(self.existing)
217 cf = find_connection_file(self.existing)
217 except Exception:
218 except Exception:
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 self.exit(1)
220 self.exit(1)
220 self.log.info("Connecting to existing kernel: %s" % cf)
221 self.log.info("Connecting to existing kernel: %s" % cf)
221 self.connection_file = cf
222 self.connection_file = cf
222 else:
223 else:
223 # not existing, check if we are going to write the file
224 # not existing, check if we are going to write the file
224 # and ensure that self.connection_file is a full path, not just the shortname
225 # and ensure that self.connection_file is a full path, not just the shortname
225 try:
226 try:
226 cf = find_connection_file(self.connection_file)
227 cf = find_connection_file(self.connection_file)
227 except Exception:
228 except Exception:
228 # file might not exist
229 # file might not exist
229 if self.connection_file == os.path.basename(self.connection_file):
230 if self.connection_file == os.path.basename(self.connection_file):
230 # just shortname, put it in security dir
231 # just shortname, put it in security dir
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 else:
233 else:
233 cf = self.connection_file
234 cf = self.connection_file
234 self.connection_file = cf
235 self.connection_file = cf
235
236
236 # should load_connection_file only be used for existing?
237 # should load_connection_file only be used for existing?
237 # as it is now, this allows reusing ports if an existing
238 # as it is now, this allows reusing ports if an existing
238 # file is requested
239 # file is requested
239 try:
240 try:
240 self.load_connection_file()
241 self.load_connection_file()
241 except Exception:
242 except Exception:
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 self.exit(1)
244 self.exit(1)
244
245
245 def load_connection_file(self):
246 def load_connection_file(self):
246 """load ip/port/hmac config from JSON connection file"""
247 """load ip/port/hmac config from JSON connection file"""
247 # this is identical to IPKernelApp.load_connection_file
248 # this is identical to IPKernelApp.load_connection_file
248 # perhaps it can be centralized somewhere?
249 # perhaps it can be centralized somewhere?
249 try:
250 try:
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 except IOError:
252 except IOError:
252 self.log.debug("Connection File not found: %s", self.connection_file)
253 self.log.debug("Connection File not found: %s", self.connection_file)
253 return
254 return
254 self.log.debug(u"Loading connection file %s", fname)
255 self.log.debug(u"Loading connection file %s", fname)
255 with open(fname) as f:
256 with open(fname) as f:
256 cfg = json.load(f)
257 cfg = json.load(f)
257
258 self.transport = cfg.get('transport', 'tcp')
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 self.ip = cfg.get('ip', LOCALHOST)
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260
260
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 name = channel + '_port'
262 name = channel + '_port'
263 if getattr(self, name) == 0 and name in cfg:
263 if getattr(self, name) == 0 and name in cfg:
264 # not overridden by config or cl_args
264 # not overridden by config or cl_args
265 setattr(self, name, cfg[name])
265 setattr(self, name, cfg[name])
266 if 'key' in cfg:
266 if 'key' in cfg:
267 self.config.Session.key = str_to_bytes(cfg['key'])
267 self.config.Session.key = str_to_bytes(cfg['key'])
268 if 'signature_scheme' in cfg:
268 if 'signature_scheme' in cfg:
269 self.config.Session.signature_scheme = cfg['signature_scheme']
269 self.config.Session.signature_scheme = cfg['signature_scheme']
270
270
271 def init_ssh(self):
271 def init_ssh(self):
272 """set up ssh tunnels, if needed."""
272 """set up ssh tunnels, if needed."""
273 if not self.existing or (not self.sshserver and not self.sshkey):
273 if not self.existing or (not self.sshserver and not self.sshkey):
274 return
274 return
275
276 self.load_connection_file()
275 self.load_connection_file()
277
276
278 transport = self.config.KernelManager.transport
277 transport = self.transport
279 ip = self.config.KernelManager.ip
278 ip = self.ip
280
279
281 if transport != 'tcp':
280 if transport != 'tcp':
282 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
281 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
283 sys.exit(-1)
282 sys.exit(-1)
284
283
285 if self.sshkey and not self.sshserver:
284 if self.sshkey and not self.sshserver:
286 # specifying just the key implies that we are connecting directly
285 # specifying just the key implies that we are connecting directly
287 self.sshserver = ip
286 self.sshserver = ip
288 ip = LOCALHOST
287 ip = LOCALHOST
289
288
290 # build connection dict for tunnels:
289 # build connection dict for tunnels:
291 info = dict(ip=ip,
290 info = dict(ip=ip,
292 shell_port=self.shell_port,
291 shell_port=self.shell_port,
293 iopub_port=self.iopub_port,
292 iopub_port=self.iopub_port,
294 stdin_port=self.stdin_port,
293 stdin_port=self.stdin_port,
295 hb_port=self.hb_port
294 hb_port=self.hb_port
296 )
295 )
297
296
298 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
297 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
299
298
300 # tunnels return a new set of ports, which will be on localhost:
299 # tunnels return a new set of ports, which will be on localhost:
301 self.config.KernelManager.ip = LOCALHOST
300 self.ip = LOCALHOST
302 try:
301 try:
303 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
302 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
304 except:
303 except:
305 # even catch KeyboardInterrupt
304 # even catch KeyboardInterrupt
306 self.log.error("Could not setup tunnels", exc_info=True)
305 self.log.error("Could not setup tunnels", exc_info=True)
307 self.exit(1)
306 self.exit(1)
308
307
309 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
308 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
310
309
311 cf = self.connection_file
310 cf = self.connection_file
312 base,ext = os.path.splitext(cf)
311 base,ext = os.path.splitext(cf)
313 base = os.path.basename(base)
312 base = os.path.basename(base)
314 self.connection_file = os.path.basename(base)+'-ssh'+ext
313 self.connection_file = os.path.basename(base)+'-ssh'+ext
315 self.log.critical("To connect another client via this tunnel, use:")
314 self.log.critical("To connect another client via this tunnel, use:")
316 self.log.critical("--existing %s" % self.connection_file)
315 self.log.critical("--existing %s" % self.connection_file)
317
316
318 def _new_connection_file(self):
317 def _new_connection_file(self):
319 cf = ''
318 cf = ''
320 while not cf:
319 while not cf:
321 # we don't need a 128b id to distinguish kernels, use more readable
320 # we don't need a 128b id to distinguish kernels, use more readable
322 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
321 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
323 # kernels can subclass.
322 # kernels can subclass.
324 ident = str(uuid.uuid4()).split('-')[-1]
323 ident = str(uuid.uuid4()).split('-')[-1]
325 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
324 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
326 # only keep if it's actually new. Protect against unlikely collision
325 # only keep if it's actually new. Protect against unlikely collision
327 # in 48b random search space
326 # in 48b random search space
328 cf = cf if not os.path.exists(cf) else ''
327 cf = cf if not os.path.exists(cf) else ''
329 return cf
328 return cf
330
329
331 def init_kernel_manager(self):
330 def init_kernel_manager(self):
332 # Don't let Qt or ZMQ swallow KeyboardInterupts.
331 # Don't let Qt or ZMQ swallow KeyboardInterupts.
333 if self.existing:
332 if self.existing:
334 self.kernel_manager = None
333 self.kernel_manager = None
335 return
334 return
336 signal.signal(signal.SIGINT, signal.SIG_DFL)
335 signal.signal(signal.SIGINT, signal.SIG_DFL)
337
336
338 # Create a KernelManager and start a kernel.
337 # Create a KernelManager and start a kernel.
339 self.kernel_manager = self.kernel_manager_class(
338 self.kernel_manager = self.kernel_manager_class(
339 ip=self.ip,
340 transport=self.transport,
340 shell_port=self.shell_port,
341 shell_port=self.shell_port,
341 iopub_port=self.iopub_port,
342 iopub_port=self.iopub_port,
342 stdin_port=self.stdin_port,
343 stdin_port=self.stdin_port,
343 hb_port=self.hb_port,
344 hb_port=self.hb_port,
344 connection_file=self.connection_file,
345 connection_file=self.connection_file,
345 parent=self,
346 parent=self,
346 )
347 )
347 self.kernel_manager.client_factory = self.kernel_client_class
348 self.kernel_manager.client_factory = self.kernel_client_class
348 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
349 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
349 atexit.register(self.kernel_manager.cleanup_ipc_files)
350 atexit.register(self.kernel_manager.cleanup_ipc_files)
350
351
351 if self.sshserver:
352 if self.sshserver:
352 # ssh, write new connection file
353 # ssh, write new connection file
353 self.kernel_manager.write_connection_file()
354 self.kernel_manager.write_connection_file()
354
355
355 # in case KM defaults / ssh writing changes things:
356 # in case KM defaults / ssh writing changes things:
356 km = self.kernel_manager
357 km = self.kernel_manager
357 self.shell_port=km.shell_port
358 self.shell_port=km.shell_port
358 self.iopub_port=km.iopub_port
359 self.iopub_port=km.iopub_port
359 self.stdin_port=km.stdin_port
360 self.stdin_port=km.stdin_port
360 self.hb_port=km.hb_port
361 self.hb_port=km.hb_port
361 self.connection_file = km.connection_file
362 self.connection_file = km.connection_file
362
363
363 atexit.register(self.kernel_manager.cleanup_connection_file)
364 atexit.register(self.kernel_manager.cleanup_connection_file)
364
365
365 def init_kernel_client(self):
366 def init_kernel_client(self):
366 if self.kernel_manager is not None:
367 if self.kernel_manager is not None:
367 self.kernel_client = self.kernel_manager.client()
368 self.kernel_client = self.kernel_manager.client()
368 else:
369 else:
369 self.kernel_client = self.kernel_client_class(
370 self.kernel_client = self.kernel_client_class(
371 ip=self.ip,
372 transport=self.transport,
370 shell_port=self.shell_port,
373 shell_port=self.shell_port,
371 iopub_port=self.iopub_port,
374 iopub_port=self.iopub_port,
372 stdin_port=self.stdin_port,
375 stdin_port=self.stdin_port,
373 hb_port=self.hb_port,
376 hb_port=self.hb_port,
374 connection_file=self.connection_file,
377 connection_file=self.connection_file,
375 parent=self,
378 parent=self,
376 )
379 )
377
380
378 self.kernel_client.start_channels()
381 self.kernel_client.start_channels()
379
382
380
383
381
384
382 def initialize(self, argv=None):
385 def initialize(self, argv=None):
383 """
386 """
384 Classes which mix this class in should call:
387 Classes which mix this class in should call:
385 IPythonConsoleApp.initialize(self,argv)
388 IPythonConsoleApp.initialize(self,argv)
386 """
389 """
387 self.init_connection_file()
390 self.init_connection_file()
388 default_secure(self.config)
391 default_secure(self.config)
389 self.init_ssh()
392 self.init_ssh()
390 self.init_kernel_manager()
393 self.init_kernel_manager()
391 self.init_kernel_client()
394 self.init_kernel_client()
392
395
@@ -1,385 +1,382
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26
26
27 # If run on Windows, install an exception hook which pops up a
27 # If run on Windows, install an exception hook which pops up a
28 # message box. Pythonw.exe hides the console, so without this
28 # message box. Pythonw.exe hides the console, so without this
29 # the application silently fails to load.
29 # the application silently fails to load.
30 #
30 #
31 # We always install this handler, because the expectation is for
31 # We always install this handler, because the expectation is for
32 # qtconsole to bring up a GUI even if called from the console.
32 # qtconsole to bring up a GUI even if called from the console.
33 # The old handler is called, so the exception is printed as well.
33 # The old handler is called, so the exception is printed as well.
34 # If desired, check for pythonw with an additional condition
34 # If desired, check for pythonw with an additional condition
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 if os.name == 'nt':
36 if os.name == 'nt':
37 old_excepthook = sys.excepthook
37 old_excepthook = sys.excepthook
38
38
39 def gui_excepthook(exctype, value, tb):
39 def gui_excepthook(exctype, value, tb):
40 try:
40 try:
41 import ctypes, traceback
41 import ctypes, traceback
42 MB_ICONERROR = 0x00000010L
42 MB_ICONERROR = 0x00000010L
43 title = u'Error starting IPython QtConsole'
43 title = u'Error starting IPython QtConsole'
44 msg = u''.join(traceback.format_exception(exctype, value, tb))
44 msg = u''.join(traceback.format_exception(exctype, value, tb))
45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
46 finally:
46 finally:
47 # Also call the old exception hook to let it do
47 # Also call the old exception hook to let it do
48 # its thing too.
48 # its thing too.
49 old_excepthook(exctype, value, tb)
49 old_excepthook(exctype, value, tb)
50
50
51 sys.excepthook = gui_excepthook
51 sys.excepthook = gui_excepthook
52
52
53 # System library imports
53 # System library imports
54 from IPython.external.qt import QtCore, QtGui
54 from IPython.external.qt import QtCore, QtGui
55
55
56 # Local imports
56 # Local imports
57 from IPython.config.application import catch_config_error
57 from IPython.config.application import catch_config_error
58 from IPython.core.application import BaseIPythonApplication
58 from IPython.core.application import BaseIPythonApplication
59 from IPython.qt.console.ipython_widget import IPythonWidget
59 from IPython.qt.console.ipython_widget import IPythonWidget
60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
61 from IPython.qt.console import styles
61 from IPython.qt.console import styles
62 from IPython.qt.console.mainwindow import MainWindow
62 from IPython.qt.console.mainwindow import MainWindow
63 from IPython.qt.client import QtKernelClient
63 from IPython.qt.client import QtKernelClient
64 from IPython.qt.manager import QtKernelManager
64 from IPython.qt.manager import QtKernelManager
65 from IPython.utils.traitlets import (
65 from IPython.utils.traitlets import (
66 Dict, Unicode, CBool, Any
66 Dict, Unicode, CBool, Any
67 )
67 )
68
68
69 from IPython.consoleapp import (
69 from IPython.consoleapp import (
70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
71 )
71 )
72
72
73 #-----------------------------------------------------------------------------
73 #-----------------------------------------------------------------------------
74 # Network Constants
74 # Network Constants
75 #-----------------------------------------------------------------------------
75 #-----------------------------------------------------------------------------
76
76
77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
78
78
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80 # Globals
80 # Globals
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82
82
83 _examples = """
83 _examples = """
84 ipython qtconsole # start the qtconsole
84 ipython qtconsole # start the qtconsole
85 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
85 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
86 """
86 """
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Aliases and Flags
89 # Aliases and Flags
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 # start with copy of flags
92 # start with copy of flags
93 flags = dict(flags)
93 flags = dict(flags)
94 qt_flags = {
94 qt_flags = {
95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
96 "Disable rich text support."),
96 "Disable rich text support."),
97 }
97 }
98
98
99 # and app_flags from the Console Mixin
99 # and app_flags from the Console Mixin
100 qt_flags.update(app_flags)
100 qt_flags.update(app_flags)
101 # add frontend flags to the full set
101 # add frontend flags to the full set
102 flags.update(qt_flags)
102 flags.update(qt_flags)
103
103
104 # start with copy of front&backend aliases list
104 # start with copy of front&backend aliases list
105 aliases = dict(aliases)
105 aliases = dict(aliases)
106 qt_aliases = dict(
106 qt_aliases = dict(
107 style = 'IPythonWidget.syntax_style',
107 style = 'IPythonWidget.syntax_style',
108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
109 colors = 'ZMQInteractiveShell.colors',
109 colors = 'ZMQInteractiveShell.colors',
110
110
111 editor = 'IPythonWidget.editor',
111 editor = 'IPythonWidget.editor',
112 paging = 'ConsoleWidget.paging',
112 paging = 'ConsoleWidget.paging',
113 )
113 )
114 # and app_aliases from the Console Mixin
114 # and app_aliases from the Console Mixin
115 qt_aliases.update(app_aliases)
115 qt_aliases.update(app_aliases)
116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
117 # add frontend aliases to the full set
117 # add frontend aliases to the full set
118 aliases.update(qt_aliases)
118 aliases.update(qt_aliases)
119
119
120 # get flags&aliases into sets, and remove a couple that
120 # get flags&aliases into sets, and remove a couple that
121 # shouldn't be scrubbed from backend flags:
121 # shouldn't be scrubbed from backend flags:
122 qt_aliases = set(qt_aliases.keys())
122 qt_aliases = set(qt_aliases.keys())
123 qt_aliases.remove('colors')
123 qt_aliases.remove('colors')
124 qt_flags = set(qt_flags.keys())
124 qt_flags = set(qt_flags.keys())
125
125
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127 # Classes
127 # Classes
128 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
129
129
130 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
131 # IPythonQtConsole
131 # IPythonQtConsole
132 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
133
133
134
134
135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
136 name = 'ipython-qtconsole'
136 name = 'ipython-qtconsole'
137
137
138 description = """
138 description = """
139 The IPython QtConsole.
139 The IPython QtConsole.
140
140
141 This launches a Console-style application using Qt. It is not a full
141 This launches a Console-style application using Qt. It is not a full
142 console, in that launched terminal subprocesses will not be able to accept
142 console, in that launched terminal subprocesses will not be able to accept
143 input.
143 input.
144
144
145 The QtConsole supports various extra features beyond the Terminal IPython
145 The QtConsole supports various extra features beyond the Terminal IPython
146 shell, such as inline plotting with matplotlib, via:
146 shell, such as inline plotting with matplotlib, via:
147
147
148 ipython qtconsole --matplotlib=inline
148 ipython qtconsole --matplotlib=inline
149
149
150 as well as saving your session as HTML, and printing the output.
150 as well as saving your session as HTML, and printing the output.
151
151
152 """
152 """
153 examples = _examples
153 examples = _examples
154
154
155 classes = [IPythonWidget] + IPythonConsoleApp.classes
155 classes = [IPythonWidget] + IPythonConsoleApp.classes
156 flags = Dict(flags)
156 flags = Dict(flags)
157 aliases = Dict(aliases)
157 aliases = Dict(aliases)
158 frontend_flags = Any(qt_flags)
158 frontend_flags = Any(qt_flags)
159 frontend_aliases = Any(qt_aliases)
159 frontend_aliases = Any(qt_aliases)
160 kernel_client_class = QtKernelClient
160 kernel_client_class = QtKernelClient
161 kernel_manager_class = QtKernelManager
161 kernel_manager_class = QtKernelManager
162
162
163 stylesheet = Unicode('', config=True,
163 stylesheet = Unicode('', config=True,
164 help="path to a custom CSS stylesheet")
164 help="path to a custom CSS stylesheet")
165
165
166 hide_menubar = CBool(False, config=True,
166 hide_menubar = CBool(False, config=True,
167 help="Start the console window with the menu bar hidden.")
167 help="Start the console window with the menu bar hidden.")
168
168
169 maximize = CBool(False, config=True,
169 maximize = CBool(False, config=True,
170 help="Start the console window maximized.")
170 help="Start the console window maximized.")
171
171
172 plain = CBool(False, config=True,
172 plain = CBool(False, config=True,
173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
174
174
175 def _plain_changed(self, name, old, new):
175 def _plain_changed(self, name, old, new):
176 kind = 'plain' if new else 'rich'
176 kind = 'plain' if new else 'rich'
177 self.config.ConsoleWidget.kind = kind
177 self.config.ConsoleWidget.kind = kind
178 if new:
178 if new:
179 self.widget_factory = IPythonWidget
179 self.widget_factory = IPythonWidget
180 else:
180 else:
181 self.widget_factory = RichIPythonWidget
181 self.widget_factory = RichIPythonWidget
182
182
183 # the factory for creating a widget
183 # the factory for creating a widget
184 widget_factory = Any(RichIPythonWidget)
184 widget_factory = Any(RichIPythonWidget)
185
185
186 def parse_command_line(self, argv=None):
186 def parse_command_line(self, argv=None):
187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
188 self.build_kernel_argv(argv)
188 self.build_kernel_argv(argv)
189
189
190
190
191 def new_frontend_master(self):
191 def new_frontend_master(self):
192 """ Create and return new frontend attached to new kernel, launched on localhost.
192 """ Create and return new frontend attached to new kernel, launched on localhost.
193 """
193 """
194 kernel_manager = self.kernel_manager_class(
194 kernel_manager = self.kernel_manager_class(
195 connection_file=self._new_connection_file(),
195 connection_file=self._new_connection_file(),
196 parent=self,
196 parent=self,
197 autorestart=True,
197 autorestart=True,
198 )
198 )
199 # start the kernel
199 # start the kernel
200 kwargs = dict()
200 kwargs = dict()
201 kwargs['extra_arguments'] = self.kernel_argv
201 kwargs['extra_arguments'] = self.kernel_argv
202 kernel_manager.start_kernel(**kwargs)
202 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.client_factory = self.kernel_client_class
203 kernel_manager.client_factory = self.kernel_client_class
204 kernel_client = kernel_manager.client()
204 kernel_client = kernel_manager.client()
205 kernel_client.start_channels(shell=True, iopub=True)
205 kernel_client.start_channels(shell=True, iopub=True)
206 widget = self.widget_factory(config=self.config,
206 widget = self.widget_factory(config=self.config,
207 local_kernel=True)
207 local_kernel=True)
208 self.init_colors(widget)
208 self.init_colors(widget)
209 widget.kernel_manager = kernel_manager
209 widget.kernel_manager = kernel_manager
210 widget.kernel_client = kernel_client
210 widget.kernel_client = kernel_client
211 widget._existing = False
211 widget._existing = False
212 widget._may_close = True
212 widget._may_close = True
213 widget._confirm_exit = self.confirm_exit
213 widget._confirm_exit = self.confirm_exit
214 return widget
214 return widget
215
215
216 def new_frontend_slave(self, current_widget):
216 def new_frontend_slave(self, current_widget):
217 """Create and return a new frontend attached to an existing kernel.
217 """Create and return a new frontend attached to an existing kernel.
218
218
219 Parameters
219 Parameters
220 ----------
220 ----------
221 current_widget : IPythonWidget
221 current_widget : IPythonWidget
222 The IPythonWidget whose kernel this frontend is to share
222 The IPythonWidget whose kernel this frontend is to share
223 """
223 """
224 kernel_client = self.kernel_client_class(
224 kernel_client = self.kernel_client_class(
225 connection_file=current_widget.kernel_client.connection_file,
225 connection_file=current_widget.kernel_client.connection_file,
226 config = self.config,
226 config = self.config,
227 )
227 )
228 kernel_client.load_connection_file()
228 kernel_client.load_connection_file()
229 kernel_client.start_channels()
229 kernel_client.start_channels()
230 widget = self.widget_factory(config=self.config,
230 widget = self.widget_factory(config=self.config,
231 local_kernel=False)
231 local_kernel=False)
232 self.init_colors(widget)
232 self.init_colors(widget)
233 widget._existing = True
233 widget._existing = True
234 widget._may_close = False
234 widget._may_close = False
235 widget._confirm_exit = False
235 widget._confirm_exit = False
236 widget.kernel_client = kernel_client
236 widget.kernel_client = kernel_client
237 widget.kernel_manager = current_widget.kernel_manager
237 widget.kernel_manager = current_widget.kernel_manager
238 return widget
238 return widget
239
239
240 def init_qt_app(self):
240 def init_qt_app(self):
241 # separate from qt_elements, because it must run first
241 # separate from qt_elements, because it must run first
242 self.app = QtGui.QApplication([])
242 self.app = QtGui.QApplication([])
243
243
244 def init_qt_elements(self):
244 def init_qt_elements(self):
245 # Create the widget.
245 # Create the widget.
246
246
247 base_path = os.path.abspath(os.path.dirname(__file__))
247 base_path = os.path.abspath(os.path.dirname(__file__))
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
249 self.app.icon = QtGui.QIcon(icon_path)
249 self.app.icon = QtGui.QIcon(icon_path)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
250 QtGui.QApplication.setWindowIcon(self.app.icon)
251
251
252 try:
252 ip = self.ip
253 ip = self.config.KernelManager.ip
254 except AttributeError:
255 ip = LOCALHOST
256 local_kernel = (not self.existing) or ip in LOCAL_IPS
253 local_kernel = (not self.existing) or ip in LOCAL_IPS
257 self.widget = self.widget_factory(config=self.config,
254 self.widget = self.widget_factory(config=self.config,
258 local_kernel=local_kernel)
255 local_kernel=local_kernel)
259 self.init_colors(self.widget)
256 self.init_colors(self.widget)
260 self.widget._existing = self.existing
257 self.widget._existing = self.existing
261 self.widget._may_close = not self.existing
258 self.widget._may_close = not self.existing
262 self.widget._confirm_exit = self.confirm_exit
259 self.widget._confirm_exit = self.confirm_exit
263
260
264 self.widget.kernel_manager = self.kernel_manager
261 self.widget.kernel_manager = self.kernel_manager
265 self.widget.kernel_client = self.kernel_client
262 self.widget.kernel_client = self.kernel_client
266 self.window = MainWindow(self.app,
263 self.window = MainWindow(self.app,
267 confirm_exit=self.confirm_exit,
264 confirm_exit=self.confirm_exit,
268 new_frontend_factory=self.new_frontend_master,
265 new_frontend_factory=self.new_frontend_master,
269 slave_frontend_factory=self.new_frontend_slave,
266 slave_frontend_factory=self.new_frontend_slave,
270 )
267 )
271 self.window.log = self.log
268 self.window.log = self.log
272 self.window.add_tab_with_frontend(self.widget)
269 self.window.add_tab_with_frontend(self.widget)
273 self.window.init_menu_bar()
270 self.window.init_menu_bar()
274
271
275 # Ignore on OSX, where there is always a menu bar
272 # Ignore on OSX, where there is always a menu bar
276 if sys.platform != 'darwin' and self.hide_menubar:
273 if sys.platform != 'darwin' and self.hide_menubar:
277 self.window.menuBar().setVisible(False)
274 self.window.menuBar().setVisible(False)
278
275
279 self.window.setWindowTitle('IPython')
276 self.window.setWindowTitle('IPython')
280
277
281 def init_colors(self, widget):
278 def init_colors(self, widget):
282 """Configure the coloring of the widget"""
279 """Configure the coloring of the widget"""
283 # Note: This will be dramatically simplified when colors
280 # Note: This will be dramatically simplified when colors
284 # are removed from the backend.
281 # are removed from the backend.
285
282
286 # parse the colors arg down to current known labels
283 # parse the colors arg down to current known labels
287 try:
284 try:
288 colors = self.config.ZMQInteractiveShell.colors
285 colors = self.config.ZMQInteractiveShell.colors
289 except AttributeError:
286 except AttributeError:
290 colors = None
287 colors = None
291 try:
288 try:
292 style = self.config.IPythonWidget.syntax_style
289 style = self.config.IPythonWidget.syntax_style
293 except AttributeError:
290 except AttributeError:
294 style = None
291 style = None
295 try:
292 try:
296 sheet = self.config.IPythonWidget.style_sheet
293 sheet = self.config.IPythonWidget.style_sheet
297 except AttributeError:
294 except AttributeError:
298 sheet = None
295 sheet = None
299
296
300 # find the value for colors:
297 # find the value for colors:
301 if colors:
298 if colors:
302 colors=colors.lower()
299 colors=colors.lower()
303 if colors in ('lightbg', 'light'):
300 if colors in ('lightbg', 'light'):
304 colors='lightbg'
301 colors='lightbg'
305 elif colors in ('dark', 'linux'):
302 elif colors in ('dark', 'linux'):
306 colors='linux'
303 colors='linux'
307 else:
304 else:
308 colors='nocolor'
305 colors='nocolor'
309 elif style:
306 elif style:
310 if style=='bw':
307 if style=='bw':
311 colors='nocolor'
308 colors='nocolor'
312 elif styles.dark_style(style):
309 elif styles.dark_style(style):
313 colors='linux'
310 colors='linux'
314 else:
311 else:
315 colors='lightbg'
312 colors='lightbg'
316 else:
313 else:
317 colors=None
314 colors=None
318
315
319 # Configure the style
316 # Configure the style
320 if style:
317 if style:
321 widget.style_sheet = styles.sheet_from_template(style, colors)
318 widget.style_sheet = styles.sheet_from_template(style, colors)
322 widget.syntax_style = style
319 widget.syntax_style = style
323 widget._syntax_style_changed()
320 widget._syntax_style_changed()
324 widget._style_sheet_changed()
321 widget._style_sheet_changed()
325 elif colors:
322 elif colors:
326 # use a default dark/light/bw style
323 # use a default dark/light/bw style
327 widget.set_default_style(colors=colors)
324 widget.set_default_style(colors=colors)
328
325
329 if self.stylesheet:
326 if self.stylesheet:
330 # we got an explicit stylesheet
327 # we got an explicit stylesheet
331 if os.path.isfile(self.stylesheet):
328 if os.path.isfile(self.stylesheet):
332 with open(self.stylesheet) as f:
329 with open(self.stylesheet) as f:
333 sheet = f.read()
330 sheet = f.read()
334 else:
331 else:
335 raise IOError("Stylesheet %r not found." % self.stylesheet)
332 raise IOError("Stylesheet %r not found." % self.stylesheet)
336 if sheet:
333 if sheet:
337 widget.style_sheet = sheet
334 widget.style_sheet = sheet
338 widget._style_sheet_changed()
335 widget._style_sheet_changed()
339
336
340
337
341 def init_signal(self):
338 def init_signal(self):
342 """allow clean shutdown on sigint"""
339 """allow clean shutdown on sigint"""
343 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
340 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
344 # need a timer, so that QApplication doesn't block until a real
341 # need a timer, so that QApplication doesn't block until a real
345 # Qt event fires (can require mouse movement)
342 # Qt event fires (can require mouse movement)
346 # timer trick from http://stackoverflow.com/q/4938723/938949
343 # timer trick from http://stackoverflow.com/q/4938723/938949
347 timer = QtCore.QTimer()
344 timer = QtCore.QTimer()
348 # Let the interpreter run each 200 ms:
345 # Let the interpreter run each 200 ms:
349 timer.timeout.connect(lambda: None)
346 timer.timeout.connect(lambda: None)
350 timer.start(200)
347 timer.start(200)
351 # hold onto ref, so the timer doesn't get cleaned up
348 # hold onto ref, so the timer doesn't get cleaned up
352 self._sigint_timer = timer
349 self._sigint_timer = timer
353
350
354 @catch_config_error
351 @catch_config_error
355 def initialize(self, argv=None):
352 def initialize(self, argv=None):
356 self.init_qt_app()
353 self.init_qt_app()
357 super(IPythonQtConsoleApp, self).initialize(argv)
354 super(IPythonQtConsoleApp, self).initialize(argv)
358 IPythonConsoleApp.initialize(self,argv)
355 IPythonConsoleApp.initialize(self,argv)
359 self.init_qt_elements()
356 self.init_qt_elements()
360 self.init_signal()
357 self.init_signal()
361
358
362 def start(self):
359 def start(self):
363
360
364 # draw the window
361 # draw the window
365 if self.maximize:
362 if self.maximize:
366 self.window.showMaximized()
363 self.window.showMaximized()
367 else:
364 else:
368 self.window.show()
365 self.window.show()
369 self.window.raise_()
366 self.window.raise_()
370
367
371 # Start the application main loop.
368 # Start the application main loop.
372 self.app.exec_()
369 self.app.exec_()
373
370
374 #-----------------------------------------------------------------------------
371 #-----------------------------------------------------------------------------
375 # Main entry point
372 # Main entry point
376 #-----------------------------------------------------------------------------
373 #-----------------------------------------------------------------------------
377
374
378 def main():
375 def main():
379 app = IPythonQtConsoleApp()
376 app = IPythonQtConsoleApp()
380 app.initialize()
377 app.initialize()
381 app.start()
378 app.start()
382
379
383
380
384 if __name__ == '__main__':
381 if __name__ == '__main__':
385 main()
382 main()
General Comments 0
You need to be logged in to leave comments. Login now