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

'use strict';

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

30 31
export interface IWindowCreationOptions {
	state: IWindowState;
32
	extensionDevelopmentPath?: 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 implements ICodeWindow {
E
Erich Gamma 已提交
57

M
Matt Bierner 已提交
58 59
	public static readonly themeStorageKey = 'theme';
	public static readonly themeBackgroundStorageKey = 'themeBackground';
60

61 62 63
	private static readonly DEFAULT_BG_LIGHT = '#FFFFFF';
	private static readonly DEFAULT_BG_DARK = '#1E1E1E';
	private static readonly DEFAULT_BG_HC_BLACK = '#000000';
B
Benjamin Pasero 已提交
64

65 66
	private static readonly MIN_WIDTH = 200;
	private static readonly MIN_HEIGHT = 120;
E
Erich Gamma 已提交
67

B
Benjamin Pasero 已提交
68
	private hiddenTitleBarStyle: boolean;
E
Erich Gamma 已提交
69
	private showTimeoutHandle: any;
B
Benjamin Pasero 已提交
70
	private _id: number;
71
	private _win: Electron.BrowserWindow;
E
Erich Gamma 已提交
72 73 74
	private _lastFocusTime: number;
	private _readyState: ReadyState;
	private windowState: IWindowState;
75
	private currentMenuBarVisibility: MenuBarVisibility;
76
	private toDispose: IDisposable[];
77
	private representedFilename: string;
E
Erich Gamma 已提交
78

B
Benjamin Pasero 已提交
79
	private whenReadyCallbacks: TValueCallback<ICodeWindow>[];
E
Erich Gamma 已提交
80 81 82 83

	private currentConfig: IWindowConfiguration;
	private pendingLoadConfig: IWindowConfiguration;

84 85
	private marketplaceHeadersPromise: TPromise<object>;

B
Benjamin Pasero 已提交
86
	private touchBarGroups: Electron.TouchBarSegmentedControl[];
87

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

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

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

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

112 113 114
		// Request handling
		this.handleMarketplaceRequests();

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

	private createBrowserWindow(config: IWindowCreationOptions): void {

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

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

B
Benjamin Pasero 已提交
127 128 129 130 131
		let backgroundColor = this.getBackgroundColor();
		if (isMacintosh && backgroundColor.toUpperCase() === CodeWindow.DEFAULT_BG_DARK) {
			backgroundColor = '#171717'; // https://github.com/electron/electron/issues/5150
		}

132
		const options: Electron.BrowserWindowConstructorOptions = {
E
Erich Gamma 已提交
133 134 135 136
			width: this.windowState.width,
			height: this.windowState.height,
			x: this.windowState.x,
			y: this.windowState.y,
B
Benjamin Pasero 已提交
137
			backgroundColor,
B
Benjamin Pasero 已提交
138 139
			minWidth: CodeWindow.MIN_WIDTH,
			minHeight: CodeWindow.MIN_HEIGHT,
140
			show: !isFullscreenOrMaximized,
B
Benjamin Pasero 已提交
141
			title: product.nameLong,
142
			webPreferences: {
143
				'backgroundThrottling': false, // by default if Code is in the background, intervals and timeouts get throttled,
144
				disableBlinkFeatures: 'Auxclick' // disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
145
			}
E
Erich Gamma 已提交
146 147
		};

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 158 159 160 161
		if (isMacintosh) {
			options.acceptFirstMouse = true; // enabled by default

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

162
		let useNativeTabs = false;
163
		if (isMacintosh && windowConfig && windowConfig.nativeTabs === true) {
164 165 166 167
			options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
			useNativeTabs = true;
		}

168
		let useCustomTitleStyle = false;
S
SteVen Batten 已提交
169
		if (isMacintosh) {
S
SteVen Batten 已提交
170
			useCustomTitleStyle = !windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom'; // Default to custom on macOS
S
SteVen Batten 已提交
171

172
			const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
S
SteVen Batten 已提交
173 174
			if (isDev) {
				useCustomTitleStyle = false; // not enabled when developing due to https://github.com/electron/electron/issues/3647
B
Benjamin Pasero 已提交
175
			}
S
SteVen Batten 已提交
176 177
		} else {
			useCustomTitleStyle = windowConfig && windowConfig.titleBarStyle === 'custom'; // Must be specified on Windows/Linux
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 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
		// Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups,
		// it can happen that the position we set to the window is not the correct one on the display.
		// To workaround, we ask the window for its position and set it again if not matching.
		// This only applies if the window is not fullscreen or maximized and multiple monitors are used.
		if (isWindows && !isFullscreenOrMaximized) {
			try {
				if (screen.getAllDisplays().length > 1) {
					const [x, y] = this._win.getPosition();
					if (x !== this.windowState.x || y !== this.windowState.y) {
						this._win.setPosition(this.windowState.x, this.windowState.y, false);
					}
				}
			} catch (err) {
				this.logService.warn(`Unexpected error fixing window position on windows with multiple windows: ${err}\n${err.stack}`);
			}
		}

213 214 215 216
		if (useCustomTitleStyle) {
			this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
		}

217
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
218
			this._win.maximize();
E
Erich Gamma 已提交
219

B
Benjamin Pasero 已提交
220
			if (this.windowState.mode === WindowMode.Fullscreen) {
B
Benjamin Pasero 已提交
221
				this._win.setFullScreen(true);
222 223
			}

B
Benjamin Pasero 已提交
224 225
			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 已提交
226 227 228
			}
		}

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

232
	public hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
233
		return this.hiddenTitleBarStyle;
234 235
	}

236
	public get isExtensionDevelopmentHost(): boolean {
237
		return !!this.config.extensionDevelopmentPath;
238 239
	}

240
	public get isExtensionTestHost(): boolean {
241
		return !!this.config.extensionTestsPath;
242 243
	}

244
	public get extensionDevelopmentPath(): string {
245
		return this.config.extensionDevelopmentPath;
E
Erich Gamma 已提交
246 247 248 249 250 251
	}

	public get config(): IWindowConfiguration {
		return this.currentConfig;
	}

B
Benjamin Pasero 已提交
252 253 254 255
	public get id(): number {
		return this._id;
	}

256
	public get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
257 258 259
		return this._win;
	}

260
	public setRepresentedFilename(filename: string): void {
261
		if (isMacintosh) {
262 263 264 265 266 267 268
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

	public getRepresentedFilename(): string {
269
		if (isMacintosh) {
270 271 272 273 274 275
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
276
	public focus(): void {
E
Erich Gamma 已提交
277 278 279 280
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
281 282
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
283 284
		}

B
Benjamin Pasero 已提交
285
		this._win.focus();
E
Erich Gamma 已提交
286 287 288 289 290 291
	}

	public get lastFocusTime(): number {
		return this._lastFocusTime;
	}

292 293
	public get backupPath(): string {
		return this.currentConfig ? this.currentConfig.backupPath : void 0;
E
Erich Gamma 已提交
294 295
	}

296
	public get openedWorkspace(): IWorkspaceIdentifier {
297
		return this.currentConfig ? this.currentConfig.workspace : void 0;
298 299
	}

300 301 302 303
	public get openedFolderPath(): string {
		return this.currentConfig ? this.currentConfig.folderPath : void 0;
	}

E
Erich Gamma 已提交
304 305 306 307 308 309 310 311 312
	public setReady(): void {
		this._readyState = ReadyState.READY;

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

B
Benjamin Pasero 已提交
313 314
	public ready(): TPromise<ICodeWindow> {
		return new TPromise<ICodeWindow>((c) => {
E
Erich Gamma 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327
			if (this._readyState === ReadyState.READY) {
				return c(this);
			}

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

	public get readyState(): ReadyState {
		return this._readyState;
	}

328 329 330 331
	private handleMarketplaceRequests(): void {

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

333 334
		// Inject headers when requests are incoming
		const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
M
Matt Bierner 已提交
335
		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
336 337 338
			this.marketplaceHeadersPromise.done(headers => {
				cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
			});
339
		});
340 341 342
	}

	private registerListeners(): void {
343 344

		// Prevent loading of svgs
345
		this._win.webContents.session.webRequest.onBeforeRequest(null, (details, callback) => {
346 347 348 349 350 351 352 353 354 355
			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 已提交
356
		this._win.webContents.session.webRequest.onHeadersReceived(null, (details: any, callback: any) => {
357 358 359 360 361 362 363 364
			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 已提交
365 366 367 368 369 370 371 372
		// 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 已提交
373
				this.pendingLoadConfig = null;
E
Erich Gamma 已提交
374 375
			}

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

B
Benjamin Pasero 已提交
382 383
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
384 385 386 387 388
				}
			}
		});

		// App commands support
389
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
390 391 392 393 394

		// Handle code that wants to open links
		this._win.webContents.on('new-window', (event: Event, url: string) => {
			event.preventDefault();

B
Benjamin Pasero 已提交
395
			shell.openExternal(url);
E
Erich Gamma 已提交
396 397 398 399
		});

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

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

409 410 411 412
			app.emit('browser-window-maximize', e, this._win);
		});

		this._win.on('unmaximize', (e) => {
B
Benjamin Pasero 已提交
413 414 415 416
			if (this.currentConfig) {
				this.currentConfig.maximized = false;
			}

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

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

		// Prevent any kind of navigation triggered by the user!
		// But do not touch this in dev version because it will prevent "Reload" from dev tools
436
		if (this.environmentService.isBuilt) {
E
Erich Gamma 已提交
437 438 439 440 441 442
			this._win.webContents.on('will-navigate', (event: Event) => {
				if (event) {
					event.preventDefault();
				}
			});
		}
B
Benjamin Pasero 已提交
443 444

		// Handle configuration changes
445
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
446

447
		// Handle Workspace events
B
Benjamin Pasero 已提交
448
		this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476

		// 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());
			}
		}
477 478
	}

479
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
480 481 482

		// Make sure to update our workspace config if we detect that it
		// was deleted
483
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
484
			this.currentConfig.workspace = void 0;
485
		}
E
Erich Gamma 已提交
486 487
	}

B
Benjamin Pasero 已提交
488
	private onConfigurationUpdated(): void {
489
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
490 491 492 493
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
494 495

		// Swipe command support (macOS)
496
		if (isMacintosh) {
497
			const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
B
Benjamin Pasero 已提交
498
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
499
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
500 501 502
			} else {
				this._win.removeAllListeners('swipe');
			}
503
		}
504
	}
