/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/node/ipc'; import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI, UriComponents } from 'vs/base/common/uri'; import { BrowserWindow } from 'electron'; import { Event } from 'vs/base/common/event'; import { hasArgs } from 'vs/platform/environment/node/argv'; export const ID = 'launchService'; export const ILaunchService = createDecorator(ID); export interface IStartArguments { args: ParsedArgs; userEnv: IProcessEnvironment; } export interface IWindowInfo { pid: number; title: string; folderURIs: UriComponents[]; } export interface IMainProcessInfo { mainPID: number; // All arguments after argv[0], the exec path mainArguments: string[]; windows: IWindowInfo[]; } function parseOpenUrl(args: ParsedArgs): URI[] { if (args['open-url'] && args._urls && args._urls.length > 0) { // --open-url must contain -- followed by the url(s) // process.argv is used over args._ as args._ are resolved to file paths at this point return args._urls .map(url => { try { return URI.parse(url); } catch (err) { return null; } }) .filter(uri => !!uri); } return []; } export interface ILaunchService { _serviceBrand: any; start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise; getMainProcessId(): TPromise; getMainProcessInfo(): TPromise; getLogsPath(): TPromise; } export interface ILaunchChannel extends IChannel { call(command: 'start', arg: IStartArguments): TPromise; call(command: 'get-main-process-id', arg: null): TPromise; call(command: 'get-main-process-info', arg: null): TPromise; call(command: 'get-logs-path', arg: null): TPromise; call(command: string, arg: any): TPromise; } export class LaunchChannel implements ILaunchChannel { constructor(private service: ILaunchService) { } listen(event: string): Event { throw new Error('No event found'); } call(command: string, arg: any): TPromise { switch (command) { case 'start': const { args, userEnv } = arg as IStartArguments; return this.service.start(args, userEnv); case 'get-main-process-id': return this.service.getMainProcessId(); case 'get-main-process-info': return this.service.getMainProcessInfo(); case 'get-logs-path': return this.service.getLogsPath(); } return undefined; } } export class LaunchChannelClient implements ILaunchService { _serviceBrand: any; constructor(private channel: ILaunchChannel) { } start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise { return this.channel.call('start', { args, userEnv }); } getMainProcessId(): TPromise { return this.channel.call('get-main-process-id', null); } getMainProcessInfo(): TPromise { return this.channel.call('get-main-process-info', null); } getLogsPath(): TPromise { return this.channel.call('get-logs-path', null); } } export class LaunchService implements ILaunchService { _serviceBrand: any; constructor( @ILogService private logService: ILogService, @IWindowsMainService private windowsMainService: IWindowsMainService, @IURLService private urlService: IURLService, @IWorkspacesMainService private workspacesMainService: IWorkspacesMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService ) { } start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise { this.logService.trace('Received data from other instance: ', args, userEnv); const urlsToOpen = parseOpenUrl(args); // Check early for open-url which is handled in URL service if (urlsToOpen.length) { let whenWindowReady = TPromise.as(null); // Create a window if there is none if (this.windowsMainService.getWindowCount() === 0) { const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0]; whenWindowReady = window.ready(); } // Make sure a window is open, ready to receive the url event whenWindowReady.then(() => { for (const url of urlsToOpen) { this.urlService.open(url); } }); return TPromise.as(null); } // Otherwise handle in windows service return this.startOpenWindow(args, userEnv); } private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise { const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; let usedWindows: ICodeWindow[]; // Special case extension development if (!!args.extensionDevelopmentPath) { this.windowsMainService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv }); } // Start without file/folder arguments else if (!hasArgs(args._) && !hasArgs(args['folder-uri']) && !hasArgs(args['file-uri'])) { let openNewWindow = false; // Force new window if (args['new-window'] || args['unity-launch']) { openNewWindow = true; } // Force reuse window else if (args['reuse-window']) { openNewWindow = false; } // Otherwise check for settings else { const windowConfig = this.configurationService.getValue('window'); const openWithoutArgumentsInNewWindowConfig = (windowConfig && windowConfig.openWithoutArgumentsInNewWindow) || 'default' /* default */; switch (openWithoutArgumentsInNewWindowConfig) { case 'on': openNewWindow = true; break; case 'off': openNewWindow = false; break; default: openNewWindow = !isMacintosh; // prefer to restore running instance on macOS } } if (openNewWindow) { usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: true, forceEmpty: true }); } else { usedWindows = [this.windowsMainService.focusLastActive(args, context)]; } } // Start with file/folder arguments else { usedWindows = this.windowsMainService.open({ context, cli: args, userEnv, forceNewWindow: args['new-window'], preferNewWindow: !args['reuse-window'] && !args.wait, forceReuseWindow: args['reuse-window'], diffMode: args.diff, addMode: args.add }); } // If the other instance is waiting to be killed, we hook up a window listener if one window // is being used and only then resolve the startup promise which will kill this second instance. // In addition, we poll for the wait marker file to be deleted to return. if (args.wait && args.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) { return Promise.race([ this.windowsMainService.waitForWindowCloseOrLoad(usedWindows[0].id), whenDeleted(args.waitMarkerFilePath) ]).then(() => void 0, () => void 0); } return TPromise.as(null); } getMainProcessId(): TPromise { this.logService.trace('Received request for process ID from other instance.'); return TPromise.as(process.pid); } getMainProcessInfo(): TPromise { this.logService.trace('Received request for main process info from other instance.'); const windows: IWindowInfo[] = []; BrowserWindow.getAllWindows().forEach(window => { const codeWindow = this.windowsMainService.getWindowById(window.id); if (codeWindow) { windows.push(this.codeWindowToInfo(codeWindow)); } else { windows.push(this.browserWindowToInfo(window)); } }); return TPromise.wrap({ mainPID: process.pid, mainArguments: process.argv.slice(1), windows } as IMainProcessInfo); } getLogsPath(): TPromise { this.logService.trace('Received request for logs path from other instance.'); return TPromise.as(this.environmentService.logsPath); } private codeWindowToInfo(window: ICodeWindow): IWindowInfo { const folderURIs: URI[] = []; if (window.openedFolderUri) { folderURIs.push(window.openedFolderUri); } else if (window.openedWorkspace) { const rootFolders = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath).folders; rootFolders.forEach(root => { folderURIs.push(root.uri); }); } return this.browserWindowToInfo(window.win, folderURIs); } private browserWindowToInfo(win: BrowserWindow, folderURIs: URI[] = []): IWindowInfo { return { pid: win.webContents.getOSProcessId(), title: win.getTitle(), folderURIs } as IWindowInfo; } }