main.ts 16.2 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';

8
import { app, ipcMain as ipc, BrowserWindow } from 'electron';
J
Joao Moreno 已提交
9 10
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
J
Joao Moreno 已提交
11
import { parseMainProcessArgv } from 'vs/platform/environment/node/argv';
J
Joao Moreno 已提交
12
import { mkdirp } from 'vs/base/node/pfs';
13
import { validatePaths } from 'vs/code/electron-main/paths';
C
Christof Marti 已提交
14 15
import { OpenContext } from 'vs/code/common/windows';
import { IWindowsMainService, WindowsManager } from 'vs/code/electron-main/windows';
J
Joao Moreno 已提交
16 17 18
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
B
Benjamin Pasero 已提交
19
import { LifecycleService, ILifecycleService } from 'vs/code/electron-main/lifecycle';
20
import { VSCodeMenu } from 'vs/code/electron-main/menus';
21
import { getShellEnvironment } from 'vs/code/electron-main/shellEnv';
J
Joao Moreno 已提交
22
import { IUpdateService } from 'vs/platform/update/common/update';
J
Joao Moreno 已提交
23
import { UpdateChannel } from 'vs/platform/update/common/updateIpc';
J
Joao Moreno 已提交
24
import { UpdateService } from 'vs/platform/update/electron-main/updateService';
25
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main';
J
Joao Moreno 已提交
26 27
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
import { TPromise } from 'vs/base/common/winjs.base';
28 29
import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc';
import { GitAskpassService } from 'vs/workbench/parts/git/electron-main/askpassService';
30
import { spawnSharedProcess } from 'vs/code/electron-main/sharedProcess';
J
Joao Moreno 已提交
31
import { Mutex } from 'windows-mutex';
32
import { LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient, ILaunchService } from './launch';
J
Joao Moreno 已提交
33 34 35 36
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
B
Benjamin Pasero 已提交
37 38
import { ILogService, MainLogService } from 'vs/code/electron-main/log';
import { IStorageService, StorageService } from 'vs/code/electron-main/storage';
D
Daniel Imms 已提交
39
import { IBackupMainService } from 'vs/platform/backup/common/backup';
40
import { BackupChannel } from 'vs/platform/backup/common/backupIpc';
D
Daniel Imms 已提交
41
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
J
Joao Moreno 已提交
42
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
J
Joao Moreno 已提交
43 44
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
45
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
J
Joao Moreno 已提交
46
import { IRequestService } from 'vs/platform/request/node/request';
J
Joao Moreno 已提交
47
import { RequestService } from 'vs/platform/request/node/requestService';
J
Joao Moreno 已提交
48
import { IURLService } from 'vs/platform/url/common/url';
J
Joao Moreno 已提交
49 50
import { URLChannel } from 'vs/platform/url/common/urlIpc';
import { URLService } from 'vs/platform/url/electron-main/urlService';
51 52
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
53 54 55 56
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
57 58
import product from 'vs/platform/node/product';
import pkg from 'vs/platform/node/package';
B
Benjamin Pasero 已提交
59 60
import * as fs from 'original-fs';

61 62 63 64 65 66 67 68 69 70 71

ipc.on('vscode:fetchShellEnv', (event, windowId) => {
	const win = BrowserWindow.fromId(windowId);
	getShellEnvironment().then(shellEnv => {
		win.webContents.send('vscode:acceptShellEnv', shellEnv);
	}, err => {
		win.webContents.send('vscode:acceptShellEnv', {});
		console.error('Error fetching shell env', err);
	});
});

J
Johannes Rieken 已提交
72
function quit(accessor: ServicesAccessor, errorOrMessage?: Error | string): void {
J
Joao Moreno 已提交
73
	const logService = accessor.get(ILogService);
E
Erich Gamma 已提交
74

75
	let exitCode = 0;
J
Johannes Rieken 已提交
76 77 78
	if (typeof errorOrMessage === 'string') {
		logService.log(errorOrMessage);
	} else if (errorOrMessage) {
79
		exitCode = 1; // signal error to the outside
J
Johannes Rieken 已提交
80 81
		if (errorOrMessage.stack) {
			console.error(errorOrMessage.stack);
82
		} else {
J
Johannes Rieken 已提交
83
			console.error('Startup error: ' + errorOrMessage.toString());
84
		}
E
Erich Gamma 已提交
85 86
	}

87
	process.exit(exitCode); // in main, process.exit === app.exit
E
Erich Gamma 已提交
88 89
}

