window.ts 34.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as path from 'vs/base/common/path';
J
Joao Moreno 已提交
7
import * as objects from 'vs/base/common/objects';
8
import * as nls from 'vs/nls';
9
import { URI } from 'vs/base/common/uri';
B
Benjamin Pasero 已提交
10
import { IStateService } from 'vs/platform/state/common/state';
B
Benjamin Pasero 已提交
11
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display } from 'electron';
J
Joao Moreno 已提交
12
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
13
import { ILogService } from 'vs/platform/log/common/log';
14
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
15
import { parseArgs } from 'vs/platform/environment/node/argv';
16
import product from 'vs/platform/product/node/product';
B
Benjamin Pasero 已提交
17
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows';
B
Benjamin Pasero 已提交
18
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
19
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
20
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
21
import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
22
import { IBackupMainService } from 'vs/platform/backup/common/backup';
B
Benjamin Pasero 已提交
23
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
24
import * as perf from 'vs/base/common/performance';
25
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
26
import { getBackgroundColor } from 'vs/code/electron-main/theme';
B
Benjamin Pasero 已提交
27
import { RunOnceScheduler } from 'vs/base/common/async';
28
import { endsWith } from 'vs/base/common/strings';
29

30 31
export interface IWindowCreationOptions {
	state: IWindowState;
32
	extensionDevelopmentPath?: string | string[];
33
	isExtensionTestHost?: boolean;
34 35
}

B
Benjamin Pasero 已提交
36
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
E
Erich Gamma 已提交
37 38 39
	return {
		width: 1024,
		height: 768,
40
		mode
E
Erich Gamma 已提交
41 42 43
	};
};

B
Benjamin Pasero 已提交
44 45 46 47 48 49 50 51
interface IWorkbenchEditorConfiguration {
	workbench: {
		editor: {
			swipeToNavigate: boolean
		}
	};
}

B
Benjamin Pasero 已提交
52 53 54 55
interface ITouchBarSegment extends Electron.SegmentedControlSegment {
	id: string;
}

B
Benjamin Pasero 已提交
56
export class CodeWindow extends Disposable implements ICodeWindow {
E
Erich Gamma 已提交
57

58 59
	private static readonly MIN_WIDTH = 200;
	private static readonly MIN_HEIGHT = 120;
E
Erich Gamma 已提交
60

B
Benjamin Pasero 已提交
61
	private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
62

B
Benjamin Pasero 已提交
63
	private hiddenTitleBarStyle: boolean;
64
	private showTimeoutHandle: NodeJS.Timeout;
B
Benjamin Pasero 已提交
65
	private _id: number;
66
	private _win: Electron.BrowserWindow;
E
Erich Gamma 已提交
67 68 69
	private _lastFocusTime: number;
	private _readyState: ReadyState;
	private windowState: IWindowState;
70
	private currentMenuBarVisibility: MenuBarVisibility;
71
	private representedFilename: string;
E
Erich Gamma 已提交
72

73
	private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[];
E
Erich Gamma 已提交
74 75

	private currentConfig: IWindowConfiguration;
76
	private pendingLoadConfig?: IWindowConfiguration;
E
Erich Gamma 已提交
77

J
Johannes Rieken 已提交
78
	private marketplaceHeadersPromise: Promise<object>;
79

80
	private readonly touchBarGroups: Electron.TouchBarSegmentedControl[];
81

J
Joao Moreno 已提交
82 83
	constructor(
		config: IWindowCreationOptions,
84 85 86 87 88 89
		@ILogService private readonly logService: ILogService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IStateService private readonly stateService: IStateService,
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
		@IBackupMainService private readonly backupMainService: IBackupMainService,
J
Joao Moreno 已提交
90
	) {
B
Benjamin Pasero 已提交
91 92
		super();

B
Benjamin Pasero 已提交
93
		this.touchBarGroups = [];
E
Erich Gamma 已提交
94 95 96 97
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
		this.whenReadyCallbacks = [];

B
Benjamin Pasero 已提交
98 99 100 101 102 103
		// create browser window
		this.createBrowserWindow(config);

		// respect configured menu bar visibility
		this.onConfigurationUpdated();

B
Benjamin Pasero 已提交
104 105 106
		// macOS: touch bar support
		this.createTouchBar();

107 108 109
		// Request handling
		this.handleMarketplaceRequests();

B
Benjamin Pasero 已提交
110 111 112 113 114 115
		// Eventing
		this.registerListeners();
	}

