main.ts 9.3 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, otherInstancePid: number): TPromise<void> {
E
Erich Gamma 已提交
29 30
		env.log('Received data from other instance', args);

31
		let killOtherInstance = args.waitForWindowClose;
32
		let usedWindows: window.VSCodeWindow[];
33

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

45 46 47 48 49
		// If the other instance is waiting to be killed, we hook up a window listener if one window
		// is being used and kill the other instance when that window is being closed
		if (args.waitForWindowClose && usedWindows && usedWindows.length === 1 && usedWindows[0]) {
			let windowToObserve = usedWindows[0];
			killOtherInstance = false; // only scenario where the "-w" switch is supported and makes sense
50

51 52 53 54 55 56
			let unbind = windows.onClose(id => {
				if (id === windowToObserve.id) {
					unbind();
					process.kill(otherInstancePid);
				}
			});
57 58 59 60
		}

		if (killOtherInstance) {
			process.kill(otherInstancePid);
E
Erich Gamma 已提交
61 62
		}

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

// 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) {
90
	let exitCode = 0;
E
Erich Gamma 已提交
91
	if (typeof arg === 'string') {
B
Benjamin Pasero 已提交
92
		env.log(arg);
E
Erich Gamma 已提交
93
	} else {
94
		exitCode = 1; // signal error to the outside
95 96 97 98 99
		if (arg.stack) {
			console.error(arg.stack);
		} else {
			console.error('Startup error: ' + arg.toString());
		}
E
Erich Gamma 已提交
100 101
	}

102 103 104 105
	// If not in wait mode or seeing an error: exit directly
	if (!env.cliArgs.waitForWindowClose || exitCode) {
		process.exit(exitCode);
	}
E
Erich Gamma 已提交
106 107
}

108
function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void {
E
Erich Gamma 已提交
109 110 111
	env.log('### VSCode main.js ###');
	env.log(env.appRoot, env.cliArgs);

J
Joao Moreno 已提交
112 113 114
	// Setup Windows mutex
	let windowsMutex: Mutex = null;
	try {
B
Benjamin Pasero 已提交
115
		const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
J
Joao Moreno 已提交
116
		windowsMutex = new Mutex(env.product.win32MutexName);
J
Joao Moreno 已提交
117 118 119 120
	} catch (e) {
		// noop
	}

E
Erich Gamma 已提交
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
	// 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.
137 138
	if (platform.isWindows && env.product.win32AppUserModelId) {
		app.setAppUserModelId(env.product.win32AppUserModelId);
E
Erich Gamma 已提交
139 140 141 142 143 144 145 146 147 148 149 150 151 152
	}

	// 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;
		}

153 154
		sharedProcess.dispose();

B
Benjamin Pasero 已提交
155 156 157
		if (windowsMutex) {
			windowsMutex.release();
		}
E
Erich Gamma 已提交
158 159 160 161 162 163
	});

	// Lifecycle
	lifecycle.manager.ready();

	// Load settings
164
	settings.manager.loadSync();
E
Erich Gamma 已提交
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

	// 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
189 190
	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 已提交
191 192 193
	} 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 {
194
		windows.manager.open({ cli: env.cliArgs, forceNewWindow: env.cliArgs.openNewWindow }); // default: read paths from cli
E
Erich Gamma 已提交
195 196 197
	}
}

198
function timebomb(): TPromise<void> {
E
Erich Gamma 已提交
199
	if (!env.product.expiryDate || Date.now() <= env.product.expiryDate) {
A
Alex Dima 已提交
200
		return TPromise.as(null);
E
Erich Gamma 已提交
201 202
	}

203
	return new TPromise<void>((c, e) => {
E
Erich Gamma 已提交
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
		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') {
225
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
226 227
			}

228 229 230 231
			// Since we are the second instance, we do not want to show the dock
			if (platform.isMacintosh) {
				app.dock.hide();
			}
B
Benjamin Pasero 已提交
232

233 234 235 236
			// 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 已提交
237

238 239
				return TPromise.wrapError(errorMsg);
			}
B
Benjamin Pasero 已提交
240

241 242 243
			// there's a running instance, let's connect to it
			return connect(env.mainIPCHandle).then(
				client => {
E
Erich Gamma 已提交
244 245 246 247
					env.log('Sending env to running instance...');

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

248
					return service.start(env.cliArgs, process.env, process.pid)
E
Erich Gamma 已提交
249
						.then(() => client.dispose())
250
						.then(() => TPromise.wrapError('Sent env to running instance. Terminating...'));
E
Erich Gamma 已提交
251 252 253
				},
				err => {
					if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
254
						return TPromise.wrapError(err);
E
Erich Gamma 已提交
255 256 257 258 259 260 261 262 263
					}

					// 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);
264
						return TPromise.wrapError(e);
E
Erich Gamma 已提交
265 266 267 268 269 270 271 272 273 274 275
					}

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

	return setup(true);
}

276 277
// 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 已提交
278 279 280 281 282 283 284 285
getUserEnvironment()
	.then(userEnv => {
		assign(process.env, userEnv);

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