B
Benjamin Pasero 已提交
505

B
Benjamin Pasero 已提交
506
	private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
507
		this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
B
Benjamin Pasero 已提交
508 509 510 511 512
			if (this.readyState !== ReadyState.READY) {
				return; // window must be ready
			}

			if (cmd === back) {
513
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
514
			} else if (cmd === forward) {
515
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
516 517 518 519
			}
		});
	}

520
	public load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
E
Erich Gamma 已提交
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536

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

537 538 539 540 541 542 543
		// 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;
		}

544
		// Clear Document Edited if needed
545
		if (isMacintosh && this._win.isDocumentEdited()) {
B
Benjamin Pasero 已提交
546
			if (!isReload || !this.backupMainService.isHotExitEnabled()) {
547 548 549 550 551 552 553 554 555 556 557
				this._win.setDocumentEdited(false);
			}
		}

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

			this._win.setTitle(product.nameLong);
558 559
		}

E
Erich Gamma 已提交
560
		// Load URL
561
		mark('main:loadWindow');
562
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
563 564

		// Make window visible if it did not open in N seconds because this indicates an error
565 566
		// Only do this when running out of sources and not when running tests
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) {
E
Erich Gamma 已提交
567 568 569 570
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
571
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
572 573 574 575 576
				}
			}, 10000);
		}
	}

