window.ts 32.3 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 * as perf 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

B
Benjamin Pasero 已提交
58 59
	static readonly themeStorageKey = 'theme';
	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

196 197 198 199
		if (useCustomTitleStyle) {
			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) {
B
Benjamin Pasero 已提交
204
				this._win.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 320 321
			this.marketplaceHeadersPromise.done(headers => {
				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 376 377

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

B
Benjamin Pasero 已提交
378
			shell.openExternal(url);
E
Erich Gamma 已提交
379 380 381 382
		});

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

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

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

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

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

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

		// 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
419
		if (this.environmentService.isBuilt) {
E
Erich Gamma 已提交
420 421 422 423 424 425
			this._win.webContents.on('will-navigate', (event: Event) => {
				if (event) {
					event.preventDefault();
				}
			});
		}
B
Benjamin Pasero 已提交
426 427

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

430
		// Handle Workspace events
B
Benjamin Pasero 已提交
431
		this.toDispose.push(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459

		// 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());
			}
		}
460 461
	}

462
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
463 464 465

		// Make sure to update our workspace config if we detect that it
		// was deleted
466
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
467
			this.currentConfig.workspace = void 0;
468
		}
E
Erich Gamma 已提交
469 470
	}

B
Benjamin Pasero 已提交
471
	private onConfigurationUpdated(): void {
472
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
473 474 475 476
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
477 478

		// Swipe command support (macOS)
479
		if (isMacintosh) {
480
			const config = this.configurationService.getValue<IWorkbenchEditorConfiguration>();
B
Benjamin Pasero 已提交
481
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
482
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
483 484 485
			} else {
				this._win.removeAllListeners('swipe');
			}
486
		}
487
	}
B
Benjamin Pasero 已提交
488

B
Benjamin Pasero 已提交
489
	private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
490
		this._win.on(command as 'swipe' /* | 'app-command' */, (e: Electron.Event, cmd: string) => {
B
Benjamin Pasero 已提交
491 492 493 494 495
			if (this.readyState !== ReadyState.READY) {
				return; // window must be ready
			}

			if (cmd === back) {
496
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
497
			} else if (cmd === forward) {
498
				this.send('vscode:runAction', { id: acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward', from: 'mouse' } as IRunActionInWindowRequest);
B
Benjamin Pasero 已提交
499 500 501 502
			}
		});
	}

503 504 505 506 507 508
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

B
Benjamin Pasero 已提交
509
	load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
E
Erich Gamma 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525

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

526 527 528 529 530 531 532
		// 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;
		}

533
		// Clear Document Edited if needed
534
		if (isMacintosh && this._win.isDocumentEdited()) {
B
Benjamin Pasero 已提交
535
			if (!isReload || !this.backupMainService.isHotExitEnabled()) {
536 537 538 539 540 541 542 543 544 545 546
				this._win.setDocumentEdited(false);
			}
		}

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

			this._win.setTitle(product.nameLong);
547 548
		}

E
Erich Gamma 已提交
549
		// Load URL
550
		perf.mark('main:loadWindow');
551
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
552 553

		// Make window visible if it did not open in N seconds because this indicates an error
554 555
		// Only do this when running out of sources and not when running tests
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) {
E
Erich Gamma 已提交
556 557 558 559
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
560
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
561 562 563 564 565
				}
			}, 10000);
		}
	}

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

568 569 570 571 572 573
		// 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 已提交
574 575
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
576
		delete configuration.filesToDiff;
577
		delete configuration.filesToWait;
578

E
Erich Gamma 已提交
579
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
580
		// in extension development mode. These options are all development related.
581
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
582
			configuration.verbose = cli.verbose;
583
			configuration.debugPluginHost = cli.debugPluginHost;
584
			configuration.debugBrkPluginHost = cli.debugBrkPluginHost;
585
			configuration.debugId = cli.debugId;
586
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
587 588
		}

589 590
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
591
		// Load config
592 593
		const disableExtensions = cli ? cli['disable-extensions'] : undefined;
		this.load(configuration, true, disableExtensions);
E
Erich Gamma 已提交
594 595
	}

596
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
597

598 599
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
600
		windowConfiguration.logLevel = this.logService.getLevel();
601

602
		// Set zoomlevel
603
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
604
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
605 606 607 608
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

609 610 611
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

612
		// Set Accessibility Config
613 614 615 616 617
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
618 619
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

B
Benjamin Pasero 已提交
620 621
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
622
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
623

624 625
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
626

627 628 629
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
630
		for (let key in config) {
S
Sandeep Somavarapu 已提交
631
			if (config[key] === void 0 || config[key] === null || config[key] === '') {
632 633 634
				delete config[key]; // only send over properties that have a true value
			}
		}
635

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

639
	private getBaseTheme(): string {
640
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
641 642
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
643

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

646 647 648
		return theme.split(' ')[0];
	}

