main.ts 8.9 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

B
Benjamin Pasero 已提交
8
import {app, shell, dialog} from 'electron';
E
Erich Gamma 已提交
9 10 11 12 13 14
import fs = require('fs');
import nls = require('vs/nls');
import {assign} from 'vs/base/common/objects';
import platform = require('vs/base/common/platform');
import env = require('vs/workbench/electron-main/env');
import windows = require('vs/workbench/electron-main/windows');
15
import window = require('vs/workbench/electron-main/window');
E
Erich Gamma 已提交
16 17 18 19 20
import lifecycle = require('vs/workbench/electron-main/lifecycle');
import menu = require('vs/workbench/electron-main/menus');
import settings = require('vs/workbench/electron-main/settings');
import {Instance as UpdateManager} from 'vs/workbench/electron-main/update-manager';
import {Server, serve, connect} from 'vs/base/node/service.net';
21
import {getUserEnvironment} from 'vs/base/node/env';
22
import {TPromise} from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
23
import {GitAskpassService} from 'vs/workbench/parts/git/electron-main/askpassService';
24
import {spawnSharedProcess} from 'vs/workbench/electron-main/sharedProcess';
25
import {Mutex} from 'windows-mutex';
E
Erich Gamma 已提交
26 27

export class LaunchService {
28
	public start(args: env.ICommandLineArguments, userEnv: env.IProcessEnvironment): TPromise<void> {
E
Erich Gamma 已提交
29 30 31
		env.log('Received data from other instance', args);

		// Otherwise handle in windows manager
32
		let usedWindows: window.VSCodeWindow[];
A
Alex Dima 已提交
33
		if (!!args.extensionDevelopmentPath) {
34
			windows.manager.openPluginDevelopmentHostWindow({ cli: args, userEnv: userEnv });
E
Erich Gamma 已提交
35
		} else if (args.pathArguments.length === 0 && args.openNewWindow) {
36
			usedWindows = windows.manager.open({ cli: args, userEnv: userEnv, forceNewWindow: true, forceEmpty: true });
E
Erich Gamma 已提交
37
		} else if (args.pathArguments.length === 0) {
38
			usedWindows = [windows.manager.focusLastActive(args)];
E
Erich Gamma 已提交
39
		} else {
40 41
			usedWindows = windows.manager.open({ cli: args, userEnv: userEnv, forceNewWindow: args.waitForWindowClose || args.openNewWindow, preferNewWindow: !args.openInSameWindow });
		}
42

43
		// If the other instance is waiting to be killed, we hook up a window listener if one window
44
		// is being used and only then resolve the startup promise which will kill this second instance
45
		if (args.waitForWindowClose && usedWindows && usedWindows.length === 1 && usedWindows[0]) {
46 47 48 49 50 51 52 53
			const windowId = usedWindows[0].id;

			return new TPromise<void>((c, e) => {

				const unbind = windows.onClose(id => {
					if (id === windowId) {
						unbind();
						c(null);
54
					}
55
				});
56
			});
57 58
		}

A
Alex Dima 已提交
59
		return TPromise.as(null);
E
Erich Gamma 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
	}
}

// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
process.on('uncaughtException', (err: any) => {
	if (err) {

		// take only the message and stack property
		let friendlyError = {
			message: err.message,
			stack: err.stack
		};

		// handle on client side
		windows.manager.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
	}

	console.error('[uncaught exception in main]: ' + err);
	if (err.stack) {
		console.error(err.stack);
	}
});

function quit(error?: Error);
function quit(message?: string);
function quit(arg?: any) {
86
	let exitCode = 0;
E
Erich Gamma 已提交
87
	if (typeof arg === 'string') {
B
Benjamin Pasero 已提交
88
		env.log(arg);
E
Erich Gamma 已提交
89
	} else {
90
		exitCode = 1; // signal error to the outside
91 92 93 94 95
		if (arg.stack) {
			console.error(arg.stack);
		} else {
			console.error('Startup error: ' + arg.toString());
		}
E
Erich Gamma 已提交
96 97
	}

98
	process.exit(exitCode);
E
Erich Gamma 已提交
99 100
}