	private createBrowserWindow(config: IWindowCreationOptions): void {

E
Erich Gamma 已提交
116
		// Load window state
B
Benjamin Pasero 已提交
117
		this.windowState = this.restoreWindowState(config.state);
E
Erich Gamma 已提交
118

119
		// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
B
Benjamin Pasero 已提交
120
		const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);
121

122
		const options: Electron.BrowserWindowConstructorOptions = {
E
Erich Gamma 已提交
123 124 125 126
			width: this.windowState.width,
			height: this.windowState.height,
			x: this.windowState.x,
			y: this.windowState.y,
127
			backgroundColor: getBackgroundColor(this.stateService),
B
Benjamin Pasero 已提交
128 129
			minWidth: CodeWindow.MIN_WIDTH,
			minHeight: CodeWindow.MIN_HEIGHT,
130
			show: !isFullscreenOrMaximized,
B
Benjamin Pasero 已提交
131
			title: product.nameLong,
132
			webPreferences: {
B
Benjamin Pasero 已提交
133 134 135 136
				// By default if Code is in the background, intervals and timeouts get throttled, so we
				// want to enforce that Code stays in the foreground. This triggers a disable_hidden_
				// flag that Electron provides via patch:
				// https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch
137
				backgroundThrottling: false
138
			}
E
Erich Gamma 已提交
139 140
		};

141
		if (isLinux) {
142
			options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s)
E
Erich Gamma 已提交
143 144
		}

145
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
146

147 148 149 150
		if (isMacintosh && !this.useNativeFullScreen()) {
			options.fullscreenable = false; // enables simple fullscreen mode
		}

151 152 153 154 155 156 157 158 159
		if (isMacintosh) {
			options.acceptFirstMouse = true; // enabled by default

			if (windowConfig && windowConfig.clickThroughInactive === false) {
				options.acceptFirstMouse = false;
			}
		}

		if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
160 161 162
			options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
		}

B
Benjamin Pasero 已提交
163
		const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom';
164 165 166
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
R
Ryan Adolf 已提交
167
			if (!isMacintosh) {
R
Ryan Adolf 已提交
168 169
				options.frame = false;
			}
170 171
		}

E
Erich Gamma 已提交
172 173
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
174
		this._id = this._win.id;
E
Erich Gamma 已提交
175

B
Benjamin Pasero 已提交
176
		if (isMacintosh && useCustomTitleStyle) {
177 178 179
			this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
		}

180
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
181
			this._win.maximize();
E
Erich Gamma 已提交
182

B
Benjamin Pasero 已提交
183
			if (this.windowState.mode === WindowMode.Fullscreen) {
184
				this.setFullScreen(true);
185 186
			}

B
Benjamin Pasero 已提交
187 188
			if (!this._win.isVisible()) {
				this._win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize
E
Erich Gamma 已提交
189 190 191
			}
		}

192
		this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too
E
Erich Gamma 已提交
193 194
	}

B
Benjamin Pasero 已提交
195
	hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
196
		return this.hiddenTitleBarStyle;
197 198
	}

B
Benjamin Pasero 已提交
199
	get isExtensionDevelopmentHost(): boolean {
200
		return !!this.config.extensionDevelopmentPath;
201 202
	}

B
Benjamin Pasero 已提交
203
	get isExtensionTestHost(): boolean {
204
		return !!this.config.extensionTestsPath;
205 206
	}

207 208
	/*
	get extensionDevelopmentPaths(): string | string[] | undefined {
209
		return this.config.extensionDevelopmentPath;
E
Erich Gamma 已提交
210
	}
211
	*/
E
Erich Gamma 已提交
212

B
Benjamin Pasero 已提交
213
	get config(): IWindowConfiguration {
E
Erich Gamma 已提交
214 215 216
		return this.currentConfig;
	}

B
Benjamin Pasero 已提交
217
	get id(): number {
B
Benjamin Pasero 已提交
218 219 220
		return this._id;
	}

B
Benjamin Pasero 已提交
221
	get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
222 223 224
		return this._win;
	}