649
	private getBackgroundColor(): string {
650
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
B
Benjamin Pasero 已提交
651
			return CodeWindow.DEFAULT_BG_HC_BLACK;
652 653
		}

B
Benjamin Pasero 已提交
654
		const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
655
		if (!background) {
B
Benjamin Pasero 已提交
656 657
			const baseTheme = this.getBaseTheme();

B
Benjamin Pasero 已提交
658
			return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
659 660 661 662 663
		}

		return background;
	}

B
Benjamin Pasero 已提交
664
	serializeWindowState(): IWindowState {
665 666 667
		if (!this._win) {
			return defaultWindowState();
		}
668 669

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

673
			return {
674
				mode: WindowMode.Fullscreen,
675 676
				display: display ? display.id : void 0,

677 678 679 680 681
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
682
			};
E
Erich Gamma 已提交
683 684
		}

685
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
686 687 688
		let mode: WindowMode;

		// get window mode
689
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
690 691 692 693 694 695 696 697
			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;
698
		} else {
E
Erich Gamma 已提交
699 700 701 702 703
			state.mode = WindowMode.Normal;
		}

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

706 707 708 709
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
710 711 712 713 714
		}

		return state;
	}

B
Benjamin Pasero 已提交
715
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
716 717 718 719
		if (state) {
			try {
				state = this.validateWindowState(state);
			} catch (err) {
J
Joao Moreno 已提交
720
				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 已提交
721 722 723 724 725 726 727
			}
		}

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

B
Benjamin Pasero 已提交
728
		return state;
E
Erich Gamma 已提交
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
	}

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

744
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
745 746 747

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
748
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
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 777 778 779 780 781 782 783

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

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

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

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

B
Benjamin Pasero 已提交
830
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
831
		this._win.setFullScreen(willBeFullScreen);
832

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

837
	private getMenuBarVisibility(): MenuBarVisibility {
838
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
839
		if (!windowConfig || !windowConfig.menuBarVisibility) {
840
			return 'default';
B
Benjamin Pasero 已提交
841 842 843 844
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
845
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
846 847 848
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
849 850
	}

851
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
852
		if (isMacintosh) {
B
Benjamin Pasero 已提交
853 854 855
			return; // ignore for macOS platform
		}

856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876
		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 已提交
877
		const isFullscreen = this._win.isFullScreen();
878

879
		switch (visibility) {
880
			case ('default'):
B
Benjamin Pasero 已提交
881 882
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
883 884
				break;

D
David Terry 已提交
885
			case ('visible'):
B
Benjamin Pasero 已提交
886 887
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
888
				break;
D
David Terry 已提交
889 890

			case ('toggle'):
B
Benjamin Pasero 已提交
891 892
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
893
				break;
D
David Terry 已提交
894 895

			case ('hidden'):
896 897
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(false);
898
				break;
899
		}
900 901
	}

B
Benjamin Pasero 已提交
902
	onWindowTitleDoubleClick(): void {
903 904

		// Respect system settings on mac with regards to title click on windows title
905
		if (isMacintosh) {
906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928
			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();
			}
		}
	}

B
Benjamin Pasero 已提交
929
	close(): void {
930 931 932 933 934
		if (this._win) {
			this._win.close();
		}
	}

B
Benjamin Pasero 已提交
935
	sendWhenReady(channel: string, ...args: any[]): void {
936 937 938 939 940
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

B
Benjamin Pasero 已提交
941
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
942 943 944
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
945 946
	}

B
Benjamin Pasero 已提交
947
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
948 949 950 951
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
952 953 954 955 956
		// 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);
957
		});
B
Benjamin Pasero 已提交
958
	}
959

B
Benjamin Pasero 已提交
960 961 962 963
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
964

B
Benjamin Pasero 已提交
965 966 967 968 969 970 971
		// 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);
		}
972

B
Benjamin Pasero 已提交
973
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
974
	}
975

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

B
Benjamin Pasero 已提交
978 979
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
980

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

B
Benjamin Pasero 已提交
991 992 993
		return control;
	}

B
Benjamin Pasero 已提交
994
	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
B
Benjamin Pasero 已提交
995
		const segments: ITouchBarSegment[] = items.map(item => {
996
			let icon: Electron.NativeImage;
997
			if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
B
Benjamin Pasero 已提交
998
				icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
999 1000 1001
				if (icon.isEmpty()) {
					icon = void 0;
				}
1002
			}
1003 1004

			return {
B
Benjamin Pasero 已提交
1005
				id: item.id,
1006 1007 1008
				label: !icon ? item.title as string : void 0,
				icon
			};
1009 1010
		});

B
Benjamin Pasero 已提交
1011
		return segments;
1012 1013
	}

B
Benjamin Pasero 已提交
1014
	dispose(): void {
E
Erich Gamma 已提交
1015 1016 1017 1018
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1019 1020
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
1021 1022
		this._win = null; // Important to dereference the window object to allow for GC
	}
1023
}