window.ts 33.6 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.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6 7
import * as path from 'path';
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 { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
13
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
14
import { ILogService } from 'vs/platform/log/common/log';
15
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
16
import { parseArgs } from 'vs/platform/environment/node/argv';
17
import product from 'vs/platform/node/product';
18
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows';
19
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
20
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
21
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
22
import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
B
Benjamin Pasero 已提交
23
import { IBackupMainService } from 'vs/platform/backup/common/backup';
B
Benjamin Pasero 已提交
24
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
25
import * as perf from 'vs/base/common/performance';
26
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
27
import { getBackgroundColor } from 'vs/code/electron-main/theme';
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 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 toDispose: IDisposable[];
71
	private representedFilename: string;
E
Erich Gamma 已提交
72

B
Benjamin Pasero 已提交
73
	private whenReadyCallbacks: TValueCallback<ICodeWindow>[];
E
Erich Gamma 已提交
74 75 76 77

	private currentConfig: IWindowConfiguration;
	private pendingLoadConfig: IWindowConfiguration;

78 79
	private marketplaceHeadersPromise: TPromise<object>;

B
Benjamin Pasero 已提交
80
	private touchBarGroups: Electron.TouchBarSegmentedControl[];
81

J
Joao Moreno 已提交
82 83 84
	constructor(
		config: IWindowCreationOptions,
		@ILogService private logService: ILogService,
85
		@IEnvironmentService private environmentService: IEnvironmentService,
86
		@IConfigurationService private configurationService: IConfigurationService,
B
Benjamin Pasero 已提交
87
		@IStateService private stateService: IStateService,
B
Benjamin Pasero 已提交
88 89
		@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
		@IBackupMainService private backupMainService: IBackupMainService
J
Joao Moreno 已提交
90
	) {
B
Benjamin Pasero 已提交
91
		this.touchBarGroups = [];
E
Erich Gamma 已提交
92 93 94
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
		this.whenReadyCallbacks = [];
95
		this.toDispose = [];
E
Erich Gamma 已提交
96

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

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

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

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

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

	private createBrowserWindow(config: IWindowCreationOptions): void {

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

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

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

140
		if (isLinux) {
141
			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 已提交
142 143
		}

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

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

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

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

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

164
		let useCustomTitleStyle = false;
S
SteVen Batten 已提交
165
		if (isMacintosh) {
S
SteVen Batten 已提交
166
			useCustomTitleStyle = !windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom'; // Default to custom on macOS
S
SteVen Batten 已提交
167

168
			const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
S
SteVen Batten 已提交
169 170
			if (isDev) {
				useCustomTitleStyle = false; // not enabled when developing due to https://github.com/electron/electron/issues/3647
B
Benjamin Pasero 已提交
171
			}
S
SteVen Batten 已提交
172
		} else {
173 174 175 176 177
			if (isLinux) {
				useCustomTitleStyle = windowConfig && windowConfig.titleBarStyle === 'custom';
			} else {
				useCustomTitleStyle = !windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom'; // Default to custom on Windows
			}
B
Benjamin Pasero 已提交
178 179
		}

180 181 182 183
		if (useNativeTabs) {
			useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
		}

184 185 186
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
R
Ryan Adolf 已提交
187
			if (!isMacintosh) {
R
Ryan Adolf 已提交
188 189
				options.frame = false;
			}
190 191
		}

E
Erich Gamma 已提交
192 193
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
194
		this._id = this._win.id;
E
Erich Gamma 已提交
195

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

200
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
201
			this._win.maximize();
E
Erich Gamma 已提交
202

B
Benjamin Pasero 已提交
203
			if (this.windowState.mode === WindowMode.Fullscreen) {
204
				this.setFullScreen(true);
205 206
			}

B
Benjamin Pasero 已提交
207 208
			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 已提交
209 210 211
			}
		}

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

B
Benjamin Pasero 已提交
215
	hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
216
		return this.hiddenTitleBarStyle;
217 218
	}

B
Benjamin Pasero 已提交
219
	get isExtensionDevelopmentHost(): boolean {
220
		return !!this.config.extensionDevelopmentPath;
221 222
	}

B
Benjamin Pasero 已提交
223
	get isExtensionTestHost(): boolean {
224
		return !!this.config.extensionTestsPath;
225 226
	}

B
Benjamin Pasero 已提交
227
	get extensionDevelopmentPath(): string {
228
		return this.config.extensionDevelopmentPath;
E
Erich Gamma 已提交
229 230
	}

B
Benjamin Pasero 已提交
231
	get config(): IWindowConfiguration {
E
Erich Gamma 已提交
232 233 234
		return this.currentConfig;
	}

B
Benjamin Pasero 已提交
235
	get id(): number {
B
Benjamin Pasero 已提交
236 237 238
		return this._id;
	}

