window.ts 33.9 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';
11
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } 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

29 30
export interface IWindowCreationOptions {
	state: IWindowState;
31
	extensionDevelopmentPath?: string;
32
	isExtensionTestHost?: boolean;
33 34
}

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

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

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

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

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

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

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

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

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

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

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

81 82
	private nodeless: boolean;

J
Joao Moreno 已提交
83 84
	constructor(
		config: IWindowCreationOptions,
85 86 87 88 89 90
		@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 已提交
91
	) {
B
Benjamin Pasero 已提交
92 93
		super();

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

99 100
		this.nodeless = !!(environmentService.args.nodeless && !environmentService.isBuilt);

B
Benjamin Pasero 已提交
101 102 103 104 105 106
		// create browser window
		this.createBrowserWindow(config);

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

B
Benjamin Pasero 已提交
107 108 109
		// macOS: touch bar support
		this.createTouchBar();

110 111 112
		// Request handling
		this.handleMarketplaceRequests();

B
Benjamin Pasero 已提交
113 114 115 116 117 118
		// Eventing
		this.registerListeners();
	}

	private createBrowserWindow(config: IWindowCreationOptions): void {

E
Erich Gamma 已提交
119
		// Load window state
B
Benjamin Pasero 已提交
120
		this.windowState = this.restoreWindowState(config.state);
E
Erich Gamma 已提交
121

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

125
		const options: Electron.BrowserWindowConstructorOptions = {
E
Erich Gamma 已提交
126 127 128 129
			width: this.windowState.width,
			height: this.windowState.height,
			x: this.windowState.x,
			y: this.windowState.y,
130
			backgroundColor: this.nodeless ? undefined : getBackgroundColor(this.stateService),
B
Benjamin Pasero 已提交
131 132
			minWidth: CodeWindow.MIN_WIDTH,
			minHeight: CodeWindow.MIN_HEIGHT,
133
			show: !isFullscreenOrMaximized,
B
Benjamin Pasero 已提交
134
			title: product.nameLong,
135
			webPreferences: {
B
Benjamin Pasero 已提交
136 137 138 139
				// 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
140
				backgroundThrottling: false
141
			}
E
Erich Gamma 已提交
142 143
		};

144 145 146 147
		if (this.nodeless) {
			options.webPreferences!.nodeIntegration = false; // simulate Electron 5 behaviour
		}

148
		if (isLinux) {
149
			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 已提交
150 151
		}

152
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
153

154 155 156 157
		if (isMacintosh && !this.useNativeFullScreen()) {
			options.fullscreenable = false; // enables simple fullscreen mode
		}

158 159 160 161 162 163 164 165 166
		if (isMacintosh) {
			options.acceptFirstMouse = true; // enabled by default

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

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

B
Benjamin Pasero 已提交
170
		const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom';
171 172 173
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
R
Ryan Adolf 已提交
174
			if (!isMacintosh) {
R
Ryan Adolf 已提交
175 176
				options.frame = false;
			}
177 178
		}

E
Erich Gamma 已提交
179 180
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
181
		this._id = this._win.id;
E
Erich Gamma 已提交
182

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

187
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
188
			this._win.maximize();
E
Erich Gamma 已提交
189

B
Benjamin Pasero 已提交
190
			if (this.windowState.mode === WindowMode.Fullscreen) {
191
				this.setFullScreen(true);
192 193
			}

B
Benjamin Pasero 已提交
194 195
			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 已提交
196 197 198
			}
		}

199 200 201 202
		if (this.nodeless) {
			this._win.webContents.toggleDevTools();
		}

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

B
Benjamin Pasero 已提交
206
	hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
207
		return this.hiddenTitleBarStyle;
208 209
	}

B
Benjamin Pasero 已提交
210
	get isExtensionDevelopmentHost(): boolean {
211
		return !!this.config.extensionDevelopmentPath;
212 213
	}

B
Benjamin Pasero 已提交
214
	get isExtensionTestHost(): boolean {
215
		return !!this.config.extensionTestsPath;
216 217
	}

218
	get extensionDevelopmentPath(): string | undefined {
219
		return this.config.extensionDevelopmentPath;
E
Erich Gamma 已提交
220 221
	}

B
Benjamin Pasero 已提交
222
	get config(): IWindowConfiguration {
E
Erich Gamma 已提交
223 224 225
		return this.currentConfig;
	}

B
Benjamin Pasero 已提交
226
	get id(): number {
B
Benjamin Pasero 已提交
227 228 229
		return this._id;
	}