577
	public reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
E
Erich Gamma 已提交
578

579 580 581 582 583 584
		// 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 已提交
585 586
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
587
		delete configuration.filesToDiff;
588
		delete configuration.filesToWait;
589

E
Erich Gamma 已提交
590
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
591
		// in extension development mode. These options are all development related.
592
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
593
			configuration.verbose = cli.verbose;
594
			configuration.debugPluginHost = cli.debugPluginHost;
595
			configuration.debugBrkPluginHost = cli.debugBrkPluginHost;
596
			configuration.debugId = cli.debugId;
597
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
598 599
		}

600 601
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
602
		// Load config
603 604
		const disableExtensions = cli ? cli['disable-extensions'] : undefined;
		this.load(configuration, true, disableExtensions);
E
Erich Gamma 已提交
605 606
	}

607
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
608

609 610
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
611
		windowConfiguration.logLevel = this.logService.getLevel();
612

613
		// Set zoomlevel
614
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
615
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
616 617 618 619
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

620 621 622
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

623
		// Set Accessibility Config
624 625 626 627 628
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
629 630
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

631 632
		// Theme
		windowConfiguration.baseTheme = this.getBaseTheme();
633
		windowConfiguration.backgroundColor = this.getBackgroundColor();
M
Martin Aeschlimann 已提交
634