90
// TODO@Joao wow this is huge, clean up!
91
function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platform.IProcessEnvironment): void {
J
Joao Moreno 已提交
92 93
	const instantiationService = accessor.get(IInstantiationService);
	const logService = accessor.get(ILogService);
J
Joao Moreno 已提交
94
	const environmentService = accessor.get(IEnvironmentService);
B
Benjamin Pasero 已提交
95
	const lifecycleService = accessor.get(ILifecycleService);
J
Joao Moreno 已提交
96
	const configurationService = accessor.get(IConfigurationService) as ConfigurationService<any>;
J
Joao Moreno 已提交
97
	let windowsMainService: IWindowsMainService;
J
Joao Moreno 已提交
98 99 100 101 102 103

	// 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
B
Benjamin Pasero 已提交
104
			const friendlyError = {
J
Joao Moreno 已提交
105 106 107 108 109
				message: err.message,
				stack: err.stack
			};

			// handle on client side
J
Joao Moreno 已提交
110 111 112
			if (windowsMainService) {
				windowsMainService.sendToFocused('vscode:reportError', JSON.stringify(friendlyError));
			}
J
Joao Moreno 已提交
113
		}
J
Joao Moreno 已提交
114

J
Joao Moreno 已提交
115 116 117 118 119
		console.error('[uncaught exception in main]: ' + err);
		if (err.stack) {
			console.error(err.stack);
		}
	});
J
Joao Moreno 已提交
120

B
Benjamin Pasero 已提交
121
	logService.log('Starting VS Code in verbose mode');
122
	logService.log(`from: ${environmentService.appRoot}`);
123
	logService.log('args:', environmentService.args);
E
Erich Gamma 已提交
124

J
Joao Moreno 已提交
125 126
	// Setup Windows mutex
	let windowsMutex: Mutex = null;
B
Benjamin Pasero 已提交
127 128 129 130 131 132 133
	if (platform.isWindows) {
		try {
			const Mutex = (<any>require.__$__nodeRequire('windows-mutex')).Mutex;
			windowsMutex = new Mutex(product.win32MutexName);
		} catch (e) {
			// noop
		}
J
Joao Moreno 已提交
134 135
	}

J
Joao Moreno 已提交
136
	// Register Main IPC services
137 138 139
	const askpassService = new GitAskpassService();
	const askpassChannel = new AskpassChannel(askpassService);
	mainIpcServer.registerChannel('askpass', askpassChannel);
J
Joao Moreno 已提交
140 141

	// Create Electron IPC Server
142
	const electronIpcServer = new ElectronIPCServer();
J
Joao Moreno 已提交
143

E
Erich Gamma 已提交
144
	// Spawn shared process
J
Joao Moreno 已提交
145 146
	const initData = { args: environmentService.args };

147 148
	const sharedProcess = spawnSharedProcess(initData)
		.then(disposable => connect(environmentService.sharedIPCHandle, 'main'));
E
Erich Gamma 已提交
149

150 151 152 153
	// Create a new service collection, because the telemetry service
	// requires a connection to shared process, which was only established
	// now.
	const services = new ServiceCollection();
J
Joao Moreno 已提交
154

155
	services.set(IUpdateService, new SyncDescriptor(UpdateService));
J
Joao Moreno 已提交
156 157
	services.set(IWindowsMainService, new SyncDescriptor(WindowsManager));
	services.set(IWindowsService, new SyncDescriptor(WindowsService));
158
	services.set(ILaunchService, new SyncDescriptor(LaunchService));
159

160
	if (environmentService.isBuilt && !environmentService.isExtensionDevelopment && !!product.enableTelemetry) {
161 162 163 164 165 166 167 168
		const channel = getDelayedChannel<ITelemetryAppenderChannel>(sharedProcess.then(c => c.getChannel('telemetryAppender')));
		const appender = new TelemetryAppenderClient(channel);
		const commonProperties = resolveCommonProperties(product.commit, pkg.version);
		const piiPaths = [environmentService.appRoot, environmentService.extensionsPath];
		const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths };
		services.set(ITelemetryService, new SyncDescriptor(TelemetryService, config));
	} else {
		services.set(ITelemetryService, NullTelemetryService);
E
Erich Gamma 已提交
169 170
	}

171
	const instantiationService2 = instantiationService.createChild(services);
E
Erich Gamma 已提交
172