B
Benjamin Pasero 已提交
239
	get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
240 241 242
		return this._win;
	}

B
Benjamin Pasero 已提交
243
	setRepresentedFilename(filename: string): void {
244
		if (isMacintosh) {
245 246 247 248 249 250
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

B
Benjamin Pasero 已提交
251
	getRepresentedFilename(): string {
252
		if (isMacintosh) {
253 254 255 256 257 258
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
259
	focus(): void {
E
Erich Gamma 已提交
260 261 262 263
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
264 265
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
266 267
		}

B
Benjamin Pasero 已提交
268
		this._win.focus();
E
Erich Gamma 已提交
269 270
	}

B
Benjamin Pasero 已提交
271
	get lastFocusTime(): number {
E
Erich Gamma 已提交
272 273 274
		return this._lastFocusTime;
	}

B
Benjamin Pasero 已提交
275
	get backupPath(): string {
276
		return this.currentConfig ? this.currentConfig.backupPath : void 0;
E
Erich Gamma 已提交
277 278
	}

B
Benjamin Pasero 已提交
279
	get openedWorkspace(): IWorkspaceIdentifier {
280
		return this.currentConfig ? this.currentConfig.workspace : void 0;
281 282
	}

283 284
	get openedFolderUri(): URI {
		return this.currentConfig ? this.currentConfig.folderUri : void 0;
285 286
	}

B
Benjamin Pasero 已提交
287
	setReady(): void {
E
Erich Gamma 已提交
288 289 290 291 292 293 294 295
		this._readyState = ReadyState.READY;

		// inform all waiting promises that we are ready now
		while (this.whenReadyCallbacks.length) {
			this.whenReadyCallbacks.pop()(this);
		}
	}

B
Benjamin Pasero 已提交
296
	ready(): TPromise<ICodeWindow> {
B
Benjamin Pasero 已提交
297
		return new TPromise<ICodeWindow>((c) => {
E
Erich Gamma 已提交
298 299 300 301 302 303 304 305 306
			if (this._readyState === ReadyState.READY) {
				return c(this);
			}

			// otherwise keep and call later when we are ready
			this.whenReadyCallbacks.push(c);
		});
	}

B
Benjamin Pasero 已提交
307
	get readyState(): ReadyState {
E
Erich Gamma 已提交
308 309 310
		return this._readyState;
	}

311 312 313 314
	private handleMarketplaceRequests(): void {

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

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

	private registerListeners(): void {
326 327

		// Prevent loading of svgs
328
		this._win.webContents.session.webRequest.onBeforeRequest(null, (details, callback) => {
329 330 331 332 333 334 335 336 337 338
			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({});
		});

M
Matt Bierner 已提交
339
		this._win.webContents.session.webRequest.onHeadersReceived(null, (details: any, callback: any) => {
340 341 342 343 344 345 346 347
			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 已提交
348 349 350 351 352 353 354 355
		// 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;

B
Benjamin Pasero 已提交
356
				this.pendingLoadConfig = null;
E
Erich Gamma 已提交
357 358
			}

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

B
Benjamin Pasero 已提交
365 366
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
367 368 369 370 371
				}
			}
		});

		// App commands support
372
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
373 374 375

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

379 380 381 382 383 384 385 386
		// Simple fullscreen doesn't resize automatically when the resolution changes
		screen.on('display-metrics-changed', () => {
			if (isMacintosh && this.isFullScreen() && !this.useNativeFullScreen()) {
				this.setFullScreen(false);
				this.setFullScreen(true);
			}
		});

S
SteVen Batten 已提交
387
		// Window (Un)Maximize
388
		this._win.on('maximize', (e) => {
B
Benjamin Pasero 已提交
389 390 391 392
			if (this.currentConfig) {
				this.currentConfig.maximized = true;
			}

393 394 395 396
			app.emit('browser-window-maximize', e, this._win);
		});

		this._win.on('unmaximize', (e) => {
B
Benjamin Pasero 已提交
397 398 399 400
			if (this.currentConfig) {
				this.currentConfig.maximized = false;
			}

401 402
			app.emit('browser-window-unmaximize', e, this._win);
		});
S
SteVen Batten 已提交
403

404 405 406 407 408 409 410 411 412
		// 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 已提交
413
		// Window Failed to load
414
		this._win.webContents.on('did-fail-load', (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
B
Benjamin Pasero 已提交
415
			this.logService.warn('[electron event]: fail to load, ', errorDescription);
E
Erich Gamma 已提交
416 417
		});

B
Benjamin Pasero 已提交
418
		// Handle configuration changes
419
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
420

421
		// Handle Workspace events
B
Benjamin Pasero 已提交
422
		this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450

		// TODO@Ben workaround for https://github.com/Microsoft/vscode/issues/13612
		// It looks like smooth scrolling disappears as soon as the window is minimized
		// and maximized again. Touching some window properties "fixes" it, like toggling
		// the visibility of the menu.
		if (isWindows) {
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
			if (windowConfig && windowConfig.smoothScrollingWorkaround === true) {
				let minimized = false;

				const restoreSmoothScrolling = () => {
					if (minimized) {
						const visibility = this.getMenuBarVisibility();
						const temporaryVisibility: MenuBarVisibility = (visibility === 'hidden' || visibility === 'toggle') ? 'default' : 'hidden';
						setTimeout(() => {
							this.doSetMenuBarVisibility(temporaryVisibility);
							this.doSetMenuBarVisibility(visibility);
						}, 0);
					}

					minimized = false;
				};

				this._win.on('minimize', () => minimized = true);
				this._win.on('restore', () => restoreSmoothScrolling());
				this._win.on('maximize', () => restoreSmoothScrolling());
			}
		}
451 452
	}

453
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
454 455 456

		// Make sure to update our workspace config if we detect that it
		// was deleted
457
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
458
			this.currentConfig.workspace = void 0;
459
		}