B
Benjamin Pasero 已提交
635 636
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
637
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
638

639
		// Perf Counters
640
		windowConfiguration.perfEntries = exportEntries();
641
		windowConfiguration.perfStartTime = (<any>global).perfStartTime;
642
		windowConfiguration.perfWindowLoadTime = Date.now();
643

644 645 646
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
647
		for (let key in config) {
S
Sandeep Somavarapu 已提交
648
			if (config[key] === void 0 || config[key] === null || config[key] === '') {
649 650 651
				delete config[key]; // only send over properties that have a true value
			}
		}
652

B
Benjamin Pasero 已提交
653
		return `${require.toUrl('vs/workbench/electron-browser/bootstrap/index.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
E
Erich Gamma 已提交
654 655
	}

656
	private getBaseTheme(): string {
657
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
658 659
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
660

B
Benjamin Pasero 已提交
661
		const theme = this.stateService.getItem<string>(CodeWindow.themeStorageKey, 'vs-dark');
B
Benjamin Pasero 已提交
662

663 664 665
		return theme.split(' ')[0];
	}

666
	private getBackgroundColor(): string {
667
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
B
Benjamin Pasero 已提交
668
			return CodeWindow.DEFAULT_BG_HC_BLACK;
669 670
		}

B
Benjamin Pasero 已提交
671
		const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
672
		if (!background) {
B
Benjamin Pasero 已提交
673 674
			const baseTheme = this.getBaseTheme();

B
Benjamin Pasero 已提交
675
			return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
676 677 678 679 680
		}

		return background;
	}

E
Erich Gamma 已提交
681
	public serializeWindowState(): IWindowState {
682 683 684
		if (!this._win) {
			return defaultWindowState();
		}
685 686

		// fullscreen gets special treatment
B
Benjamin Pasero 已提交
687
		if (this._win.isFullScreen()) {
688 689
			const display = screen.getDisplayMatching(this.getBounds());

690
			return {
691
				mode: WindowMode.Fullscreen,
692 693
				display: display ? display.id : void 0,

694 695 696 697 698
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
699
			};
E
Erich Gamma 已提交
700 701
		}

702
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
703 704 705
		let mode: WindowMode;

		// get window mode
706
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
707 708 709 710 711 712 713 714
			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;
715
		} else {
E
Erich Gamma 已提交
716 717 718 719 720
			state.mode = WindowMode.Normal;
		}

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

723 724 725 726
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
727 728 729 730 731
		}

		return state;
	}

B
Benjamin Pasero 已提交
732
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
733 734 735 736
		if (state) {
			try {
				state = this.validateWindowState(state);
			} catch (err) {
J
Joao Moreno 已提交
737
				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 已提交
738 739 740 741 742 743 744
			}
		}

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

B
Benjamin Pasero 已提交
745
		return state;
E
Erich Gamma 已提交
746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
	}

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

761
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
762 763 764

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
765
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800

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

801 802 803 804 805 806 807 808 809 810 811 812 813
		// 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
814 815
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
U
unknown 已提交
816 817 818 819 820 821 822
		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 已提交
823
			if (state.mode === WindowMode.Maximized) {
824
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
825 826 827 828 829 830 831 832 833 834 835 836
				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 已提交
837
	public getBounds(): Electron.Rectangle {
B
Benjamin Pasero 已提交
838 839
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
840 841 842 843 844

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

	public toggleFullScreen(): void {
B
Benjamin Pasero 已提交
845
		const willBeFullScreen = !this._win.isFullScreen();
E
Erich Gamma 已提交
846

B
Benjamin Pasero 已提交
847
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
848
		this._win.setFullScreen(willBeFullScreen);
849

850
		// respect configured menu bar visibility or default to toggle if not set
851
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
852 853
	}

854
	private getMenuBarVisibility(): MenuBarVisibility {
855
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
856
		if (!windowConfig || !windowConfig.menuBarVisibility) {
857
			return 'default';
B
Benjamin Pasero 已提交
858 859 860 861
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
862
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
863 864 865
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
866 867
	}

868
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
869
		if (isMacintosh) {
B
Benjamin Pasero 已提交
870 871 872
			return; // ignore for macOS platform
		}

873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893
		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 {
B
Benjamin Pasero 已提交
894
		const isFullscreen = this._win.isFullScreen();
895

896
		switch (visibility) {
897
			case ('default'):
B
Benjamin Pasero 已提交
898 899
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
900 901
				break;

D
David Terry 已提交
902
			case ('visible'):
B
Benjamin Pasero 已提交
903 904
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
905
				break;
D
David Terry 已提交
906 907

			case ('toggle'):
B
Benjamin Pasero 已提交
908 909
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
910
				break;
D
David Terry 已提交
911 912

			case ('hidden'):
913 914
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(false);
915
				break;
916
		}
917 918
	}

919 920 921
	public onWindowTitleDoubleClick(): void {

		// Respect system settings on mac with regards to title click on windows title
922
		if (isMacintosh) {
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945
			const action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
			switch (action) {
				case 'Minimize':
					this.win.minimize();
					break;
				case 'None':
					break;
				case 'Maximize':
				default:
					this.win.maximize();
			}
		}

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

946 947 948 949 950 951
	public close(): void {
		if (this._win) {
			this._win.close();
		}
	}

952 953 954 955 956 957 958
	public sendWhenReady(channel: string, ...args: any[]): void {
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

	public send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
959 960 961
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
962 963
	}

B
Benjamin Pasero 已提交
964
	public updateTouchBar(groups: ISerializableCommandAction[][]): void {
965 966 967 968
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
969 970 971 972 973
		// 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);
974
		});
B
Benjamin Pasero 已提交
975
	}
976

B
Benjamin Pasero 已提交
977 978 979 980
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
981

B
Benjamin Pasero 已提交
982 983 984 985 986 987 988
		// 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);
		}
989

B
Benjamin Pasero 已提交
990 991 992 993 994
		// Ugly workaround for native crash on macOS 10.12.1. We are not
		// leveraging the API for changing the ESC touch bar item.
		// See https://github.com/electron/electron/issues/10442
		(<any>this._win)._setEscapeTouchBarItem = () => { };

B
Benjamin Pasero 已提交
995
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
996
	}
997

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

B
Benjamin Pasero 已提交
1000 1001
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
1002

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

B
Benjamin Pasero 已提交
1013 1014 1015
		return control;
	}

B
Benjamin Pasero 已提交
1016
	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
B
Benjamin Pasero 已提交
1017
		const segments: ITouchBarSegment[] = items.map(item => {
1018
			let icon: Electron.NativeImage;
1019
			if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
B
Benjamin Pasero 已提交
1020
				icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
1021 1022 1023
				if (icon.isEmpty()) {
					icon = void 0;
				}
1024
			}
1025 1026

			return {
B
Benjamin Pasero 已提交
1027
				id: item.id,
1028 1029 1030
				label: !icon ? item.title as string : void 0,
				icon
			};
1031 1032
		});

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

E
Erich Gamma 已提交
1036 1037 1038 1039 1040
	public dispose(): void {
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1041 1042
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
1043 1044
		this._win = null; // Important to dereference the window object to allow for GC
	}
1045
}