/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import {app, shell, dialog} from 'electron'; 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'); 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'; import {getUserEnvironment} from 'vs/base/node/env'; import {TPromise} from 'vs/base/common/winjs.base'; import {GitAskpassService} from 'vs/workbench/parts/git/electron-main/askpassService'; import {spawnSharedProcess} from 'vs/workbench/electron-main/sharedProcess'; import {Mutex} from 'windows-mutex'; export class LaunchService { public start(args: env.ICommandLineArguments, userEnv: env.IProcessEnvironment, otherInstancePid: number): TPromise { env.log('Received data from other instance', args); let killOtherInstance = args.waitForWindowClose; // Otherwise handle in windows manager if (!!args.extensionDevelopmentPath) { windows.manager.openPluginDevelopmentHostWindow({ cli: args, userEnv: userEnv }); } else if (args.pathArguments.length === 0 && args.openNewWindow) { windows.manager.open({ cli: args, userEnv: userEnv, forceNewWindow: true, forceEmpty: true }); } else if (args.pathArguments.length === 0) { windows.manager.focusLastActive(args); } else { let usedWindows = windows.manager.open({ cli: args, userEnv: userEnv, forceNewWindow: args.waitForWindowClose || !args.openInSameWindow }); // 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) { let windowToObserve = usedWindows[0]; killOtherInstance = false; // only scenario where the "-w" switch is supported and makes sense let unbind = windows.onClose(id => { if (id === windowToObserve.id) { unbind(); process.kill(otherInstancePid); } }); } } if (killOtherInstance) { process.kill(otherInstancePid); } return TPromise.as(null); } } // 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) { let exitCode = 0; if (typeof arg === 'string') { env.log(arg); } else { exitCode = 1; // signal error to the outside if (arg.stack) { console.error(arg.stack); } else { console.error('Startup error: ' + arg.toString()); } } // If not in wait mode or seeing an error: exit directly if (!env.cliArgs.waitForWindowClose || exitCode) { process.exit(exitCode); } } function main(ipcServer: Server, userEnv: env.IProcessEnvironment): void { env.log('### VSCode main.js ###'); env.log(env.appRoot, env.cliArgs); // Setup Windows mutex let windowsMutex: Mutex = null; try { const Mutex = (require.__$__nodeRequire('windows-mutex')).Mutex; windowsMutex = new Mutex(env.product.win32MutexName); } catch (e) { // noop } // 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. if (platform.isWindows && env.product.win32AppUserModelId) { app.setAppUserModelId(env.product.win32AppUserModelId); } // 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; } sharedProcess.dispose(); if (windowsMutex) { windowsMutex.release(); } }); // Lifecycle lifecycle.manager.ready(); // Load settings settings.manager.loadSync(); // 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 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 } 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 { windows.manager.open({ cli: env.cliArgs, forceNewWindow: env.cliArgs.openNewWindow }); // default: read paths from cli } } function timebomb(): TPromise { if (!env.product.expiryDate || Date.now() <= env.product.expiryDate) { return TPromise.as(null); } return new TPromise((c, e) => { 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 { function setup(retry: boolean): TPromise { return serve(env.mainIPCHandle).then(null, err => { if (err.code !== 'EADDRINUSE') { return TPromise.wrapError(err); } // Since we are the second instance, we do not want to show the dock if (platform.isMacintosh) { app.dock.hide(); } // 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); return TPromise.wrapError(errorMsg); } // there's a running instance, let's connect to it return connect(env.mainIPCHandle).then( client => { env.log('Sending env to running instance...'); const service = client.getService('LaunchService', LaunchService); return service.start(env.cliArgs, process.env, process.pid) .then(() => client.dispose()) .then(() => TPromise.wrapError('Sent env to running instance. Terminating...')); }, err => { if (!retry || platform.isWindows || err.code !== 'ECONNREFUSED') { return TPromise.wrapError(err); } // 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); return TPromise.wrapError(e); } return setup(false); } ); }); } return setup(true); } // 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) getUserEnvironment() .then(userEnv => { assign(process.env, userEnv); return timebomb() .then(setupIPC) .then(ipcServer => main(ipcServer, userEnv)); }) .done(null, quit);