E
Erich Gamma 已提交
460 461
	}

B
Benjamin Pasero 已提交
462
	private onConfigurationUpdated(): void {
463
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
464 465 466 467
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
468 469

		// Swipe command support (macOS)
470
		if (isMacintosh) {
471
			const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
B
Benjamin Pasero 已提交
472
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
473
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
474 475 476
			} else {
				this._win.removeAllListeners('swipe');
			}
477
		}
478
	}
B
Benjamin Pasero 已提交
479

B
Benjamin Pasero 已提交
480
	private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
481
		this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
B
Benjamin Pasero 已提交
482 483 484 485 486
			if (this.readyState !== ReadyState.READY) {
				return; // window must be ready
			}

			if (cmd === back) {
487
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
488
			} else if (cmd === forward) {
489
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
490 491 492 493
			}
		});
	}

494 495 496 497 498 499
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

B
Benjamin Pasero 已提交
500
	load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
E
Erich Gamma 已提交
501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516

		// 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
		if (this.readyState === ReadyState.NONE) {
			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;
		}

517 518 519 520 521 522 523
		// 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;
		}

524
		// Clear Document Edited if needed
525
		if (isMacintosh && this._win.isDocumentEdited()) {
B
Benjamin Pasero 已提交
526
			if (!isReload || !this.backupMainService.isHotExitEnabled()) {
527 528 529 530 531 532 533 534 535 536 537
				this._win.setDocumentEdited(false);
			}
		}

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

			this._win.setTitle(product.nameLong);
538 539
		}

E
Erich Gamma 已提交
540
		// Load URL
541
		perf.mark('main:loadWindow');
542
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
543 544

		// Make window visible if it did not open in N seconds because this indicates an error
545 546
		// Only do this when running out of sources and not when running tests
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) {
E
Erich Gamma 已提交
547 548 549 550
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
551
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
552 553 554 555 556
				}
			}, 10000);
		}
	}

B
Benjamin Pasero 已提交
557
	reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
558

559 560 561 562 563 564
		// If config is not provided, copy our current one
		if (!configuration) {
			configuration = objects.mixin({}, this.currentConfig);
		}

		// Delete some properties we do not want during reload
E
Erich Gamma 已提交
565 566
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
567
		delete configuration.filesToDiff;
568
		delete configuration.filesToWait;
569

E
Erich Gamma 已提交
570
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
571
		// in extension development mode. These options are all development related.
572
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
573
			configuration.verbose = cli.verbose;
574
			configuration.debugPluginHost = cli.debugPluginHost;
575
			configuration.debugBrkPluginHost = cli.debugBrkPluginHost;
576
			configuration.debugId = cli.debugId;
577
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
578 579
		}

580 581
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
582
		// Load config
583 584
		const disableExtensions = cli ? cli['disable-extensions'] : undefined;
		this.load(configuration, true, disableExtensions);
E
Erich Gamma 已提交
585 586
	}

587
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
588

589 590
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
591
		windowConfiguration.logLevel = this.logService.getLevel();
592

593
		// Set zoomlevel
594
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
595
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
596 597 598 599
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

600
		// Set fullscreen state
601
		windowConfiguration.fullscreen = this.isFullScreen();
602

603
		// Set Accessibility Config
604 605 606 607 608
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
609 610
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

B
Benjamin Pasero 已提交
611 612
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
613
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
614

615 616
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
617

618 619 620
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
621
		for (let key in config) {
622
			if (config[key] === void 0 || config[key] === null || config[key] === '' || config[key] === false) {
623 624 625
				delete config[key]; // only send over properties that have a true value
			}
		}
626

627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
		// 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 {
646
		return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