101
function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void {
E
Erich Gamma 已提交
102 103 104
	env.log('### VSCode main.js ###');
	env.log(env.appRoot, env.cliArgs);

J
Joao Moreno 已提交
105 106 107
	// Setup Windows mutex
	let windowsMutex: Mutex = null;
	try {
B
Benjamin Pasero 已提交
108
		const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
J
Joao Moreno 已提交
109
		windowsMutex = new Mutex(env.product.win32MutexName);
J
Joao Moreno 已提交
110 111 112 113
	} catch (e) {
		// noop
	}

E
Erich Gamma 已提交
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
	// Register IPC services
	ipcServer.registerService('LaunchService', new LaunchService());
	ipcServer.registerService('GitAskpassService', new GitAskpassService());

	// Used by sub processes to communicate back to the main instance
	process.env['VSCODE_PID'] = '' + process.pid;
	process.env['VSCODE_IPC_HOOK'] = env.mainIPCHandle;
	process.env['VSCODE_SHARED_IPC_HOOK'] = env.sharedIPCHandle;

	// Spawn shared process
	const sharedProcess = spawnSharedProcess();

	// Make sure we associate the program with the app user model id
	// This will help Windows to associate the running program with
	// any shortcut that is pinned to the taskbar and prevent showing
	// two icons in the taskbar for the same app.
130 131
	if (platform.isWindows && env.product.win32AppUserModelId) {
		app.setAppUserModelId(env.product.win32AppUserModelId);
E
Erich Gamma 已提交
132 133 134 135 136 137 138 139 140 141 142 143 144 145
	}

	// Set programStart in the global scope
	global.programStart = env.cliArgs.programStart;

	// Dispose on app quit
	app.on('will-quit', () => {
		env.log('App#dispose: deleting running instance handle');

		if (ipcServer) {
			ipcServer.dispose();
			ipcServer = null;
		}

146 147
		sharedProcess.dispose();

B
Benjamin Pasero 已提交
148 149 150
		if (windowsMutex) {
			windowsMutex.release();
		}
E
Erich Gamma 已提交
151 152 153 154 155 156
	});

	// Lifecycle
	lifecycle.manager.ready();

	// Load settings
157
	settings.manager.loadSync();
E
Erich Gamma 已提交
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

	// Propagate to clients
	windows.manager.ready(userEnv);

	// Install Menu
	menu.manager.ready();

	// Install Tasks
	if (platform.isWindows && env.isBuilt) {
		app.setUserTasks([
			{
				title: nls.localize('newWindow', "New Window"),
				program: process.execPath,
				arguments: '-n', // force new window
				iconPath: process.execPath,
				iconIndex: 0
			}
		]);
	}

	// Setup auto update
	UpdateManager.initialize();

	// Open our first window
182 183
	if (env.cliArgs.openNewWindow && env.cliArgs.pathArguments.length === 0) {
		windows.manager.open({ cli: env.cliArgs, forceNewWindow: true, forceEmpty: true }); // new window if "-n" was used without paths
E
Erich Gamma 已提交
184 185 186
	} else if (global.macOpenFiles && global.macOpenFiles.length && (!env.cliArgs.pathArguments || !env.cliArgs.pathArguments.length)) {
		windows.manager.open({ cli: env.cliArgs, pathsToOpen: global.macOpenFiles }); // mac: open-file event received on startup
	} else {
187
		windows.manager.open({ cli: env.cliArgs, forceNewWindow: env.cliArgs.openNewWindow }); // default: read paths from cli
E
Erich Gamma 已提交
188 189 190
	}
}

191
function timebomb(): TPromise<void> {
E
Erich Gamma 已提交
192
	if (!env.product.expiryDate || Date.now() <= env.product.expiryDate) {
A
Alex Dima 已提交
193
		return TPromise.as(null);
E
Erich Gamma 已提交
194 195
	}

196
	return new TPromise<void>((c, e) => {
E
Erich Gamma 已提交
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
		dialog.showMessageBox({
			type: 'warning',
			title: env.product.nameLong,
			message: nls.localize('expired', "Expired"),
			detail: nls.localize('expiredDetail', "This pre-release version of {0} has expired.\n\nPlease visit {1} to download the current release.", env.product.nameLong, env.product.expiryUrl),
			buttons: [nls.localize('quit', "Quit"), nls.localize('openWebSite', "Open Web Site")],
			noLink: true,
		}, (i) => {
			if (i === 1) {
				shell.openExternal(env.product.expiryUrl);
			}

			e('Product expired');
		});
	});
}

function setupIPC(): TPromise<Server> {
	function setup(retry: boolean): TPromise<Server> {
		return serve(env.mainIPCHandle).then(null, err => {
			if (err.code !== 'EADDRINUSE') {
218
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
219 220
			}

221 222 223 224
			// Since we are the second instance, we do not want to show the dock
			if (platform.isMacintosh) {
				app.dock.hide();
			}
B
Benjamin Pasero 已提交
225

226 227 228 229
			// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
			if (env.isTestingFromCli) {
				const errorMsg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
				console.error(errorMsg);
B
Benjamin Pasero 已提交
230

231 232
				return TPromise.wrapError(errorMsg);
			}
B
Benjamin Pasero 已提交
233

234 235 236
			// there's a running instance, let's connect to it
			return connect(env.mainIPCHandle).then(
				client => {
E
Erich Gamma 已提交
237 238 239 240
					env.log('Sending env to running instance...');

					const service = client.getService<LaunchService>('LaunchService', LaunchService);

241
					return service.start(env.cliArgs, process.env)
E
Erich Gamma 已提交
242
						.then(() => client.dispose())
243
						.then(() => TPromise.wrapError('Sent env to running instance. Terminating...'));
E
Erich Gamma 已提交
244 245 246
				},
				err => {
					if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
247
						return TPromise.wrapError(err);
E
Erich Gamma 已提交
248 249 250 251 252 253 254 255 256
					}

					// it happens on Linux and OS X that the pipe is left behind
					// let's delete it, since we can't connect to it
					// and the retry the whole thing
					try {
						fs.unlinkSync(env.mainIPCHandle);
					} catch (e) {
						env.log('Fatal error deleting obsolete instance handle', e);
257
						return TPromise.wrapError(e);
E
Erich Gamma 已提交
258 259 260 261 262 263 264 265 266 267 268
					}

					return setup(false);
				}
			);
		});
	}

	return setup(true);
}

269 270
// On some platforms we need to manually read from the global environment variables
// and assign them to the process environment (e.g. when doubleclick app on Mac)
E
Erich Gamma 已提交
271 272 273 274 275 276 277 278
getUserEnvironment()
	.then(userEnv => {
		assign(process.env, userEnv);

		return timebomb()
			.then(setupIPC)
			.then(ipcServer => main(ipcServer, userEnv));
	})
279
	.done(null, quit);