window.ts 32.5 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: {
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 181
		} else {
			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 380 381

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

B
Benjamin Pasero 已提交
382
			shell.openExternal(url);
E
Erich Gamma 已提交
383 384 385 386
		});

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

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

396 397 398 399
			app.emit('browser-window-maximize', e, this._win);
		});

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

404 405
			app.emit('browser-window-unmaximize', e, this._win);
		});
S
SteVen Batten 已提交
406

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

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

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

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

		// 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());
			}
		}
464 465
	}

466
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
467 468 469

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

B
Benjamin Pasero 已提交
475
	private onConfigurationUpdated(): void {
476
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
477 478 479 480
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
481 482

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

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

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

507 508 509 510 511 512
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

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

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

530 531 532 533 534 535 536
		// 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;
		}

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

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

			this._win.setTitle(product.nameLong);
551 552
		}

E
Erich Gamma 已提交
553
		// Load URL
554
		perf.mark('main:loadWindow');
555
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
556 557

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

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

572 573 574 575 576 577
		// 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 已提交
578 579
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
580
		delete configuration.filesToDiff;
581
		delete configuration.filesToWait;
582

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

593 594
		configuration.isInitialStartup = false; // since this is a reload

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

600
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
601

602 603
		// Set window ID
		windowConfiguration.windowId = this._win.id;
S
Sandeep Somavarapu 已提交
604
		windowConfiguration.logLevel = this.logService.getLevel();
605

606
		// Set zoomlevel
607
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
608
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
609 610 611 612
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

613 614 615
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

616
		// Set Accessibility Config
617 618 619 620 621
		let autoDetectHighContrast = true;
		if (windowConfig && windowConfig.autoDetectHighContrast === false) {
			autoDetectHighContrast = false;
		}
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
622 623
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

B
Benjamin Pasero 已提交
624 625
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
626
		windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh;
M
Martin Aeschlimann 已提交
627

628 629
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
630

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

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

643
	private getBaseTheme(): string {
644
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
645 646
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
647

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

650 651 652
		return theme.split(' ')[0];
	}

653
	private getBackgroundColor(): string {
654
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
B
Benjamin Pasero 已提交
655
			return CodeWindow.DEFAULT_BG_HC_BLACK;
656 657
		}

B
Benjamin Pasero 已提交
658
		const background = this.stateService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
659
		if (!background) {
B
Benjamin Pasero 已提交
660 661
			const baseTheme = this.getBaseTheme();

B
Benjamin Pasero 已提交
662
			return baseTheme === 'hc-black' ? CodeWindow.DEFAULT_BG_HC_BLACK : (baseTheme === 'vs' ? CodeWindow.DEFAULT_BG_LIGHT : CodeWindow.DEFAULT_BG_DARK);
663 664 665 666 667
		}

		return background;
	}

B
Benjamin Pasero 已提交
668
	serializeWindowState(): IWindowState {
669 670 671
		if (!this._win) {
			return defaultWindowState();
		}
672 673

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

677
			return {
678
				mode: WindowMode.Fullscreen,
679 680
				display: display ? display.id : void 0,

681 682 683 684 685
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
686
			};
E
Erich Gamma 已提交
687 688
		}

689
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
690 691 692
		let mode: WindowMode;

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

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

710 711 712 713
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
714 715 716 717 718
		}

		return state;
	}

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

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

B
Benjamin Pasero 已提交
732
		return state;
E
Erich Gamma 已提交
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747
	}

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

748
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
749 750 751

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
752
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
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 784 785 786 787

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

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

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

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

B
Benjamin Pasero 已提交
834
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
835
		this._win.setFullScreen(willBeFullScreen);
836

837
		// respect configured menu bar visibility or default to toggle if not set
838
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
839 840
	}

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

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
849
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
850 851 852
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
853 854
	}

855
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
856
		if (isMacintosh) {
B
Benjamin Pasero 已提交
857 858 859
			return; // ignore for macOS platform
		}

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

883
		switch (visibility) {
884
			case ('default'):
B
Benjamin Pasero 已提交
885 886
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
887 888
				break;

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

			case ('toggle'):
B
Benjamin Pasero 已提交
895 896
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
897
				break;
D
David Terry 已提交
898 899

			case ('hidden'):
900 901
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(false);
902
				break;
903
		}
904 905
	}

B
Benjamin Pasero 已提交
906
	onWindowTitleDoubleClick(): void {
907 908

		// Respect system settings on mac with regards to title click on windows title
909
		if (isMacintosh) {
910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
			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 已提交
933
	close(): void {
934 935 936 937 938
		if (this._win) {
			this._win.close();
		}
	}

B
Benjamin Pasero 已提交
939
	sendWhenReady(channel: string, ...args: any[]): void {
940 941 942 943 944
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

B
Benjamin Pasero 已提交
945
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
946 947 948
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
949 950
	}

B
Benjamin Pasero 已提交
951
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
952 953 954 955
		if (!isMacintosh) {
			return; // only supported on macOS
		}

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

B
Benjamin Pasero 已提交
964 965 966 967
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
968

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

B
Benjamin Pasero 已提交
977
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
978
	}
979

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

B
Benjamin Pasero 已提交
982 983
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
984

B
Benjamin Pasero 已提交
985 986 987 988 989 990 991 992 993
		// 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' });
			}
		});
994

B
Benjamin Pasero 已提交
995 996 997
		return control;
	}

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

			return {
B
Benjamin Pasero 已提交
1009
				id: item.id,
1010 1011 1012
				label: !icon ? item.title as string : void 0,
				icon
			};
1013 1014
		});

B
Benjamin Pasero 已提交
1015
		return segments;
1016 1017
	}

B
Benjamin Pasero 已提交
1018
	dispose(): void {
E
Erich Gamma 已提交
1019 1020 1021 1022
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1023 1024
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
1025 1026
		this._win = null; // Important to dereference the window object to allow for GC
	}
1027
}