B
Benjamin Pasero 已提交
230
	get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
231 232 233
		return this._win;
	}

B
Benjamin Pasero 已提交
234
	setRepresentedFilename(filename: string): void {
235
		if (isMacintosh) {
236 237 238 239 240 241
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

B
Benjamin Pasero 已提交
242
	getRepresentedFilename(): string {
243
		if (isMacintosh) {
244 245 246 247 248 249
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
250
	focus(): void {
E
Erich Gamma 已提交
251 252 253 254
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
255 256
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
257 258
		}

B
Benjamin Pasero 已提交
259
		this._win.focus();
E
Erich Gamma 已提交
260 261
	}

B
Benjamin Pasero 已提交
262
	get lastFocusTime(): number {
E
Erich Gamma 已提交
263 264 265
		return this._lastFocusTime;
	}

266
	get backupPath(): string | undefined {
R
Rob Lourens 已提交
267
		return this.currentConfig ? this.currentConfig.backupPath : undefined;
E
Erich Gamma 已提交
268 269
	}

270
	get openedWorkspace(): IWorkspaceIdentifier | undefined {
R
Rob Lourens 已提交
271
		return this.currentConfig ? this.currentConfig.workspace : undefined;
272 273
	}

274
	get openedFolderUri(): URI | undefined {
R
Rob Lourens 已提交
275
		return this.currentConfig ? this.currentConfig.folderUri : undefined;
276 277
	}

278
	get remoteAuthority(): string | undefined {
R
Rob Lourens 已提交
279
		return this.currentConfig ? this.currentConfig.remoteAuthority : undefined;
M
Martin Aeschlimann 已提交
280 281
	}

B
Benjamin Pasero 已提交
282
	setReady(): void {
E
Erich Gamma 已提交
283 284 285 286
		this._readyState = ReadyState.READY;

		// inform all waiting promises that we are ready now
		while (this.whenReadyCallbacks.length) {
287
			this.whenReadyCallbacks.pop()!(this);
E
Erich Gamma 已提交
288 289 290
		}
	}

J
Johannes Rieken 已提交
291
	ready(): Promise<ICodeWindow> {
B
Benjamin Pasero 已提交
292 293 294
		return new Promise<ICodeWindow>(resolve => {
			if (this.isReady) {
				return resolve(this);
E
Erich Gamma 已提交
295 296 297
			}

			// otherwise keep and call later when we are ready
B
Benjamin Pasero 已提交
298
			this.whenReadyCallbacks.push(resolve);
E
Erich Gamma 已提交
299 300 301
		});
	}

B
Benjamin Pasero 已提交
302 303
	get isReady(): boolean {
		return this._readyState === ReadyState.READY;
E
Erich Gamma 已提交
304 305
	}

306 307 308 309
	private handleMarketplaceRequests(): void {

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

311 312
		// Inject headers when requests are incoming
		const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
M
Matt Bierner 已提交
313
		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
314
			this.marketplaceHeadersPromise.then(headers => {
315 316
				cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
			});
317
		});
318 319 320
	}

	private registerListeners(): void {
321 322

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

			return callback({});
		});

334
		this._win.webContents.session.webRequest.onHeadersReceived(null!, (details: any, callback: any) => {
335 336 337 338 339 340 341 342
			const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any;
			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 已提交
343 344 345 346 347 348 349 350
		// 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;

351
				this.pendingLoadConfig = undefined;
E
Erich Gamma 已提交
352 353
			}

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

B
Benjamin Pasero 已提交
360 361
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
362 363 364 365 366
				}
			}
		});

		// App commands support
367
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
368 369 370

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

B
Benjamin Pasero 已提交
374 375 376
		// 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 已提交
377
		if (isMacintosh) {
B
Benjamin Pasero 已提交
378 379 380 381 382 383
			const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
				if (!this._win) {
					return; // disposed
				}

				if (!this.useNativeFullScreen() && this.isFullScreen()) {
B
Benjamin Pasero 已提交
384 385 386
					this.setFullScreen(false);
					this.setFullScreen(true);
				}
B
Benjamin Pasero 已提交
387 388 389 390 391 392 393 394 395
			}, 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 已提交
396

B
Benjamin Pasero 已提交
397 398
			screen.on('display-removed', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener)));
B
Benjamin Pasero 已提交
399
		}
400

S
SteVen Batten 已提交
401
		// Window (Un)Maximize
