Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/launch/electron-main/launchMainService.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { app } from 'electron';
7
import { coalesce } from '../../../base/common/arrays.js';
8
import { IProcessEnvironment, isMacintosh } from '../../../base/common/platform.js';
9
import { URI } from '../../../base/common/uri.js';
10
import { whenDeleted } from '../../../base/node/pfs.js';
11
import { IConfigurationService } from '../../configuration/common/configuration.js';
12
import { NativeParsedArgs } from '../../environment/common/argv.js';
13
import { isLaunchedFromCli } from '../../environment/node/argvHelper.js';
14
import { createDecorator } from '../../instantiation/common/instantiation.js';
15
import { ILogService } from '../../log/common/log.js';
16
import { IURLService } from '../../url/common/url.js';
17
import { ICodeWindow } from '../../window/electron-main/window.js';
18
import { IWindowSettings } from '../../window/common/window.js';
19
import { IOpenConfiguration, IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js';
20
import { IProtocolUrl } from '../../url/electron-main/url.js';
21
22
export const ID = 'launchMainService';
23
export const ILaunchMainService = createDecorator<ILaunchMainService>(ID);
24
25
export interface IStartArguments {
26
readonly args: NativeParsedArgs;
27
readonly userEnv: IProcessEnvironment;
28
}
29
30
export interface ILaunchMainService {
31
32
readonly _serviceBrand: undefined;
33
34
start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void>;
35
36
getMainProcessId(): Promise<number>;
37
}
38
39
export class LaunchMainService implements ILaunchMainService {
40
41
declare readonly _serviceBrand: undefined;
42
43
constructor(
44
@ILogService private readonly logService: ILogService,
45
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
46
@IURLService private readonly urlService: IURLService,
47
@IConfigurationService private readonly configurationService: IConfigurationService,
48
) { }
49
50
async start(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
51
this.logService.trace('Received data from other instance: ', args, userEnv);
52
53
// macOS: Electron > 7.x changed its behaviour to not
54
// bring the application to the foreground when a window
55
// is focused programmatically. Only via `app.focus` and
56
// the option `steal: true` can you get the previous
57
// behaviour back. The only reason to use this option is
58
// when a window is getting focused while the application
59
// is not in the foreground and since we got instructed
60
// to open a new window from another instance, we ensure
61
// that the app has focus.
62
if (isMacintosh) {
63
app.focus({ steal: true });
64
}
65
66
// Check early for open-url which is handled in URL service
67
const urlsToOpen = this.parseOpenUrl(args);
68
if (urlsToOpen.length) {
69
let whenWindowReady: Promise<unknown> = Promise.resolve();
70
71
// Create a window if there is none
72
if (this.windowsMainService.getWindowCount() === 0) {
73
const window = (await this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP })).at(0);
74
if (window) {
75
whenWindowReady = window.ready();
76
}
77
}
78
79
// Make sure a window is open, ready to receive the url event
80
whenWindowReady.then(() => {
81
for (const { uri, originalUrl } of urlsToOpen) {
82
this.urlService.open(uri, { originalUrl });
83
}
84
});
85
}
86
87
// Otherwise handle in windows service
88
else {
89
return this.startOpenWindow(args, userEnv);
90
}
91
}
92
93
private parseOpenUrl(args: NativeParsedArgs): IProtocolUrl[] {
94
if (args['open-url'] && args._urls && args._urls.length > 0) {
95
96
// --open-url must contain -- followed by the url(s)
97
// process.argv is used over args._ as args._ are resolved to file paths at this point
98
99
return coalesce(args._urls
100
.map(url => {
101
try {
102
return { uri: URI.parse(url), originalUrl: url };
103
} catch (err) {
104
return null;
105
}
106
}));
107
}
108
109
return [];
110
}
111
112
private async startOpenWindow(args: NativeParsedArgs, userEnv: IProcessEnvironment): Promise<void> {
113
const context = isLaunchedFromCli(userEnv) ? OpenContext.CLI : OpenContext.DESKTOP;
114
let usedWindows: ICodeWindow[] = [];
115
116
const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined;
117
const remoteAuthority = args.remote || undefined;
118
119
const baseConfig: IOpenConfiguration = {
120
context,
121
cli: args,
122
/**
123
* When opening a new window from a second instance that sent args and env
124
* over to this instance, we want to preserve the environment only if that second
125
* instance was spawned from the CLI or used the `--preserve-env` flag (example:
126
* when using `open -n "VSCode.app" --args --preserve-env WORKSPACE_FOLDER`).
127
*
128
* This is done to ensure that the second window gets treated exactly the same
129
* as the first window, for example, it gets the same resolved user shell environment.
130
*
131
* https://github.com/microsoft/vscode/issues/194736
132
*/
133
userEnv: (args['preserve-env'] || context === OpenContext.CLI) ? userEnv : undefined,
134
waitMarkerFileURI,
135
remoteAuthority,
136
forceProfile: args.profile,
137
forceTempProfile: args['profile-temp']
138
};
139
140
// Special case extension development
141
if (!!args.extensionDevelopmentPath) {
142
await this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, baseConfig);
143
}
144
145
// Start without file/folder arguments
146
else if (!args._.length && !args['folder-uri'] && !args['file-uri']) {
147
let openNewWindow = false;
148
149
// Force new window
150
if (args['new-window'] || baseConfig.forceProfile || baseConfig.forceTempProfile) {
151
openNewWindow = true;
152
}
153
154
// Force reuse window
155
else if (args['reuse-window']) {
156
openNewWindow = false;
157
}
158
159
// Otherwise check for settings
160
else {
161
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
162
const openWithoutArgumentsInNewWindowConfig = windowConfig?.openWithoutArgumentsInNewWindow || 'default' /* default */;
163
switch (openWithoutArgumentsInNewWindowConfig) {
164
case 'on':
165
openNewWindow = true;
166
break;
167
case 'off':
168
openNewWindow = false;
169
break;
170
default:
171
openNewWindow = !isMacintosh; // prefer to restore running instance on macOS
172
}
173
}
174
175
// Open new Window
176
if (openNewWindow) {
177
usedWindows = await this.windowsMainService.open({
178
...baseConfig,
179
forceNewWindow: true,
180
forceEmpty: true
181
});
182
}
183
184
// Focus existing window or open if none opened
185
else {
186
const lastActive = this.windowsMainService.getLastActiveWindow();
187
if (lastActive) {
188
this.windowsMainService.openExistingWindow(lastActive, baseConfig);
189
190
usedWindows = [lastActive];
191
} else {
192
usedWindows = await this.windowsMainService.open({
193
...baseConfig,
194
forceEmpty: true
195
});
196
}
197
}
198
}
199
200
// Start with file/folder arguments
201
else {
202
usedWindows = await this.windowsMainService.open({
203
...baseConfig,
204
forceNewWindow: args['new-window'],
205
preferNewWindow: !args['reuse-window'] && !args.wait,
206
forceReuseWindow: args['reuse-window'],
207
diffMode: args.diff,
208
mergeMode: args.merge,
209
addMode: args.add,
210
removeMode: args.remove,
211
noRecentEntry: !!args['skip-add-to-recently-opened'],
212
gotoLineMode: args.goto
213
});
214
}
215
216
// If the other instance is waiting to be killed, we hook up a window listener if one window
217
// is being used and only then resolve the startup promise which will kill this second instance.
218
// In addition, we poll for the wait marker file to be deleted to return.
219
if (waitMarkerFileURI && usedWindows.length === 1 && usedWindows[0]) {
220
return Promise.race([
221
usedWindows[0].whenClosedOrLoaded,
222
whenDeleted(waitMarkerFileURI.fsPath)
223
]).then(() => undefined, () => undefined);
224
}
225
}
226
227
async getMainProcessId(): Promise<number> {
228
this.logService.trace('Received request for process ID from other instance.');
229
230
return process.pid;
231
}
232
}
233
234