window.ts 32.2 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 { 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: {
B
Benjamin Pasero 已提交
143 144 145 146 147
				// 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
				'backgroundThrottling': false,
148
				disableBlinkFeatures: 'Auxclick' // disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
149
			}
E
Erich Gamma 已提交
150 151
		};

152
		if (isLinux) {
153
			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 已提交
154 155
		}

156
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
157

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

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

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

172
		let useCustomTitleStyle = false;
S
SteVen Batten 已提交
173
		if (isMacintosh) {
S
SteVen Batten 已提交
174
			useCustomTitleStyle = !windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom'; // Default to custom on macOS
S
SteVen Batten 已提交
175

176
			const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
S
SteVen Batten 已提交
177 178
			if (isDev) {
				useCustomTitleStyle = false; // not enabled when developing due to https://github.com/electron/electron/issues/3647
B
Benjamin Pasero 已提交
179
			}
S
SteVen Batten 已提交
180
		} else {
B
Benjamin Pasero 已提交
181 182 183
			if (isLinux) {
				useCustomTitleStyle = windowConfig && windowConfig.titleBarStyle === 'custom';
			} else {
184
				useCustomTitleStyle = !windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom'; // Default to custom on Windows
B
Benjamin Pasero 已提交
185
			}
B
Benjamin Pasero 已提交
186 187
		}

188 189 190 191
		if (useNativeTabs) {
			useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
		}

192 193 194
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
R
Ryan Adolf 已提交
195
			if (!isMacintosh) {
R
Ryan Adolf 已提交
196 197
				options.frame = false;
			}
198 199
		}

E
Erich Gamma 已提交
200 201
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
202
		this._id = this._win.id;
E
Erich Gamma 已提交
203

204 205 206 207
		if (useCustomTitleStyle) {
			this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
		}

208
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
209
			this._win.maximize();
E
Erich Gamma 已提交
210

B
Benjamin Pasero 已提交
211
			if (this.windowState.mode === WindowMode.Fullscreen) {
B
Benjamin Pasero 已提交
212
				this._win.setFullScreen(true);
213 214
			}

B
Benjamin Pasero 已提交
215 216
			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 已提交
217 218 219
			}
		}

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

B
Benjamin Pasero 已提交
223
	hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
224
		return this.hiddenTitleBarStyle;
225 226
	}

B
Benjamin Pasero 已提交
227
	get isExtensionDevelopmentHost(): boolean {
228
		return !!this.config.extensionDevelopmentPath;
229 230
	}

B
Benjamin Pasero 已提交
231
	get isExtensionTestHost(): boolean {
232
		return !!this.config.extensionTestsPath;
233 234
	}

B
Benjamin Pasero 已提交
235
	get extensionDevelopmentPath(): string {
236
		return this.config.extensionDevelopmentPath;
E
Erich Gamma 已提交
237 238
	}

B
Benjamin Pasero 已提交
239
	get config(): IWindowConfiguration {
E
Erich Gamma 已提交
240 241 242
		return this.currentConfig;
	}

B
Benjamin Pasero 已提交
243
	get id(): number {
B
Benjamin Pasero 已提交
244 245 246
		return this._id;
	}

B
Benjamin Pasero 已提交
247
	get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
248 249 250
		return this._win;
	}