402
		this._win.on('maximize', (e: Event) => {
B
Benjamin Pasero 已提交
403 404 405 406
			if (this.currentConfig) {
				this.currentConfig.maximized = true;
			}

407 408 409
			app.emit('browser-window-maximize', e, this._win);
		});

410
		this._win.on('unmaximize', (e: Event) => {
B
Benjamin Pasero 已提交
411 412 413 414
			if (this.currentConfig) {
				this.currentConfig.maximized = false;
			}

415 416
			app.emit('browser-window-unmaximize', e, this._win);
		});
S
SteVen Batten 已提交
417

418 419 420 421 422 423 424 425 426
		// 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 已提交
427
		// Window Failed to load
428
		this._win.webContents.on('did-fail-load', (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
B
Benjamin Pasero 已提交
429
			this.logService.warn('[electron event]: fail to load, ', errorDescription);
E
Erich Gamma 已提交
430 431
		});

B
Benjamin Pasero 已提交
432
		// Handle configuration changes
B
Benjamin Pasero 已提交
433
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
434

435
		// Handle Workspace events
B
Benjamin Pasero 已提交
436
		this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
437 438
	}

439
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
440 441 442

		// Make sure to update our workspace config if we detect that it
		// was deleted
443
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
R
Rob Lourens 已提交
444
			this.currentConfig.workspace = undefined;
445
		}
E
Erich Gamma 已提交
446 447
	}

B
Benjamin Pasero 已提交
448
	private onConfigurationUpdated(): void {
449
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
450 451 452 453
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
454 455

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

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

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

480 481 482 483 484 485
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

B
Benjamin Pasero 已提交
486
	load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
E
Erich Gamma 已提交
487 488 489

		// 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 已提交
490
		if (this._readyState === ReadyState.NONE) {
E
Erich Gamma 已提交
491 492 493 494 495 496 497 498 499 500 501 502
			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;
		}

503 504 505 506 507 508 509
		// 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;
		}

510
		// Clear Document Edited if needed
511
		if (isMacintosh && this._win.isDocumentEdited()) {
B
Benjamin Pasero 已提交
512
			if (!isReload || !this.backupMainService.isHotExitEnabled()) {
513 514 515 516 517 518 519 520 521 522 523
				this._win.setDocumentEdited(false);
			}
		}

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

			this._win.setTitle(product.nameLong);
524 525
		}

E
Erich Gamma 已提交
526
		// Load URL
527
		perf.mark('main:loadWindow');
528
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
529 530

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

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

545
		// If config is not provided, copy our current one
546
		const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig);
547 548

		// Delete some properties we do not want during reload
E
Erich Gamma 已提交
549 550
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
551
		delete configuration.filesToDiff;
552
		delete configuration.filesToWait;
553

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

564 565
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
566
		// Load config
567 568
		const disableExtensions = cli ? cli['disable-extensions'] : undefined;
		this.load(configuration, true, disableExtensions);
E
Erich Gamma 已提交
569 570
	}

571
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
572

573 574
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
575
		windowConfiguration.logLevel = this.logService.getLevel();
576

577
		// Set zoomlevel
578
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
579
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
580 581 582 583
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

584
		// Set fullscreen state
585
		windowConfiguration.fullscreen = this.isFullScreen();
586

587
		// Set Accessibility Config
588 589 590 591 592
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
593 594
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

B
Benjamin Pasero 已提交
595 596
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
597
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
598

599 600
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
601

602
		// Parts splash
603
		windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json');
604

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

614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
		// 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 {
