window.ts 32.1 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 {
181
			useCustomTitleStyle = windowConfig && windowConfig.titleBarStyle === 'custom'; // Must be specified on Windows/Linux
B
Benjamin Pasero 已提交
182 183
		}

184 185 186 187
		if (useNativeTabs) {
			useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
		}

188 189 190
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
R
Ryan Adolf 已提交
191
			if (!isMacintosh) {
R
Ryan Adolf 已提交
192 193
				options.frame = false;
			}
194 195
		}

E
Erich Gamma 已提交
196 197
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
198
		this._id = this._win.id;
E
Erich Gamma 已提交
199

200 201 202 203
		if (useCustomTitleStyle) {
			this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
		}

204
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
205
			this._win.maximize();
E
Erich Gamma 已提交
206

B
Benjamin Pasero 已提交
207
			if (this.windowState.mode === WindowMode.Fullscreen) {
B
Benjamin Pasero 已提交
208
				this._win.setFullScreen(true);
209 210
			}

B
Benjamin Pasero 已提交
211 212
			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 已提交
213 214 215
			}
		}

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

B
Benjamin Pasero 已提交
219
	hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
220
		return this.hiddenTitleBarStyle;
221 222
	}

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

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

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

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

B
Benjamin Pasero 已提交
239
	get id(): number {
B
Benjamin Pasero 已提交
240 241 242
		return this._id;
	}

B
Benjamin Pasero 已提交
243
	get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
244 245 246
		return this._win;
	}

B
Benjamin Pasero 已提交
247
	setRepresentedFilename(filename: string): void {
248
		if (isMacintosh) {
249 250 251 252 253 254
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

B
Benjamin Pasero 已提交
255
	getRepresentedFilename(): string {
256
		if (isMacintosh) {
257 258 259 260 261 262
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
263
	focus(): void {
E
Erich Gamma 已提交
264 265 266 267
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
268 269
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
270 271
		}

B
Benjamin Pasero 已提交
272
		this._win.focus();
E
Erich Gamma 已提交
273 274
	}

B
Benjamin Pasero 已提交
275
	get lastFocusTime(): number {
E
Erich Gamma 已提交
276 277 278
		return this._lastFocusTime;
	}

B
Benjamin Pasero 已提交
279
	get backupPath(): string {
280
		return this.currentConfig ? this.currentConfig.backupPath : void 0;
E
Erich Gamma 已提交
281 282
	}

B
Benjamin Pasero 已提交
283
	get openedWorkspace(): IWorkspaceIdentifier {
284
		return this.currentConfig ? this.currentConfig.workspace : void 0;
285 286
	}

287 288
	get openedFolderUri(): URI {
		return this.currentConfig ? this.currentConfig.folderUri : void 0;
289 290
	}

B
Benjamin Pasero 已提交
291
	setReady(): void {
E
Erich Gamma 已提交
292 293 294 295 296 297 298 299
		this._readyState = ReadyState.READY;

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

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

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

B
Benjamin Pasero 已提交
311
	get readyState(): ReadyState {
E
Erich Gamma 已提交
312 313 314
		return this._readyState;
	}

315 316 317 318
	private handleMarketplaceRequests(): void {

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

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

	private registerListeners(): void {
330 331

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

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

B
Benjamin Pasero 已提交
369 370
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
371 372 373 374 375
				}
			}
		});

		// App commands support
376
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
377 378 379

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

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

389 390 391 392
			app.emit('browser-window-maximize', e, this._win);
		});

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

397 398
			app.emit('browser-window-unmaximize', e, this._win);
		});
S
SteVen Batten 已提交
399

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

B
Benjamin Pasero 已提交
414
		// Handle configuration changes
415
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
416

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

		// 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());
			}
		}
447 448
	}

449
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
450 451 452

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

B
Benjamin Pasero 已提交
458
	private onConfigurationUpdated(): void {
459
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
460 461 462 463
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
464 465

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

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

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

490 491 492 493 494 495
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

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

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

513 514 515 516 517 518 519
		// 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;
		}

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

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

			this._win.setTitle(product.nameLong);
534 535
		}

E
Erich Gamma 已提交
536
		// Load URL