B
Benjamin Pasero 已提交
251
	setRepresentedFilename(filename: string): void {
252
		if (isMacintosh) {
253 254 255 256 257 258
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

B
Benjamin Pasero 已提交
259
	getRepresentedFilename(): string {
260
		if (isMacintosh) {
261 262 263 264 265 266
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
267
	focus(): void {
E
Erich Gamma 已提交
268 269 270 271
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
272 273
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
274 275
		}

B
Benjamin Pasero 已提交
276
		this._win.focus();
E
Erich Gamma 已提交
277 278
	}

B
Benjamin Pasero 已提交
279
	get lastFocusTime(): number {
E
Erich Gamma 已提交
280 281 282
		return this._lastFocusTime;
	}

B
Benjamin Pasero 已提交
283
	get backupPath(): string {
284
		return this.currentConfig ? this.currentConfig.backupPath : void 0;
E
Erich Gamma 已提交
285 286
	}

B
Benjamin Pasero 已提交
287
	get openedWorkspace(): IWorkspaceIdentifier {
288
		return this.currentConfig ? this.currentConfig.workspace : void 0;
289 290
	}

291 292
	get openedFolderUri(): URI {
		return this.currentConfig ? this.currentConfig.folderUri : void 0;
293 294
	}

B
Benjamin Pasero 已提交
295
	setReady(): void {
E
Erich Gamma 已提交
296 297 298 299 300 301 302 303
		this._readyState = ReadyState.READY;

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

B
Benjamin Pasero 已提交
304
	ready(): TPromise<ICodeWindow> {
B
Benjamin Pasero 已提交
305
		return new TPromise<ICodeWindow>((c) => {
E
Erich Gamma 已提交
306 307 308 309 310 311 312 313 314
			if (this._readyState === ReadyState.READY) {
				return c(this);
			}

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

B
Benjamin Pasero 已提交
315
	get readyState(): ReadyState {
E
Erich Gamma 已提交
316 317 318
		return this._readyState;
	}

319 320 321 322
	private handleMarketplaceRequests(): void {

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

324 325
		// Inject headers when requests are incoming
		const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
M
Matt Bierner 已提交
326
		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => {
327 328 329
			this.marketplaceHeadersPromise.done(headers => {
				cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
			});
330
		});
331 332 333
	}

	private registerListeners(): void {
334 335

		// Prevent loading of svgs
336
		this._win.webContents.session.webRequest.onBeforeRequest(null, (details, callback) => {
337 338 339 340 341 342 343 344 345 346
			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 已提交
347
		this._win.webContents.session.webRequest.onHeadersReceived(null, (details: any, callback: any) => {
348 349 350 351 352 353 354 355
			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 已提交
356 357 358 359 360 361 362 363
		// 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 已提交
364
				this.pendingLoadConfig = null;
E
Erich Gamma 已提交
365 366
			}

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

B
Benjamin Pasero 已提交
373 374
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
375 376 377 378 379
				}
			}
		});

		// App commands support
380
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
381 382 383

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

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 601 602
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

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) {
S
Sandeep Somavarapu 已提交
622
			if (config[key] === void 0 || config[key] === null || config[key] === '') {
623 624 625
				delete config[key]; // only send over properties that have a true value
			}
		}
626

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

630
	private getBaseTheme(): string {
631
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
632 633
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
634

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

637 638 639
		return theme.split(' ')[0];
	}

640
	private getBackgroundColor(): string {
641
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
B
Benjamin Pasero 已提交
642
			return CodeWindow.DEFAULT_BG_HC_BLACK;
643 644
		}

B
Benjamin Pasero 已提交
645
		const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
646
		if (!background) {
B
Benjamin Pasero 已提交
647 648
			const baseTheme = this.getBaseTheme();

B
Benjamin Pasero 已提交
649
			return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
650 651 652 653 654
		}

		return background;
	}

B
Benjamin Pasero 已提交
655
	serializeWindowState(): IWindowState {
656 657 658
		if (!this._win) {
			return defaultWindowState();
		}
659 660

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

664
			return {
665
				mode: WindowMode.Fullscreen,
666 667
				display: display ? display.id : void 0,

668 669 670 671 672
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
673
			};
E
Erich Gamma 已提交
674 675
		}

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

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

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

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

		return state;
	}

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

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

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

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
821
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
822
		this._win.setFullScreen(willBeFullScreen);
823

824
		// respect configured menu bar visibility or default to toggle if not set
825
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
826 827
	}

828
	private getMenuBarVisibility(): MenuBarVisibility {
829
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
830
		if (!windowConfig || !windowConfig.menuBarVisibility) {
831
			return 'default';
B
Benjamin Pasero 已提交
832 833 834 835
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
836
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
837 838 839
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
840 841
	}

842
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
843
		if (isMacintosh) {
B
Benjamin Pasero 已提交
844 845 846
			return; // ignore for macOS platform
		}

847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
		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 已提交
868
		const isFullscreen = this._win.isFullScreen();
869

870
		switch (visibility) {
871
			case ('default'):
B
Benjamin Pasero 已提交
872 873
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
874 875
				break;

D
David Terry 已提交
876
			case ('visible'):
B
Benjamin Pasero 已提交
877 878
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
879
				break;
D
David Terry 已提交
880 881

			case ('toggle'):
B
Benjamin Pasero 已提交
882 883
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
884
				break;
D
David Terry 已提交
885 886

			case ('hidden'):
887 888
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(false);
889
				break;
890
		}
891 892
	}

B
Benjamin Pasero 已提交
893
	onWindowTitleDoubleClick(): void {
894 895

		// Respect system settings on mac with regards to title click on windows title
896
		if (isMacintosh) {
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
			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 已提交
920
	close(): void {
921 922 923 924 925
		if (this._win) {
			this._win.close();
		}
	}

B
Benjamin Pasero 已提交
926
	sendWhenReady(channel: string, ...args: any[]): void {
927 928 929 930 931
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

B
Benjamin Pasero 已提交
932
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
933 934 935
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
936 937
	}

B
Benjamin Pasero 已提交
938
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
939 940 941 942
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
943 944 945 946 947
		// 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);
948
		});
B
Benjamin Pasero 已提交
949
	}
950

B
Benjamin Pasero 已提交
951 952 953 954
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
955

B
Benjamin Pasero 已提交
956 957 958 959 960 961 962
		// 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);
		}
963

B
Benjamin Pasero 已提交
964
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
965
	}
966

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

B
Benjamin Pasero 已提交
969 970
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
971

B
Benjamin Pasero 已提交
972 973 974 975 976 977 978 979 980
		// 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' });
			}
		});
981

B
Benjamin Pasero 已提交
982 983 984
		return control;
	}

B
Benjamin Pasero 已提交
985
	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
B
Benjamin Pasero 已提交
986
		const segments: ITouchBarSegment[] = items.map(item => {
987
			let icon: Electron.NativeImage;
988
			if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
B
Benjamin Pasero 已提交
989
				icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
990 991 992
				if (icon.isEmpty()) {
					icon = void 0;
				}
993
			}
994 995

			return {
B
Benjamin Pasero 已提交
996
				id: item.id,
997 998 999
				label: !icon ? item.title as string : void 0,
				icon
			};
1000 1001
		});

B
Benjamin Pasero 已提交
1002
		return segments;
1003 1004
	}

B
Benjamin Pasero 已提交
1005
	dispose(): void {
E
Erich Gamma 已提交
1006 1007 1008 1009
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1010 1011
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
1012 1013
		this._win = null; // Important to dereference the window object to allow for GC
	}
1014
}