633 634 635 636
		if (this.nodeless) {
			return `${require.toUrl('vs/code/electron-browser/workbench/workbench.nodeless.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
		}

637
		return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
E
Erich Gamma 已提交
638 639
	}

B
Benjamin Pasero 已提交
640
	serializeWindowState(): IWindowState {
641 642 643
		if (!this._win) {
			return defaultWindowState();
		}
644 645

		// fullscreen gets special treatment
646
		if (this.isFullScreen()) {
647 648
			const display = screen.getDisplayMatching(this.getBounds());

B
Benjamin Pasero 已提交
649 650 651
			const defaultState = defaultWindowState();

			const res = {
652
				mode: WindowMode.Fullscreen,
R
Rob Lourens 已提交
653
				display: display ? display.id : undefined,
654

B
Benjamin Pasero 已提交
655 656 657 658 659 660 661 662 663
				// 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
664
			};
B
Benjamin Pasero 已提交
665 666

			return res;
E
Erich Gamma 已提交
667 668
		}

669
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
670 671 672
		let mode: WindowMode;

		// get window mode
673
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
674 675 676 677 678 679 680 681
			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;
682
		} else {
E
Erich Gamma 已提交
683 684 685 686 687
			state.mode = WindowMode.Normal;
		}

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

690 691 692 693
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
694 695 696 697 698
		}

		return state;
	}

B
Benjamin Pasero 已提交
699
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
700 701
		if (state) {
			try {
702
				state = this.validateWindowState(state) || undefined;
E
Erich Gamma 已提交
703
			} catch (err) {
J
Joao Moreno 已提交
704
				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 已提交
705 706 707 708 709 710 711
			}
		}

		if (!state) {
			state = defaultWindowState();
		}

B
Benjamin Pasero 已提交
712
		return state;
E
Erich Gamma 已提交
713 714
	}

715
	private validateWindowState(state: IWindowState): IWindowState | null {
E
Erich Gamma 已提交
716 717 718 719
		if (!state) {
			return null;
		}

720 721 722 723 724
		if (typeof state.x !== 'number'
			|| typeof state.y !== 'number'
			|| typeof state.width !== 'number'
			|| typeof state.height !== 'number'
		) {
E
Erich Gamma 已提交
725 726 727 728 729 730 731
			return null;
		}

		if (state.width <= 0 || state.height <= 0) {
			return null;
		}

732
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
733 734 735

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
736
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771

			// Careful with maximized: in that mode x/y can well be negative!
			if (state.mode !== WindowMode.Maximized && displayBounds.width > 0 && displayBounds.height > 0 /* Linux X11 sessions sometimes report wrong display bounds */) {
				if (state.x < displayBounds.x) {
					state.x = displayBounds.x; // prevent window from falling out of the screen to the left
				}

				if (state.y < displayBounds.y) {
					state.y = displayBounds.y; // prevent window from falling out of the screen to the top
				}

				if (state.x > (displayBounds.x + displayBounds.width)) {
					state.x = displayBounds.x; // prevent window from falling out of the screen to the right
				}

				if (state.y > (displayBounds.y + displayBounds.height)) {
					state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom
				}

				if (state.width > displayBounds.width) {
					state.width = displayBounds.width; // prevent window from exceeding display bounds width
				}

				if (state.height > displayBounds.height) {
					state.height = displayBounds.height; // prevent window from exceeding display bounds height
				}
			}

			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;
		}

772 773 774 775 776 777 778 779 780 781 782 783 784
		// 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
785 786
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
U
unknown 已提交
787 788 789 790 791 792 793
		if (
			display &&												// we have a display matching the desired bounds
			bounds.x < display.bounds.x + display.bounds.width &&	// prevent window from falling out of the screen to the right
			bounds.y < display.bounds.y + display.bounds.height &&	// prevent window from falling out of the screen to the bottom
			bounds.x + bounds.width > display.bounds.x &&			// prevent window from falling out of the screen to the left
			bounds.y + bounds.height > display.bounds.y				// prevent window from falling out of the scree nto the top
		) {
E
Erich Gamma 已提交
794
			if (state.mode === WindowMode.Maximized) {
795
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
796 797 798 799 800 801 802 803 804 805 806 807
				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;
		}

		return null;
	}

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

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

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

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

821 822 823 824 825 826 827 828 829 830 831
		// 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
832
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
833 834
	}

835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
	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 已提交
858
		if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
B
Benjamin Pasero 已提交
859 860
			return true; // default
		}
861

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

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

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

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

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

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

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

892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
		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 {
913
		const isFullscreen = this.isFullScreen();
914

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

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

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

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
994 995 996 997 998
		// 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);
999
		});
B
Benjamin Pasero 已提交
1000
	}
1001

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

B
Benjamin Pasero 已提交
1007 1008 1009 1010 1011 1012 1013
		// 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);
		}
1014

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

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

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

B
Benjamin Pasero 已提交
1023 1024 1025 1026 1027 1028 1029 1030 1031
		// 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' });
			}
		});
1032

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

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

			return {
B
Benjamin Pasero 已提交
1047
				id: item.id,
R
Rob Lourens 已提交
1048
				label: !icon ? item.title as string : undefined,
1049 1050
				icon
			};
1051 1052
		});

B
Benjamin Pasero 已提交
1053
		return segments;
1054 1055
	}

B
Benjamin Pasero 已提交
1056
	dispose(): void {
B
Benjamin Pasero 已提交
1057 1058
		super.dispose();

E
Erich Gamma 已提交
1059 1060 1061 1062
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

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