Path: blob/main/src/vs/platform/launch/electron-main/launchMainService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { app } from 'electron';6import { coalesce } from '../../../base/common/arrays.js';7import { IProcessEnvironment, isMacintosh } from '../../../base/common/platform.js';8import { URI } from '../../../base/common/uri.js';9import { whenDeleted } from '../../../base/node/pfs.js';10import { IConfigurationService } from '../../configuration/common/configuration.js';11import { NativeParsedArgs } from '../../environment/common/argv.js';12import { isLaunchedFromCli } from '../../environment/node/argvHelper.js';13import { createDecorator } from '../../instantiation/common/instantiation.js';14import { ILogService } from '../../log/common/log.js';15import { IURLService } from '../../url/common/url.js';16import { ICodeWindow } from '../../window/electron-main/window.js';17import { IWindowSettings } from '../../window/common/window.js';18import { IOpenConfiguration, IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js';19import { IProtocolUrl } from '../../url/electron-main/url.js';2021export const ID = 'launchMainService';22export const ILaunchMainService = createDecorator<ILaunchMainService>(ID);2324export interface IStartArguments {25readonly args: NativeParsedArgs;26readonly userEnv: IProcessEnvironment;27}2829export interface ILaunchMainService {3031readonly _serviceBrand: undefined;3233start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void>;3435getMainProcessId(): Promise<number>;36}3738export class LaunchMainService implements ILaunchMainService {3940declare readonly _serviceBrand: undefined;4142constructor(43@ILogService private readonly logService: ILogService,44@IWindowsMainService private readonly windowsMainService: IWindowsMainService,45@IURLService private readonly urlService: IURLService,46@IConfigurationService private readonly configurationService: IConfigurationService,47) { }4849async start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void> {50this.logService.trace('Received data from other instance: ', args, userEnv);5152// macOS: Electron > 7.x changed its behaviour to not53// bring the application to the foreground when a window54// is focused programmatically. Only via `app.focus` and55// the option `steal: true` can you get the previous56// behaviour back. The only reason to use this option is57// when a window is getting focused while the application58// is not in the foreground and since we got instructed59// to open a new window from another instance, we ensure60// that the app has focus.61if (isMacintosh) {62app.focus({ steal: true });63}6465// Check early for open-url which is handled in URL service66const urlsToOpen = this.parseOpenUrl(args);67if (urlsToOpen.length) {68let whenWindowReady: Promise<unknown> = Promise.resolve();6970// Create a window if there is none71if (this.windowsMainService.getWindowCount() === 0) {72const window = (await this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP })).at(0);73if (window) {74whenWindowReady = window.ready();75}76}7778// Make sure a window is open, ready to receive the url event79whenWindowReady.then(() => {80for (const { uri, originalUrl } of urlsToOpen) {81this.urlService.open(uri, { originalUrl });82}83});84}8586// Otherwise handle in windows service87else {88return this.startOpenWindow(args, userEnv);89}90}9192private parseOpenUrl(args: NativeParsedArgs): IProtocolUrl[] {93if (args['open-url'] && args._urls && args._urls.length > 0) {9495// --open-url must contain -- followed by the url(s)96// process.argv is used over args._ as args._ are resolved to file paths at this point9798return coalesce(args._urls99.map(url => {100try {101return { uri: URI.parse(url), originalUrl: url };102} catch (err) {103return null;104}105}));106}107108return [];109}110111private async startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void> {112const context = isLaunchedFromCli(userEnv) ? OpenContext.CLI : OpenContext.DESKTOP;113let usedWindows: ICodeWindow[] = [];114115const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;116const remoteAuthority = args.remote || undefined;117118const baseConfig: IOpenConfiguration = {119context,120cli: args,121/**122* When opening a new window from a second instance that sent args and env123* over to this instance, we want to preserve the environment only if that second124* instance was spawned from the CLI or used the `--preserve-env` flag (example:125* when using `open -n "VSCode.app" --args --preserve-env WORKSPACE_FOLDER`).126*127* This is done to ensure that the second window gets treated exactly the same128* as the first window, for example, it gets the same resolved user shell environment.129*130* https://github.com/microsoft/vscode/issues/194736131*/132userEnv: (args['preserve-env'] || context === OpenContext.CLI) ? userEnv : undefined,133waitMarkerFileURI,134remoteAuthority,135forceProfile: args.profile,136forceTempProfile: args['profile-temp']137};138139// Special case extension development140if (!!args.extensionDevelopmentPath) {141await this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, baseConfig);142}143144// Start without file/folder arguments145else if (!args._.length && !args['folder-uri'] && !args['file-uri']) {146let openNewWindow = false;147148// Force new window149if (args['new-window'] || baseConfig.forceProfile || baseConfig.forceTempProfile) {150openNewWindow = true;151}152153// Force reuse window154else if (args['reuse-window']) {155openNewWindow = false;156}157158// Otherwise check for settings159else {160const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');161const openWithoutArgumentsInNewWindowConfig = windowConfig?.openWithoutArgumentsInNewWindow || 'default' /* default */;162switch (openWithoutArgumentsInNewWindowConfig) {163case 'on':164openNewWindow = true;165break;166case 'off':167openNewWindow = false;168break;169default:170openNewWindow = !isMacintosh; // prefer to restore running instance on macOS171}172}173174// Open new Window175if (openNewWindow) {176usedWindows = await this.windowsMainService.open({177...baseConfig,178forceNewWindow: true,179forceEmpty: true180});181}182183// Focus existing window or open if none opened184else {185const lastActive = this.windowsMainService.getLastActiveWindow();186if (lastActive) {187this.windowsMainService.openExistingWindow(lastActive, baseConfig);188189usedWindows = [lastActive];190} else {191usedWindows = await this.windowsMainService.open({192...baseConfig,193forceEmpty: true194});195}196}197}198199// Start with file/folder arguments200else {201usedWindows = await this.windowsMainService.open({202...baseConfig,203forceNewWindow: args['new-window'],204preferNewWindow: !args['reuse-window'] && !args.wait,205forceReuseWindow: args['reuse-window'],206diffMode: args.diff,207mergeMode: args.merge,208addMode: args.add,209removeMode: args.remove,210noRecentEntry: !!args['skip-add-to-recently-opened'],211gotoLineMode: args.goto212});213}214215// If the other instance is waiting to be killed, we hook up a window listener if one window216// is being used and only then resolve the startup promise which will kill this second instance.217// In addition, we poll for the wait marker file to be deleted to return.218if (waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) {219return Promise.race([220usedWindows[0].whenClosedOrLoaded,221whenDeleted(waitMarkerFileURI.fsPath)222]).then(() => undefined, () => undefined);223}224}225226async getMainProcessId(): Promise<number> {227this.logService.trace('Received request for process ID from other instance.');228229return process.pid;230}231}232233234