E
Erich Gamma 已提交
647 648
	}

B
Benjamin Pasero 已提交
649
	serializeWindowState(): IWindowState {
650 651 652
		if (!this._win) {
			return defaultWindowState();
		}
653 654

		// fullscreen gets special treatment
655
		if (this.isFullScreen()) {
656 657
			const display = screen.getDisplayMatching(this.getBounds());

B
Benjamin Pasero 已提交
658 659 660
			const defaultState = defaultWindowState();

			const res = {
661
				mode: WindowMode.Fullscreen,
662 663
				display: display ? display.id : void 0,

B
Benjamin Pasero 已提交
664 665 666 667 668 669 670 671 672
				// 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
673
			};
B
Benjamin Pasero 已提交
674 675

			return res;
E
Erich Gamma 已提交
676 677
		}

678
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
679 680 681
		let mode: WindowMode;

		// get window mode
682
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
683 684 685 686 687 688 689 690
			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;
691
		} else {
E
Erich Gamma 已提交
692 693 694 695 696
			state.mode = WindowMode.Normal;
		}

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

699 700 701 702
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
703 704 705 706 707
		}

		return state;
	}

B
Benjamin Pasero 已提交
708
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
709 710 711 712
		if (state) {
			try {
				state = this.validateWindowState(state);
			} catch (err) {
J
Joao Moreno 已提交
713
				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 已提交
714 715 716 717 718 719 720
			}
		}

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

B
Benjamin Pasero 已提交
721
		return state;
E
Erich Gamma 已提交
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
	}

	private validateWindowState(state: IWindowState): IWindowState {
		if (!state) {
			return null;
		}

		if ([state.x, state.y, state.width, state.height].some(n => typeof n !== 'number')) {
			return null;
		}

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

737
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
738 739 740

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
741
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
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 772 773 774 775 776

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

777 778 779 780 781 782 783 784 785 786 787 788 789
		// 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
790 791
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
U
unknown 已提交
792 793 794 795 796 797 798
		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 已提交
799
			if (state.mode === WindowMode.Maximized) {
800
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
801 802 803 804 805 806 807 808 809 810 811 812
				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 已提交
813
	getBounds(): Electron.Rectangle {
B
Benjamin Pasero 已提交
814 815
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
816 817 818 819

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

B
Benjamin Pasero 已提交
820
	toggleFullScreen(): void {
821 822
		this.setFullScreen(!this.isFullScreen());
	}
E
Erich Gamma 已提交
823

824
	private setFullScreen(fullscreen: boolean): void {
825

826 827 828 829 830 831 832 833 834 835 836
		// 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
837
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
838 839
	}

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

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

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

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

		return menuBarVisibility;
E
Erich Gamma 已提交
882 883
	}

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

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

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

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

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

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

B
Benjamin Pasero 已提交
935
	onWindowTitleDoubleClick(): void {
936 937

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

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

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

B
Benjamin Pasero 已提交
972
	sendWhenReady(channel: string, ...args: any[]): void {
973 974 975 976 977
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

B
Benjamin Pasero 已提交
978
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
979 980 981
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
982 983
	}

B
Benjamin Pasero 已提交
984
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
985 986 987 988
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
989 990 991 992 993
		// 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);
994
		});
B
Benjamin Pasero 已提交
995
	}
996

B
Benjamin Pasero 已提交
997 998 999 1000
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
1001

B
Benjamin Pasero 已提交
1002 1003 1004 1005 1006 1007 1008
		// 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);
		}
1009

B
Benjamin Pasero 已提交
1010
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
1011
	}
1012

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

B
Benjamin Pasero 已提交
1015 1016
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
1017

B
Benjamin Pasero 已提交
1018 1019 1020 1021 1022 1023 1024 1025 1026
		// 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' });
			}
		});
1027

B
Benjamin Pasero 已提交
1028 1029 1030
		return control;
	}

B
Benjamin Pasero 已提交
1031
	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
B
Benjamin Pasero 已提交
1032
		const segments: ITouchBarSegment[] = items.map(item => {
1033
			let icon: Electron.NativeImage;
1034
			if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
B
Benjamin Pasero 已提交
1035
				icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
1036 1037 1038
				if (icon.isEmpty()) {
					icon = void 0;
				}
1039
			}
1040 1041

			return {
B
Benjamin Pasero 已提交
1042
				id: item.id,
1043 1044 1045
				label: !icon ? item.title as string : void 0,
				icon
			};
1046 1047
		});

B
Benjamin Pasero 已提交
1048
		return segments;
1049 1050
	}

B
Benjamin Pasero 已提交
1051
	dispose(): void {
E
Erich Gamma 已提交
1052 1053 1054 1055
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1056 1057
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
1058 1059
		this._win = null; // Important to dereference the window object to allow for GC
	}
1060
}