537
		perf.mark('main:loadWindow');
538
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
539 540

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

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

555 556 557 558 559 560
		// 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 已提交
561 562
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
563
		delete configuration.filesToDiff;
564
		delete configuration.filesToWait;
565

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

576 577
		configuration.isInitialStartup = false; // since this is a reload

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

583
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
584

585 586
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
587
		windowConfiguration.logLevel = this.logService.getLevel();
588

589
		// Set zoomlevel
590
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
591
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
592 593 594 595
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

596 597 598
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

599
		// Set Accessibility Config
600 601 602 603 604
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
605 606
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

B
Benjamin Pasero 已提交
607 608
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
609
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
610

611 612
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
613

614 615 616
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
617
		for (let key in config) {
S
Sandeep Somavarapu 已提交
618
			if (config[key] === void 0 || config[key] === null || config[key] === '') {
619 620 621
				delete config[key]; // only send over properties that have a true value
			}
		}
622

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

626
	private getBaseTheme(): string {
627
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
628 629
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
630

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

633 634 635
		return theme.split(' ')[0];
	}

636
	private getBackgroundColor(): string {
637
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
B
Benjamin Pasero 已提交
638
			return CodeWindow.DEFAULT_BG_HC_BLACK;
639 640
		}

B
Benjamin Pasero 已提交
641
		const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
642
		if (!background) {
B
Benjamin Pasero 已提交
643 644
			const baseTheme = this.getBaseTheme();

B
Benjamin Pasero 已提交
645
			return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
646 647 648 649 650
		}

		return background;
	}

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

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

660
			return {
661
				mode: WindowMode.Fullscreen,
662 663
				display: display ? display.id : void 0,

664 665 666 667 668
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
669
			};
E
Erich Gamma 已提交
670 671
		}

672
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
673 674 675
		let mode: WindowMode;

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

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

693 694 695 696
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
697 698 699 700 701
		}

		return state;
	}

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

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

B
Benjamin Pasero 已提交
715
		return state;
E
Erich Gamma 已提交
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730
	}

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

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

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

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

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

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

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

B
Benjamin Pasero 已提交
817
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
818
		this._win.setFullScreen(willBeFullScreen);
819

820
		// respect configured menu bar visibility or default to toggle if not set
821
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
822 823
	}

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

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
832
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
833 834 835
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
836 837
	}

838
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
839
		if (isMacintosh) {
B
Benjamin Pasero 已提交
840 841 842
			return; // ignore for macOS platform
		}

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

866
		switch (visibility) {
867
			case ('default'):
B
Benjamin Pasero 已提交
868 869
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
870 871
				break;

D
David Terry 已提交
872
			case ('visible'):
B
Benjamin Pasero 已提交
873 874
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
875
				break;
D
David Terry 已提交
876 877

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

			case ('hidden'):
883 884
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(false);
885
				break;
886
		}
887 888
	}

B
Benjamin Pasero 已提交
889
	onWindowTitleDoubleClick(): void {
890 891

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

B
Benjamin Pasero 已提交
922
	sendWhenReady(channel: string, ...args: any[]): void {
923 924 925 926 927
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

B
Benjamin Pasero 已提交
928
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
929 930 931
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
932 933
	}

B
Benjamin Pasero 已提交
934
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
935 936 937 938
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
939 940 941 942 943
		// 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);
944
		});
B
Benjamin Pasero 已提交
945
	}
946

B
Benjamin Pasero 已提交
947 948 949 950
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
951

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

B
Benjamin Pasero 已提交
960
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
961
	}
962

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

B
Benjamin Pasero 已提交
965 966
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
967

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

B
Benjamin Pasero 已提交
978 979 980
		return control;
	}

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

			return {
B
Benjamin Pasero 已提交
992
				id: item.id,
993 994 995
				label: !icon ? item.title as string : void 0,
				icon
			};
996 997
		});

B
Benjamin Pasero 已提交
998
		return segments;
999 1000
	}

B
Benjamin Pasero 已提交
1001
	dispose(): void {
E
Erich Gamma 已提交
1002 1003 1004 1005
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1006 1007
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
1008 1009
		this._win = null; // Important to dereference the window object to allow for GC
	}
1010
}