B
Benjamin Pasero 已提交
225
	setRepresentedFilename(filename: string): void {
226
		if (isMacintosh) {
227 228 229 230 231 232
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

B
Benjamin Pasero 已提交
233
	getRepresentedFilename(): string {
234
		if (isMacintosh) {
235 236 237 238 239 240
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
241
	focus(): void {
E
Erich Gamma 已提交
242 243 244 245
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
246 247
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
248 249
		}

B
Benjamin Pasero 已提交
250
		this._win.focus();
E
Erich Gamma 已提交
251 252
	}

B
Benjamin Pasero 已提交
253
	get lastFocusTime(): number {
E
Erich Gamma 已提交
254 255 256
		return this._lastFocusTime;
	}

257
	get backupPath(): string | undefined {
R
Rob Lourens 已提交
258
		return this.currentConfig ? this.currentConfig.backupPath : undefined;
E
Erich Gamma 已提交
259 260
	}

261
	get openedWorkspace(): IWorkspaceIdentifier | undefined {
R
Rob Lourens 已提交
262
		return this.currentConfig ? this.currentConfig.workspace : undefined;
263 264
	}

265
	get openedFolderUri(): URI | undefined {
R
Rob Lourens 已提交
266
		return this.currentConfig ? this.currentConfig.folderUri : undefined;
267 268
	}

269
	get remoteAuthority(): string | undefined {
R
Rob Lourens 已提交
270
		return this.currentConfig ? this.currentConfig.remoteAuthority : undefined;
M
Martin Aeschlimann 已提交
271 272
	}

B
Benjamin Pasero 已提交
273
	setReady(): void {
E
Erich Gamma 已提交
274 275 276 277
		this._readyState = ReadyState.READY;

		// inform all waiting promises that we are ready now
		while (this.whenReadyCallbacks.length) {
278
			this.whenReadyCallbacks.pop()!(this);
E
Erich Gamma 已提交
279 280 281
		}
	}

J
Johannes Rieken 已提交
282
	ready(): Promise<ICodeWindow> {
B
Benjamin Pasero 已提交
283 284 285
		return new Promise<ICodeWindow>(resolve => {
			if (this.isReady) {
				return resolve(this);
E
Erich Gamma 已提交
286 287 288
			}

			// otherwise keep and call later when we are ready
B
Benjamin Pasero 已提交
289
			this.whenReadyCallbacks.push(resolve);
E
Erich Gamma 已提交
290 291 292
		});
	}

B
Benjamin Pasero 已提交
293 294
	get isReady(): boolean {
		return this._readyState === ReadyState.READY;
E
Erich Gamma 已提交
295 296
	}

297 298 299 300
	private handleMarketplaceRequests(): void {

		// Resolve marketplace headers
		this.marketplaceHeadersPromise = resolveMarketplaceHeaders(this.environmentService);
E
Erich Gamma 已提交
301

302 303
		// Inject headers when requests are incoming
		const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
304
		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
305
			this.marketplaceHeadersPromise.then(headers => {
306
				const requestHeaders = objects.assign(details.requestHeaders, headers);
307 308 309
				if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) {
					requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`;
				}
310
				cb({ cancel: false, requestHeaders });
311
			});
312
		});
313 314 315
	}

	private registerListeners(): void {
316 317

		// Prevent loading of svgs
318
		this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
319 320
			if (details.url.indexOf('.svg') > 0) {
				const uri = URI.parse(details.url);
321
				if (uri && !uri.scheme.match(/file/i) && endsWith(uri.path, '.svg')) {
322 323 324 325 326 327 328
					return callback({ cancel: true });
				}
			}

			return callback({});
		});

329 330
		this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
			const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']);
331 332 333 334 335 336 337
			if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
				return callback({ cancel: true });
			}

			return callback({ cancel: false, responseHeaders: details.responseHeaders });
		});

E
Erich Gamma 已提交
338 339 340 341 342 343 344 345
		// Remember that we loaded
		this._win.webContents.on('did-finish-load', () => {
			this._readyState = ReadyState.LOADING;

			// Associate properties from the load request if provided
			if (this.pendingLoadConfig) {
				this.currentConfig = this.pendingLoadConfig;

346
				this.pendingLoadConfig = undefined;
E
Erich Gamma 已提交
347 348
			}

B
Benjamin Pasero 已提交
349
			// To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded
B
Benjamin Pasero 已提交
350
			if (this._win && !this._win.isVisible()) {
B
Benjamin Pasero 已提交
351
				if (this.windowState.mode === WindowMode.Maximized) {
B
Benjamin Pasero 已提交
352
					this._win.maximize();
E
Erich Gamma 已提交
353 354
				}

B
Benjamin Pasero 已提交
355 356
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
357 358 359 360 361
				}
			}
		});

		// App commands support
362
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
363 364 365

		// Window Focus
		this._win.on('focus', () => {
B
Benjamin Pasero 已提交
366
			this._lastFocusTime = Date.now();
E
Erich Gamma 已提交
367 368
		});

B
Benjamin Pasero 已提交
369 370 371
		// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
		// we need to detect when display metrics change or displays are added/removed and toggle the
		// fullscreen manually.
B
Benjamin Pasero 已提交
372
		if (isMacintosh) {
B
Benjamin Pasero 已提交
373 374 375 376 377 378
			const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
				if (!this._win) {
					return; // disposed
				}

				if (!this.useNativeFullScreen() && this.isFullScreen()) {
B
Benjamin Pasero 已提交
379 380 381
					this.setFullScreen(false);
					this.setFullScreen(true);
				}
B
Benjamin Pasero 已提交
382 383 384 385 386 387 388 389 390
			}, 100));

			const displayChangedListener = () => simpleFullScreenScheduler.schedule();

			screen.on('display-metrics-changed', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));

			screen.on('display-added', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-added', displayChangedListener)));
B
Benjamin Pasero 已提交
391

B
Benjamin Pasero 已提交
392 393
			screen.on('display-removed', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener)));
B
Benjamin Pasero 已提交
394
		}
395

S
SteVen Batten 已提交
396
		// Window (Un)Maximize
397
		this._win.on('maximize', (e: Event) => {
B
Benjamin Pasero 已提交
398 399 400 401
			if (this.currentConfig) {
				this.currentConfig.maximized = true;
			}

402 403 404
			app.emit('browser-window-maximize', e, this._win);
		});

405
		this._win.on('unmaximize', (e: Event) => {
B
Benjamin Pasero 已提交
406 407 408 409
			if (this.currentConfig) {
				this.currentConfig.maximized = false;
			}

410 411
			app.emit('browser-window-unmaximize', e, this._win);
		});
S
SteVen Batten 已提交
412

413 414 415 416 417 418 419 420 421
		// Window Fullscreen
		this._win.on('enter-full-screen', () => {
			this.sendWhenReady('vscode:enterFullScreen');
		});

		this._win.on('leave-full-screen', () => {
			this.sendWhenReady('vscode:leaveFullScreen');
		});

E
Erich Gamma 已提交
422
		// Window Failed to load
423
		this._win.webContents.on('did-fail-load', (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
B
Benjamin Pasero 已提交
424
			this.logService.warn('[electron event]: fail to load, ', errorDescription);
E
Erich Gamma 已提交
425 426
		});

B
Benjamin Pasero 已提交
427
		// Handle configuration changes
B
Benjamin Pasero 已提交
428
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
429

430
		// Handle Workspace events
B
Benjamin Pasero 已提交
431
		this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
432 433
	}

434
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
435 436 437

		// Make sure to update our workspace config if we detect that it
		// was deleted
438
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
R
Rob Lourens 已提交
439
			this.currentConfig.workspace = undefined;
440
		}
E
Erich Gamma 已提交
441 442
	}

B
Benjamin Pasero 已提交
443
	private onConfigurationUpdated(): void {
444
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
445 446 447 448
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
449 450

		// Swipe command support (macOS)
451
		if (isMacintosh) {
452
			const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
B
Benjamin Pasero 已提交
453
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
454
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
455 456 457
			} else {
				this._win.removeAllListeners('swipe');
			}
458
		}
459
	}
B
Benjamin Pasero 已提交
460

B
Benjamin Pasero 已提交
461
	private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
462
		this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
B
Benjamin Pasero 已提交
463
			if (!this.isReady) {
B
Benjamin Pasero 已提交
464 465 466 467
				return; // window must be ready
			}

			if (cmd === back) {
468
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
469
			} else if (cmd === forward) {
470
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
471 472 473 474
			}
		});
	}

475 476 477 478 479 480
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

B
Benjamin Pasero 已提交
481
	load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
E
Erich Gamma 已提交
482 483 484

		// If this is the first time the window is loaded, we associate the paths
		// directly with the window because we assume the loading will just work
B
Benjamin Pasero 已提交
485
		if (this._readyState === ReadyState.NONE) {
E
Erich Gamma 已提交
486 487 488 489 490 491 492 493 494 495 496 497
			this.currentConfig = config;
		}

		// Otherwise, the window is currently showing a folder and if there is an
		// unload handler preventing the load, we cannot just associate the paths
		// because the loading might be vetoed. Instead we associate it later when
		// the window load event has fired.
		else {
			this.pendingLoadConfig = config;
			this._readyState = ReadyState.NAVIGATING;
		}

498 499 500 501 502 503 504
		// Add disable-extensions to the config, but do not preserve it on currentConfig or
		// pendingLoadConfig so that it is applied only on this load
		const configuration = objects.assign({}, config);
		if (disableExtensions !== undefined) {
			configuration['disable-extensions'] = disableExtensions;
		}

505
		// Clear Document Edited if needed
506
		if (isMacintosh && this._win.isDocumentEdited()) {
B
Benjamin Pasero 已提交
507
			if (!isReload || !this.backupMainService.isHotExitEnabled()) {
508 509 510 511 512 513 514 515 516 517 518
				this._win.setDocumentEdited(false);
			}
		}

		// Clear Title and Filename if needed
		if (!isReload) {
			if (this.getRepresentedFilename()) {
				this.setRepresentedFilename('');
			}

			this._win.setTitle(product.nameLong);
519 520
		}

E
Erich Gamma 已提交
521
		// Load URL
522
		perf.mark('main:loadWindow');
523
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
524 525

		// Make window visible if it did not open in N seconds because this indicates an error
526
		// Only do this when running out of sources and not when running tests
527
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsLocationURI) {
E
Erich Gamma 已提交
528 529 530 531
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
532
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
533 534 535 536 537
				}
			}, 10000);
		}
	}

538
	reload(configurationIn?: IWindowConfiguration, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
539

540
		// If config is not provided, copy our current one
541
		const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig);
542 543

		// Delete some properties we do not want during reload
544
		delete configuration.filesToOpenOrCreate;
545
		delete configuration.filesToDiff;
546
		delete configuration.filesToWait;
547

E
Erich Gamma 已提交
548
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
549
		// in extension development mode. These options are all development related.
550
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
551
			configuration.verbose = cli.verbose;
552 553
			configuration['inspect-extensions'] = cli['inspect-extensions'];
			configuration['inspect-brk-extensions'] = cli['inspect-brk-extensions'];
554
			configuration.debugId = cli.debugId;
555
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
556 557
		}

558 559
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
560
		// Load config
561 562
		const disableExtensions = cli ? cli['disable-extensions'] : undefined;
		this.load(configuration, true, disableExtensions);
E
Erich Gamma 已提交
563 564
	}

565
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
566

567 568
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
569
		windowConfiguration.logLevel = this.logService.getLevel();
570

571
		// Set zoomlevel
572
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
573
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
574 575 576 577
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

578
		// Set fullscreen state
579
		windowConfiguration.fullscreen = this.isFullScreen();
580

581
		// Set Accessibility Config
582 583 584 585 586
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
587 588
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

B
Benjamin Pasero 已提交
589 590
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
591
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
592

593 594
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
595

596
		// Parts splash
597
		windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json');
598

599 600 601
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
602
		for (let key in config) {
R
Rob Lourens 已提交
603
			if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) {
604 605 606
				delete config[key]; // only send over properties that have a true value
			}
		}
607

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
		// In the unlikely event of the URL becoming larger than 2MB, remove parts of
		// it that are not under our control. Mainly, the user environment can be very
		// large depending on user configuration, so we can only remove it in that case.
		let configUrl = this.doGetUrl(config);
		if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
			delete config.userEnv;
			this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.');

			configUrl = this.doGetUrl(config);

			if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
				this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.');
			}
		}

		return configUrl;
	}

	private doGetUrl(config: object): string {
627
		return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
E
Erich Gamma 已提交
628 629
	}

B
Benjamin Pasero 已提交
630
	serializeWindowState(): IWindowState {
631 632 633
		if (!this._win) {
			return defaultWindowState();
		}
634 635

		// fullscreen gets special treatment
636
		if (this.isFullScreen()) {
637 638
			const display = screen.getDisplayMatching(this.getBounds());

B
Benjamin Pasero 已提交
639 640 641
			const defaultState = defaultWindowState();

			const res = {
642
				mode: WindowMode.Fullscreen,
R
Rob Lourens 已提交
643
				display: display ? display.id : undefined,
644

B
Benjamin Pasero 已提交
645 646 647 648 649 650 651 652 653
				// Still carry over window dimensions from previous sessions
				// if we can compute it in fullscreen state.
				// does not seem possible in all cases on Linux for example
				// (https://github.com/Microsoft/vscode/issues/58218) so we
				// fallback to the defaults in that case.
				width: this.windowState.width || defaultState.width,
				height: this.windowState.height || defaultState.height,
				x: this.windowState.x || 0,
				y: this.windowState.y || 0
654
			};
B
Benjamin Pasero 已提交
655 656

			return res;
E
Erich Gamma 已提交
657 658
		}

659
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
660 661 662
		let mode: WindowMode;

		// get window mode
663
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
664 665 666 667 668 669 670 671
			mode = WindowMode.Maximized;
		} else {
			mode = WindowMode.Normal;
		}

		// we don't want to save minimized state, only maximized or normal
		if (mode === WindowMode.Maximized) {
			state.mode = WindowMode.Maximized;
672
		} else {
E
Erich Gamma 已提交
673 674 675 676 677
			state.mode = WindowMode.Normal;
		}

		// only consider non-minimized window states
		if (mode === WindowMode.Normal || mode === WindowMode.Maximized) {
678
			const bounds = this.getBounds();
E
Erich Gamma 已提交
679

680 681 682 683
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
684 685 686 687 688
		}

		return state;
	}

B
Benjamin Pasero 已提交
689
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
690 691
		if (state) {
			try {
692
				state = this.validateWindowState(state);
E
Erich Gamma 已提交
693
			} catch (err) {
J
Joao Moreno 已提交
694
				this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
E
Erich Gamma 已提交
695 696
			}
		}
M
Matt Bierner 已提交
697
		return state || defaultWindowState();
E
Erich Gamma 已提交
698 699
	}

700
	private validateWindowState(state: IWindowState): IWindowState | undefined {
701 702 703 704 705
		if (typeof state.x !== 'number'
			|| typeof state.y !== 'number'
			|| typeof state.width !== 'number'
			|| typeof state.height !== 'number'
		) {
706
			return undefined;
E
Erich Gamma 已提交
707 708 709
		}

		if (state.width <= 0 || state.height <= 0) {
710
			return undefined;
E
Erich Gamma 已提交
711 712
		}

713
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
714 715 716

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
B
Benjamin Pasero 已提交
717 718 719 720
			const displayWorkingArea = this.getWorkingArea(displays[0]);
			if (state.mode !== WindowMode.Maximized && displayWorkingArea) {
				if (state.x < displayWorkingArea.x) {
					state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the left
E
Erich Gamma 已提交
721 722
				}

B
Benjamin Pasero 已提交
723 724
				if (state.y < displayWorkingArea.y) {
					state.y = displayWorkingArea.y; // prevent window from falling out of the screen to the top
E
Erich Gamma 已提交
725 726
				}

B
Benjamin Pasero 已提交
727 728
				if (state.x > (displayWorkingArea.x + displayWorkingArea.width)) {
					state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the right
E
Erich Gamma 已提交
729 730
				}

B
Benjamin Pasero 已提交
731 732
				if (state.y > (displayWorkingArea.y + displayWorkingArea.height)) {
					state.y = displayWorkingArea.y; // prevent window from falling out of the screen to the bottom
E
Erich Gamma 已提交
733 734
				}

B
Benjamin Pasero 已提交
735 736
				if (state.width > displayWorkingArea.width) {
					state.width = displayWorkingArea.width; // prevent window from exceeding display bounds width
E
Erich Gamma 已提交
737 738
				}

B
Benjamin Pasero 已提交
739 740
				if (state.height > displayWorkingArea.height) {
					state.height = displayWorkingArea.height; // prevent window from exceeding display bounds height
E
Erich Gamma 已提交
741 742 743 744 745 746 747 748 749 750
				}
			}

			if (state.mode === WindowMode.Maximized) {
				return defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
			}

			return state;
		}

751 752 753 754 755 756 757 758 759 760 761 762 763
		// Multi Montior (fullscreen): try to find the previously used display
		if (state.display && state.mode === WindowMode.Fullscreen) {
			const display = displays.filter(d => d.id === state.display)[0];
			if (display && display.bounds && typeof display.bounds.x === 'number' && typeof display.bounds.y === 'number') {
				const defaults = defaultWindowState(WindowMode.Fullscreen); // make sure we have good values when the user restores the window
				defaults.x = display.bounds.x; // carefull to use displays x/y position so that the window ends up on the correct monitor
				defaults.y = display.bounds.y;

				return defaults;
			}
		}

		// Multi Monitor (non-fullscreen): be less strict because metrics can be crazy
764 765
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
B
Benjamin Pasero 已提交
766
		const displayWorkingArea = this.getWorkingArea(display);
U
unknown 已提交
767
		if (
B
Benjamin Pasero 已提交
768 769 770 771 772 773
			display &&														// we have a display matching the desired bounds
			displayWorkingArea &&											// we have valid working area bounds
			bounds.x < displayWorkingArea.x + displayWorkingArea.width &&	// prevent window from falling out of the screen to the right
			bounds.y < displayWorkingArea.y + displayWorkingArea.height &&	// prevent window from falling out of the screen to the bottom
			bounds.x + bounds.width > displayWorkingArea.x &&				// prevent window from falling out of the screen to the left
			bounds.y + bounds.height > displayWorkingArea.y					// prevent window from falling out of the scree nto the top
U
unknown 已提交
774
		) {
E
Erich Gamma 已提交
775
			if (state.mode === WindowMode.Maximized) {
776
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
777 778 779 780 781 782 783 784 785
				defaults.x = state.x; // carefull to keep x/y position so that the window ends up on the correct monitor
				defaults.y = state.y;

				return defaults;
			}

			return state;
		}

786
		return undefined;
E
Erich Gamma 已提交
787
	}
B
Benjamin Pasero 已提交
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805

	private getWorkingArea(display: Display): Rectangle | undefined {

		// Prefer the working area of the display to account for taskbars on the
		// desktop being positioned somewhere (https://github.com/Microsoft/vscode/issues/50830).
		//
		// Linux X11 sessions sometimes report wrong display bounds, so we validate
		// the reported sizes are positive.
		if (display.workArea.width > 0 && display.workArea.height > 0) {
			return display.workArea;
		}

		if (display.bounds.width > 0 && display.bounds.height > 0) {
			return display.bounds;
		}

		return undefined;
	}
E
Erich Gamma 已提交
806

B
Benjamin Pasero 已提交
807
	getBounds(): Electron.Rectangle {
B
Benjamin Pasero 已提交
808 809
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
810 811 812 813

		return { x: pos[0], y: pos[1], width: dimension[0], height: dimension[1] };
	}

B
Benjamin Pasero 已提交
814
	toggleFullScreen(): void {
815 816
		this.setFullScreen(!this.isFullScreen());
	}
E
Erich Gamma 已提交
817

818
	private setFullScreen(fullscreen: boolean): void {
819

820 821 822 823 824 825 826 827 828 829 830
		// Set fullscreen state
		if (this.useNativeFullScreen()) {
			this.setNativeFullScreen(fullscreen);
		} else {
			this.setSimpleFullScreen(fullscreen);
		}

		// Events
		this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen');

		// Respect configured menu bar visibility or default to toggle if not set
831
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
832 833
	}

834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
	isFullScreen(): boolean {
		return this._win.isFullScreen() || this._win.isSimpleFullScreen();
	}

	private setNativeFullScreen(fullscreen: boolean): void {
		if (this._win.isSimpleFullScreen()) {
			this._win.setSimpleFullScreen(false);
		}

		this._win.setFullScreen(fullscreen);
	}

	private setSimpleFullScreen(fullscreen: boolean): void {
		if (this._win.isFullScreen()) {
			this._win.setFullScreen(false);
		}

		this._win.setSimpleFullScreen(fullscreen);
		this._win.webContents.focus(); // workaround issue where focus is not going into window
	}

	private useNativeFullScreen(): boolean {
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
857
		if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
B
Benjamin Pasero 已提交
858 859
			return true; // default
		}
860

B
Benjamin Pasero 已提交
861 862 863 864
		if (windowConfig.nativeTabs) {
			return true; // https://github.com/electron/electron/issues/16142
		}

B
Benjamin Pasero 已提交
865
		return windowConfig.nativeFullScreen !== false;
866 867
	}

B
Benjamin Pasero 已提交
868 869 870 871
	isMinimized(): boolean {
		return this._win.isMinimized();
	}

872
	private getMenuBarVisibility(): MenuBarVisibility {
873
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
874
		if (!windowConfig || !windowConfig.menuBarVisibility) {
875
			return 'default';
B
Benjamin Pasero 已提交
876 877 878 879
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
880
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
881 882 883
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
884 885
	}

886
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
887
		if (isMacintosh) {
B
Benjamin Pasero 已提交
888 889 890
			return; // ignore for macOS platform
		}

891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911
		if (visibility === 'toggle') {
			if (notify) {
				this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key."));
			}
		}

		if (visibility === 'hidden') {
			// for some weird reason that I have no explanation for, the menu bar is not hiding when calling
			// this without timeout (see https://github.com/Microsoft/vscode/issues/19777). there seems to be
			// a timing issue with us opening the first window and the menu bar getting created. somehow the
			// fact that we want to hide the menu without being able to bring it back via Alt key makes Electron
			// still show the menu. Unable to reproduce from a simple Hello World application though...
			setTimeout(() => {
				this.doSetMenuBarVisibility(visibility);
			});
		} else {
			this.doSetMenuBarVisibility(visibility);
		}
	}

	private doSetMenuBarVisibility(visibility: MenuBarVisibility): void {
912
		const isFullscreen = this.isFullScreen();
913

914
		switch (visibility) {
915
			case ('default'):
B
Benjamin Pasero 已提交
916 917
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
918 919
				break;

D
David Terry 已提交
920
			case ('visible'):
B
Benjamin Pasero 已提交
921 922
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
923
				break;
D
David Terry 已提交
924 925

			case ('toggle'):
B
Benjamin Pasero 已提交
926 927
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
928
				break;
D
David Terry 已提交
929 930

			case ('hidden'):
931 932
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(false);
933
				break;
934
		}
935 936
	}

B
Benjamin Pasero 已提交
937
	onWindowTitleDoubleClick(): void {
938 939

		// Respect system settings on mac with regards to title click on windows title
940
		if (isMacintosh) {
941 942 943 944 945 946 947 948 949
			const action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
			switch (action) {
				case 'Minimize':
					this.win.minimize();
					break;
				case 'None':
					break;
				case 'Maximize':
				default:
B
Benjamin Pasero 已提交
950 951 952 953 954
					if (this.win.isMaximized()) {
						this.win.unmaximize();
					} else {
						this.win.maximize();
					}
955 956 957 958 959 960 961 962 963 964 965 966 967
			}
		}

		// Linux/Windows: just toggle maximize/minimized state
		else {
			if (this.win.isMaximized()) {
				this.win.unmaximize();
			} else {
				this.win.maximize();
			}
		}
	}

B
Benjamin Pasero 已提交
968
	close(): void {
969 970 971 972 973
		if (this._win) {
			this._win.close();
		}
	}

B
Benjamin Pasero 已提交
974
	sendWhenReady(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
975
		if (this.isReady) {
976
			this.send(channel, ...args);
B
Benjamin Pasero 已提交
977 978 979
		} else {
			this.ready().then(() => this.send(channel, ...args));
		}
980 981
	}

B
Benjamin Pasero 已提交
982
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
983 984 985
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
986 987
	}

B
Benjamin Pasero 已提交
988
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
989 990 991 992
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
993 994 995 996 997
		// Update segments for all groups. Setting the segments property
		// of the group directly prevents ugly flickering from happening
		this.touchBarGroups.forEach((touchBarGroup, index) => {
			const commands = groups[index];
			touchBarGroup.segments = this.createTouchBarGroupSegments(commands);
998
		});
B
Benjamin Pasero 已提交
999
	}
1000

B
Benjamin Pasero 已提交
1001 1002 1003 1004
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
1005

B
Benjamin Pasero 已提交
1006 1007 1008 1009 1010 1011 1012
		// To avoid flickering, we try to reuse the touch bar group
		// as much as possible by creating a large number of groups
		// for reusing later.
		for (let i = 0; i < 10; i++) {
			const groupTouchBar = this.createTouchBarGroup();
			this.touchBarGroups.push(groupTouchBar);
		}
1013

B
Benjamin Pasero 已提交
1014
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
1015
	}
1016

B
Benjamin Pasero 已提交
1017
	private createTouchBarGroup(items: ISerializableCommandAction[] = []): Electron.TouchBarSegmentedControl {
1018

B
Benjamin Pasero 已提交
1019 1020
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
1021

B
Benjamin Pasero 已提交
1022 1023 1024 1025 1026 1027 1028 1029 1030
		// Group Control
		const control = new TouchBar.TouchBarSegmentedControl({
			segments,
			mode: 'buttons',
			segmentStyle: 'automatic',
			change: (selectedIndex) => {
				this.sendWhenReady('vscode:runAction', { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' });
			}
		});
1031

B
Benjamin Pasero 已提交
1032 1033 1034
		return control;
	}

B
Benjamin Pasero 已提交
1035
	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
B
Benjamin Pasero 已提交
1036
		const segments: ITouchBarSegment[] = items.map(item => {
1037
			let icon: Electron.NativeImage | undefined;
1038
			if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
B
Benjamin Pasero 已提交
1039
				icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
1040
				if (icon.isEmpty()) {
R
Rob Lourens 已提交
1041
					icon = undefined;
1042
				}
1043
			}
1044

1045 1046 1047 1048 1049 1050 1051
			let title: string;
			if (typeof item.title === 'string') {
				title = item.title;
			} else {
				title = item.title.value;
			}

1052
			return {
B
Benjamin Pasero 已提交
1053
				id: item.id,
1054
				label: !icon ? title : undefined,
1055 1056
				icon
			};
1057 1058
		});

B
Benjamin Pasero 已提交
1059
		return segments;
1060 1061
	}

B
Benjamin Pasero 已提交
1062
	dispose(): void {
B
Benjamin Pasero 已提交
1063 1064
		super.dispose();

E
Erich Gamma 已提交
1065 1066 1067 1068
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1069
		this._win = null!; // Important to dereference the window object to allow for GC
E
Erich Gamma 已提交
1070
	}
1071
}