173
	instantiationService2.invokeFunction(accessor => {
J
Joao Moreno 已提交
174 175 176
		// TODO@Joao: unfold this
		windowsMainService = accessor.get(IWindowsMainService);

177 178 179 180 181
		// Register more Main IPC services
		const launchService = accessor.get(ILaunchService);
		const launchChannel = new LaunchChannel(launchService);
		mainIpcServer.registerChannel('launch', launchChannel);

182 183 184 185
		// Register more Electron IPC services
		const updateService = accessor.get(IUpdateService);
		const updateChannel = new UpdateChannel(updateService);
		electronIpcServer.registerChannel('update', updateChannel);
186

187 188 189 190
		const urlService = accessor.get(IURLService);
		const urlChannel = instantiationService2.createInstance(URLChannel, urlService);
		electronIpcServer.registerChannel('url', urlChannel);

191 192 193 194
		const backupService = accessor.get(IBackupMainService);
		const backupChannel = instantiationService2.createInstance(BackupChannel, backupService);
		electronIpcServer.registerChannel('backup', backupChannel);

J
Joao Moreno 已提交
195 196 197
		const windowsService = accessor.get(IWindowsService);
		const windowsChannel = new WindowsChannel(windowsService);
		electronIpcServer.registerChannel('windows', windowsChannel);
J
Joao Moreno 已提交
198
		sharedProcess.done(client => client.registerChannel('windows', windowsChannel));
199

200 201 202 203 204 205
		// 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.
		if (platform.isWindows && product.win32AppUserModelId) {
			app.setAppUserModelId(product.win32AppUserModelId);
E
Erich Gamma 已提交
206 207
		}

208 209 210 211 212
		function dispose() {
			if (mainIpcServer) {
				mainIpcServer.dispose();
				mainIpcServer = null;
			}
213

214 215 216
			if (windowsMutex) {
				windowsMutex.release();
			}
217

218
			configurationService.dispose();
B
Benjamin Pasero 已提交
219
		}
220

221 222 223
		// Dispose on app quit
		app.on('will-quit', () => {
			logService.log('App#will-quit: disposing resources');
E
Erich Gamma 已提交
224

225 226
			dispose();
		});
227

228 229 230
		// Dispose on vscode:exit
		ipc.on('vscode:exit', (event, code: number) => {
			logService.log('IPC#vscode:exit', code);
231

232 233
			dispose();
			process.exit(code); // in main, process.exit === app.exit
B
Benjamin Pasero 已提交
234
		});
235

236 237
		// Lifecycle
		lifecycleService.ready();
238

239 240
		// Propagate to clients
		windowsMainService.ready(userEnv);
E
Erich Gamma 已提交
241

242
		// Open our first window
C
chrmarti 已提交
243
		const context = !!process.env['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
244
		if (environmentService.args['new-window'] && environmentService.args._.length === 0) {
245
			windowsMainService.open({ context, cli: environmentService.args, forceNewWindow: true, forceEmpty: true, initialStartup: true }); // new window if "-n" was used without paths
246
		} else if (global.macOpenFiles && global.macOpenFiles.length && (!environmentService.args._ || !environmentService.args._.length)) {
247
			windowsMainService.open({ context: OpenContext.DOCK, cli: environmentService.args, pathsToOpen: global.macOpenFiles, initialStartup: true }); // mac: open-file event received on startup
248
		} else {
249
			windowsMainService.open({ context, cli: environmentService.args, forceNewWindow: environmentService.args['new-window'] || (!environmentService.args._.length && environmentService.args['unity-launch']), diffMode: environmentService.args.diff, initialStartup: true }); // default: read paths from cli
250
		}
251 252

		// Install Menu
253
		instantiationService2.createInstance(VSCodeMenu);
254
	});
E
Erich Gamma 已提交
255 256
}

J
Joao Moreno 已提交
257 258
function setupIPC(accessor: ServicesAccessor): TPromise<Server> {
	const logService = accessor.get(ILogService);
259
	const environmentService = accessor.get(IEnvironmentService);
J
Joao Moreno 已提交
260

B
Benjamin Pasero 已提交
261 262 263 264 265 266
	function allowSetForegroundWindow(service: LaunchChannelClient): TPromise<void> {
		let promise = TPromise.as(null);
		if (platform.isWindows) {
			promise = service.getMainProcessId()
				.then(processId => {
					logService.log('Sending some foreground love to the running instance:', processId);
J
Johannes Rieken 已提交
267

B
Benjamin Pasero 已提交
268 269 270 271 272 273 274 275 276 277 278 279
					try {
						const { allowSetForegroundWindow } = <any>require.__$__nodeRequire('windows-foreground-love');
						allowSetForegroundWindow(processId);
					} catch (e) {
						// noop
					}
				});
		}

		return promise;
	}

E
Erich Gamma 已提交
280
	function setup(retry: boolean): TPromise<Server> {
281
		return serve(environmentService.mainIPCHandle).then(server => {
282 283 284 285 286 287
			if (platform.isMacintosh) {
				app.dock.show(); // dock might be hidden at this case due to a retry
			}

			return server;
		}, err => {
E
Erich Gamma 已提交
288
			if (err.code !== 'EADDRINUSE') {
289
				return TPromise.wrapError(err);
E
Erich Gamma 已提交
290 291
			}

292 293 294 295
			// Since we are the second instance, we do not want to show the dock
			if (platform.isMacintosh) {
				app.dock.hide();
			}
B
Benjamin Pasero 已提交
296

297
			// there's a running instance, let's connect to it
298
			return connect(environmentService.mainIPCHandle, 'main').then(
299
				client => {
J
Joao Moreno 已提交
300 301

					// Tests from CLI require to be the only instance currently (TODO@Ben support multiple instances and output)
J
Joao Moreno 已提交
302
					if (environmentService.extensionTestsPath && !environmentService.debugExtensionHost.break) {
J
Joao Moreno 已提交
303 304 305 306 307 308
						const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.';
						console.error(msg);
						client.dispose();
						return TPromise.wrapError(msg);
					}

J
Joao Moreno 已提交
309
					logService.log('Sending env to running instance...');
E
Erich Gamma 已提交
310

J
Joao Moreno 已提交
311 312
					const channel = client.getChannel<ILaunchChannel>('launch');
					const service = new LaunchChannelClient(channel);
E
Erich Gamma 已提交
313

B
Benjamin Pasero 已提交
314
					return allowSetForegroundWindow(service)
315
						.then(() => service.start(environmentService.args, process.env))
E
Erich Gamma 已提交
316
						.then(() => client.dispose())
317
						.then(() => TPromise.wrapError('Sent env to running instance. Terminating...'));
E
Erich Gamma 已提交
318 319 320
				},
				err => {
					if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') {
321
						return TPromise.wrapError(err);
E
Erich Gamma 已提交
322 323 324 325 326 327
					}

					// 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 {
328
						fs.unlinkSync(environmentService.mainIPCHandle);
E
Erich Gamma 已提交
329
					} catch (e) {
J
Joao Moreno 已提交
330
						logService.log('Fatal error deleting obsolete instance handle', e);
331
						return TPromise.wrapError(e);
E
Erich Gamma 已提交
332 333 334 335 336 337 338 339 340 341 342
					}

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

	return setup(true);
}

343

J
Joao Moreno 已提交
344
function createPaths(environmentService: IEnvironmentService): TPromise<any> {
345 346 347 348 349 350
	const paths = [
		environmentService.appSettingsHome,
		environmentService.userProductHome,
		environmentService.extensionsPath,
		environmentService.nodeCachedDataDir
	];
J
Joao Moreno 已提交
351 352 353
	return TPromise.join(paths.map(p => mkdirp(p))) as TPromise<any>;
}

B
Benjamin Pasero 已提交
354
function createServices(args: ParsedArgs): IInstantiationService {
J
Joao Moreno 已提交
355 356 357 358
	const services = new ServiceCollection();

	services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService, args, process.execPath));
	services.set(ILogService, new SyncDescriptor(MainLogService));
B
Benjamin Pasero 已提交
359
	services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
J
Joao Moreno 已提交
360
	services.set(IStorageService, new SyncDescriptor(StorageService));
361
	services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
J
Joao Moreno 已提交
362
	services.set(IRequestService, new SyncDescriptor(RequestService));
J
Joao Moreno 已提交
363
	services.set(IURLService, new SyncDescriptor(URLService, args['open-url']));
D
Daniel Imms 已提交
364
	services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
J
Joao Moreno 已提交
365

366
	return new InstantiationService(services, true);
367 368
}

J
Joao Moreno 已提交
369 370 371 372 373
function start(): void {
	let args: ParsedArgs;

	try {
		args = parseMainProcessArgv(process.argv);
374
		args = validatePaths(args);
J
Joao Moreno 已提交
375 376 377 378 379 380
	} catch (err) {
		console.error(err.message);
		process.exit(1);
		return;
	}

381
	const instantiationService = createServices(args);
J
Joao Moreno 已提交
382

J
Joao Moreno 已提交
383

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
	return instantiationService.invokeFunction(accessor => {
		const environmentService = accessor.get(IEnvironmentService);
		const instanceEnv: typeof process.env = {
			VSCODE_PID: String(process.pid),
			VSCODE_IPC_HOOK: environmentService.mainIPCHandle,
			VSCODE_SHARED_IPC_HOOK: environmentService.sharedIPCHandle,
			VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG']
		};

		// Patch `process.env` with the instance's environment
		assign(process.env, instanceEnv);

		return instantiationService.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
			.then(() => instantiationService.invokeFunction(setupIPC))
			.then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, instanceEnv));
J
Joao Moreno 已提交
399
	}).done(null, err => instantiationService.invokeFunction(quit, err));
J
Joao Moreno 已提交
400
}
401

402
start();