window.ts 26.8 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 { stopProfiling } from 'vs/base/node/profiler';
11
import nls = require('vs/nls');
12
import { IStorageService } from 'vs/platform/storage/node/storage';
13
import { shell, screen, BrowserWindow, systemPreferences, app } 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';
18
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
19
import { parseArgs } from 'vs/platform/environment/node/argv';
20
import product from 'vs/platform/node/product';
21
import { getCommonHTTPHeaders } from 'vs/platform/environment/node/http';
22
import { IWindowSettings, MenuBarVisibility, ICodeWindow, ReadyState, IWindowCloseEvent } from 'vs/platform/windows/common/windows';
23
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
24
import { KeyboardLayoutMonitor } from 'vs/code/node/keyboard';
25
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from "vs/base/common/platform";
26
import CommonEvent, { Emitter } from "vs/base/common/event";
27

E
Erich Gamma 已提交
28 29 30 31 32 33
export interface IWindowState {
	width?: number;
	height?: number;
	x?: number;
	y?: number;
	mode?: WindowMode;
34
	display?: number;
E
Erich Gamma 已提交
35 36
}

37 38
export interface IWindowCreationOptions {
	state: IWindowState;
39
	extensionDevelopmentPath?: string;
40
	isExtensionTestHost?: boolean;
41 42
}

E
Erich Gamma 已提交
43 44 45
export enum WindowMode {
	Maximized,
	Normal,
46
	Minimized, // not used anymore, but also cannot remove due to existing stored UI state (needs migration)
47
	Fullscreen
E
Erich Gamma 已提交
48 49
}

B
Benjamin Pasero 已提交
50
export const defaultWindowState = function (mode = WindowMode.Normal): IWindowState {
E
Erich Gamma 已提交
51 52 53
	return {
		width: 1024,
		height: 768,
54
		mode
E
Erich Gamma 已提交
55 56 57 58 59
	};
};

export interface IPath {

B
Benjamin Pasero 已提交
60
	// the workspace spath for a Code instance which can be null
E
Erich Gamma 已提交
61 62
	workspacePath?: string;

B
Benjamin Pasero 已提交
63
	// the file path to open within a Code instance
E
Erich Gamma 已提交
64 65 66 67 68 69 70 71
	filePath?: string;

	// the line number in the file path to open
	lineNumber?: number;

	// the column number in the file path to open
	columnNumber?: number;

B
Benjamin Pasero 已提交
72
	// indicator to create the file path in the Code instance
E
Erich Gamma 已提交
73 74 75
	createFilePath?: boolean;
}

B
Benjamin Pasero 已提交
76
export interface IWindowConfiguration extends ParsedArgs {
77 78 79
	appRoot: string;
	execPath: string;

80
	userEnv: IProcessEnvironment;
B
Benjamin Pasero 已提交
81

A
Alex Dima 已提交
82
	isISOKeyboard?: boolean;
83

84
	zoomLevel?: number;
85
	fullscreen?: boolean;
86
	highContrast?: boolean;
M
Martin Aeschlimann 已提交
87
	baseTheme?: string;
88
	backgroundColor?: string;
89 90 91 92 93
	accessibilitySupport?: boolean;

	isInitialStartup?: boolean;

	perfStartTime?: number;
94
	perfAppReady?: number;
95
	perfWindowLoadTime?: number;
96

B
Benjamin Pasero 已提交
97
	workspacePath?: string;
98

E
Erich Gamma 已提交
99 100
	filesToOpen?: IPath[];
	filesToCreate?: IPath[];
101
	filesToDiff?: IPath[];
102 103

	nodeCachedDataDir: string;
E
Erich Gamma 已提交
104 105
}

B
Benjamin Pasero 已提交
106
export class CodeWindow implements ICodeWindow {
E
Erich Gamma 已提交
107

108
	public static themeStorageKey = 'theme';
109
	public static themeBackgroundStorageKey = 'themeBackground';
110

E
Erich Gamma 已提交
111 112 113
	private static MIN_WIDTH = 200;
	private static MIN_HEIGHT = 120;

114
	private _onClose: Emitter<IWindowCloseEvent>;
115
	private options: IWindowCreationOptions;
B
Benjamin Pasero 已提交
116
	private hiddenTitleBarStyle: boolean;
E
Erich Gamma 已提交
117
	private showTimeoutHandle: any;
B
Benjamin Pasero 已提交
118
	private _id: number;
119
	private _win: Electron.BrowserWindow;
E
Erich Gamma 已提交
120 121
	private _lastFocusTime: number;
	private _readyState: ReadyState;
122
	private _extensionDevelopmentPath: string;
123
	private _isExtensionTestHost: boolean;
E
Erich Gamma 已提交
124
	private windowState: IWindowState;
125
	private currentMenuBarVisibility: MenuBarVisibility;
126
	private toDispose: IDisposable[];
127
	private representedFilename: string;
E
Erich Gamma 已提交
128

B
Benjamin Pasero 已提交
129
	private whenReadyCallbacks: TValueCallback<CodeWindow>[];
E
Erich Gamma 已提交
130 131 132 133

	private currentConfig: IWindowConfiguration;
	private pendingLoadConfig: IWindowConfiguration;

J
Joao Moreno 已提交
134 135 136
	constructor(
		config: IWindowCreationOptions,
		@ILogService private logService: ILogService,
137
		@IEnvironmentService private environmentService: IEnvironmentService,
138 139
		@IConfigurationService private configurationService: IConfigurationService,
		@IStorageService private storageService: IStorageService
J
Joao Moreno 已提交
140
	) {
141
		this.options = config;
E
Erich Gamma 已提交
142 143
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
144
		this._extensionDevelopmentPath = config.extensionDevelopmentPath;
145
		this._isExtensionTestHost = config.isExtensionTestHost;
E
Erich Gamma 已提交
146
		this.whenReadyCallbacks = [];
147
		this.toDispose = [];
E
Erich Gamma 已提交
148

149 150 151
		this._onClose = new Emitter<IWindowCloseEvent>();
		this.toDispose.push(this._onClose);

B
Benjamin Pasero 已提交
152 153 154 155 156 157 158 159 160 161 162 163 164 165
		// create browser window
		this.createBrowserWindow(config);

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

		// TODO@joao: hook this up to some initialization routine this causes a race between setting the headers and doing
		// a request that needs them. chances are low
		this.setCommonHTTPHeaders();

		// Eventing
		this.registerListeners();
	}

166 167 168 169
	public get onClose(): CommonEvent<IWindowCloseEvent> {
		return this._onClose.event;
	}

B
Benjamin Pasero 已提交
170 171
	private createBrowserWindow(config: IWindowCreationOptions): void {

E
Erich Gamma 已提交
172
		// Load window state
B
Benjamin Pasero 已提交
173
		this.windowState = this.restoreWindowState(config.state);
E
Erich Gamma 已提交
174

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

178
		const options: Electron.BrowserWindowOptions = {
E
Erich Gamma 已提交
179 180 181 182
			width: this.windowState.width,
			height: this.windowState.height,
			x: this.windowState.x,
			y: this.windowState.y,
183
			backgroundColor: this.getBackgroundColor(),
B
Benjamin Pasero 已提交
184 185
			minWidth: CodeWindow.MIN_WIDTH,
			minHeight: CodeWindow.MIN_HEIGHT,
186
			show: !isFullscreenOrMaximized,
B
Benjamin Pasero 已提交
187
			title: product.nameLong,
188
			webPreferences: {
189 190
				'backgroundThrottling': false, // by default if Code is in the background, intervals and timeouts get throttled,
				disableBlinkFeatures: 'Auxclick' // disable auxclick events (see https://developers.google.com/web/updates/2016/10/auxclick)
191
			}
E
Erich Gamma 已提交
192 193
		};

194
		if (isLinux) {
195
			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 已提交
196 197
		}

198 199 200 201 202 203 204 205
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');

		let useNativeTabs = false;
		if (windowConfig && windowConfig.nativeTabs) {
			options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
			useNativeTabs = true;
		}

206
		let useCustomTitleStyle = false;
207
		if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) {
208
			const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
209
			if (!isDev) {
210
				useCustomTitleStyle = true; // not enabled when developing due to https://github.com/electron/electron/issues/3647
B
Benjamin Pasero 已提交
211 212 213
			}
		}

214 215 216 217
		if (useNativeTabs) {
			useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
		}

218 219 220 221 222
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
		}

E
Erich Gamma 已提交
223 224
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
225
		this._id = this._win.id;
E
Erich Gamma 已提交
226

227 228 229 230
		if (useCustomTitleStyle) {
			this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
		}

231
		// Set relaunch command
232
		if (isWindows && product.win32AppUserModelId && typeof this._win.setAppDetails === 'function') {
233 234 235 236 237 238 239
			this._win.setAppDetails({
				appId: product.win32AppUserModelId,
				relaunchCommand: `"${process.execPath}" -n`,
				relaunchDisplayName: product.nameLong
			});
		}

240
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
241
			this._win.maximize();
E
Erich Gamma 已提交
242

B
Benjamin Pasero 已提交
243
			if (this.windowState.mode === WindowMode.Fullscreen) {
B
Benjamin Pasero 已提交
244
				this._win.setFullScreen(true);
245 246
			}

B
Benjamin Pasero 已提交
247 248
			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 已提交
249 250 251
			}
		}

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

B
Benjamin Pasero 已提交
255 256 257 258 259 260 261 262 263 264 265 266 267 268
	private setCommonHTTPHeaders(): void {
		getCommonHTTPHeaders().done(headers => {
			if (!this._win) {
				return;
			}

			const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];

			this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => {
				cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) });
			});
		});
	}

269
	public hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
270
		return this.hiddenTitleBarStyle;
271 272
	}

273
	public get isExtensionDevelopmentHost(): boolean {
274 275 276
		return !!this._extensionDevelopmentPath;
	}

277 278 279 280
	public get isExtensionTestHost(): boolean {
		return this._isExtensionTestHost;
	}

281 282
	public get extensionDevelopmentPath(): string {
		return this._extensionDevelopmentPath;
E
Erich Gamma 已提交
283 284 285 286 287 288
	}

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

B
Benjamin Pasero 已提交
289 290 291 292
	public get id(): number {
		return this._id;
	}

293
	public get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
294 295 296
		return this._win;
	}

297
	public setRepresentedFilename(filename: string): void {
298
		if (isMacintosh) {
299 300 301 302 303 304 305
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

	public getRepresentedFilename(): string {
306
		if (isMacintosh) {
307 308 309 310 311 312
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
313
	public focus(): void {
E
Erich Gamma 已提交
314 315 316 317
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
318 319
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
320 321
		}

B
Benjamin Pasero 已提交
322
		this._win.focus();
E
Erich Gamma 已提交
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
	}

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

	public get openedWorkspacePath(): string {
		return this.currentConfig.workspacePath;
	}

	public get openedFilePath(): string {
		return this.currentConfig.filesToOpen && this.currentConfig.filesToOpen[0] && this.currentConfig.filesToOpen[0].filePath;
	}

	public setReady(): void {
		this._readyState = ReadyState.READY;

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

B
Benjamin Pasero 已提交
346 347
	public ready(): TPromise<CodeWindow> {
		return new TPromise<CodeWindow>((c) => {
E
Erich Gamma 已提交
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
			if (this._readyState === ReadyState.READY) {
				return c(this);
			}

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

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

	private registerListeners(): void {

363 364 365 366 367
		// Re-emit close event
		this._win.on('close', e => {
			this._onClose.fire(e);
		});

E
Erich Gamma 已提交
368 369 370 371 372 373 374 375
		// 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 已提交
376
				this.pendingLoadConfig = null;
E
Erich Gamma 已提交
377 378
			}

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

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

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

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

B
Benjamin Pasero 已提交
398
			shell.openExternal(url);
E
Erich Gamma 已提交
399 400 401 402
		});

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

406 407 408 409 410 411 412 413 414
		// Window Fullscreen
		this._win.on('enter-full-screen', () => {
			this.sendWhenReady('vscode:enterFullScreen');
		});

		this._win.on('leave-full-screen', () => {
			this.sendWhenReady('vscode:leaveFullScreen');
		});

415
		// React to HC color scheme changes (Windows)
416
		if (isWindows) {
417 418 419 420 421 422 423 424 425
			systemPreferences.on('inverted-color-scheme-changed', () => {
				if (systemPreferences.isInvertedColorScheme()) {
					this.sendWhenReady('vscode:enterHighContrast');
				} else {
					this.sendWhenReady('vscode:leaveHighContrast');
				}
			});
		}

E
Erich Gamma 已提交
426 427 428 429 430 431 432
		// Window Failed to load
		this._win.webContents.on('did-fail-load', (event: Event, errorCode: string, errorDescription: string) => {
			console.warn('[electron event]: fail to load, ', errorDescription);
		});

		// 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
433
		if (this.environmentService.isBuilt) {
E
Erich Gamma 已提交
434 435 436 437 438 439
			this._win.webContents.on('will-navigate', (event: Event) => {
				if (event) {
					event.preventDefault();
				}
			});
		}
B
Benjamin Pasero 已提交
440 441

		// Handle configuration changes
B
Benjamin Pasero 已提交
442
		this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated()));
E
Erich Gamma 已提交
443 444
	}

B
Benjamin Pasero 已提交
445
	private onConfigurationUpdated(): void {
446
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
447 448 449 450
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
451 452

		// Swipe command support (macOS)
453
		if (isMacintosh) {
B
Benjamin Pasero 已提交
454 455
			const config = this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>();
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
456
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
457 458 459
			} else {
				this._win.removeAllListeners('swipe');
			}
460
		}
B
Benjamin Pasero 已提交
461 462
	};

B
Benjamin Pasero 已提交
463 464 465 466 467 468 469 470 471 472 473 474 475 476
	private registerNavigationListenerOn(command: 'swipe' | 'app-command', back: 'left' | 'browser-backward', forward: 'right' | 'browser-forward', acrossEditors: boolean) {
		this._win.on(command, (e, cmd) => {
			if (this.readyState !== ReadyState.READY) {
				return; // window must be ready
			}

			if (cmd === back) {
				this.send('vscode:runAction', acrossEditors ? 'workbench.action.openPreviousRecentlyUsedEditor' : 'workbench.action.navigateBack');
			} else if (cmd === forward) {
				this.send('vscode:runAction', acrossEditors ? 'workbench.action.openNextRecentlyUsedEditor' : 'workbench.action.navigateForward');
			}
		});
	}

E
Erich Gamma 已提交
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
	public load(config: IWindowConfiguration): void {

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

494
		// Make sure to clear any previous edited state
495
		if (isMacintosh && this._win.isDocumentEdited()) {
496 497 498
			this._win.setDocumentEdited(false);
		}

E
Erich Gamma 已提交
499
		// Load URL
B
Benjamin Pasero 已提交
500
		this._win.loadURL(this.getUrl(config));
E
Erich Gamma 已提交
501 502

		// Make window visible if it did not open in N seconds because this indicates an error
503 504
		// Only do this when running out of sources and not when running tests
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) {
E
Erich Gamma 已提交
505 506 507 508
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
509
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
510 511 512
				}
			}, 10000);
		}
513

J
Johannes Rieken 已提交
514 515 516
		// (--prof-startup) save profile to disk
		const { profileStartup } = this.environmentService;
		if (profileStartup) {
B
Benjamin Pasero 已提交
517
			stopProfiling(profileStartup.dir, profileStartup.prefix).done(undefined, err => console.error(err));
518
		}
E
Erich Gamma 已提交
519 520
	}

B
Benjamin Pasero 已提交
521
	public reload(cli?: ParsedArgs): void {
E
Erich Gamma 已提交
522 523

		// Inherit current properties but overwrite some
524
		const configuration: IWindowConfiguration = objects.mixin({}, this.currentConfig);
E
Erich Gamma 已提交
525 526
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
527
		delete configuration.filesToDiff;
528

E
Erich Gamma 已提交
529
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
530
		// in extension development mode. These options are all development related.
531
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
532
			configuration.verbose = cli.verbose;
533
			configuration.debugPluginHost = cli.debugPluginHost;
534
			configuration.debugBrkPluginHost = cli.debugBrkPluginHost;
535
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
536 537
		}

538 539
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
540 541 542 543
		// Load config
		this.load(configuration);
	}

544
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
545

546
		// Set zoomlevel
547 548
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
549 550 551 552
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

553 554 555
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

556
		// Set Accessibility Config
557
		windowConfiguration.highContrast = isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast);
558 559
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

560 561 562
		// Set Keyboard Config
		windowConfiguration.isISOKeyboard = KeyboardLayoutMonitor.INSTANCE.isISOKeyboard();

563 564
		// Theme
		windowConfiguration.baseTheme = this.getBaseTheme();
565
		windowConfiguration.backgroundColor = this.getBackgroundColor();
M
Martin Aeschlimann 已提交
566

567 568
		// Perf Counters
		windowConfiguration.perfStartTime = global.perfStartTime;
569
		windowConfiguration.perfAppReady = global.perfAppReady;
570
		windowConfiguration.perfWindowLoadTime = Date.now();
571

572 573 574
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
575 576 577 578 579
		for (let key in config) {
			if (!config[key]) {
				delete config[key]; // only send over properties that have a true value
			}
		}
580

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

584
	private getBaseTheme(): string {
585
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
586 587
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
588

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

591 592 593
		return theme.split(' ')[0];
	}

594
	private getBackgroundColor(): string {
595
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
596 597 598
			return '#000000';
		}

B
Benjamin Pasero 已提交
599
		const background = this.storageService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
600
		if (!background) {
B
Benjamin Pasero 已提交
601 602
			const baseTheme = this.getBaseTheme();

603
			return baseTheme === 'hc-black' ? '#000000' : (baseTheme === 'vs' ? '#FFFFFF' : (isMacintosh ? '#171717' : '#1E1E1E')); // https://github.com/electron/electron/issues/5150
604 605 606 607 608
		}

		return background;
	}

E
Erich Gamma 已提交
609
	public serializeWindowState(): IWindowState {
610 611

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

615
			return {
616
				mode: WindowMode.Fullscreen,
617 618
				display: display ? display.id : void 0,

619 620 621 622 623
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
624
			};
E
Erich Gamma 已提交
625 626
		}

627
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
628 629 630
		let mode: WindowMode;

		// get window mode
631
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
632 633 634 635 636 637 638 639
			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;
640
		} else {
E
Erich Gamma 已提交
641 642 643 644 645
			state.mode = WindowMode.Normal;
		}

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

648 649 650 651
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
652 653 654 655 656
		}

		return state;
	}

B
Benjamin Pasero 已提交
657
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
658 659 660 661
		if (state) {
			try {
				state = this.validateWindowState(state);
			} catch (err) {
J
Joao Moreno 已提交
662
				this.logService.log(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
E
Erich Gamma 已提交
663 664 665 666 667 668 669
			}
		}

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

B
Benjamin Pasero 已提交
670
		return state;
E
Erich Gamma 已提交
671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
	}

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

686
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
687 688 689

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
690
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725

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

726 727 728 729 730 731 732 733 734 735 736 737 738
		// 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
739 740
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
E
Erich Gamma 已提交
741 742
		if (display && display.bounds.x + display.bounds.width > bounds.x && display.bounds.y + display.bounds.height > bounds.y) {
			if (state.mode === WindowMode.Maximized) {
743
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
744 745 746 747 748 749 750 751 752 753 754 755
				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 已提交
756
	public getBounds(): Electron.Rectangle {
B
Benjamin Pasero 已提交
757 758
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
759 760 761 762 763

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

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

B
Benjamin Pasero 已提交
766
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
767
		this._win.setFullScreen(willBeFullScreen);
768

769
		// respect configured menu bar visibility or default to toggle if not set
770
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
771 772
	}

773
	private getMenuBarVisibility(): MenuBarVisibility {
774
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
B
Benjamin Pasero 已提交
775
		if (!windowConfig || !windowConfig.menuBarVisibility) {
776
			return 'default';
B
Benjamin Pasero 已提交
777 778 779 780
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
781
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
782 783 784
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
785 786
	}

787
	public setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
788
		if (isMacintosh) {
B
Benjamin Pasero 已提交
789 790 791
			return; // ignore for macOS platform
		}

B
Benjamin Pasero 已提交
792
		const isFullscreen = this._win.isFullScreen();
793

794
		switch (visibility) {
795
			case ('default'):
B
Benjamin Pasero 已提交
796 797
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
798 799
				break;

D
David Terry 已提交
800
			case ('visible'):
B
Benjamin Pasero 已提交
801 802
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
803
				break;
D
David Terry 已提交
804 805

			case ('toggle'):
B
Benjamin Pasero 已提交
806 807
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
808

809 810 811
				if (notify) {
					this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
				};
812
				break;
D
David Terry 已提交
813 814

			case ('hidden'):
815 816 817 818 819 820
				// 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(() => {
B
Benjamin Pasero 已提交
821 822
					this._win.setMenuBarVisibility(false);
					this._win.setAutoHideMenuBar(false);
823
				});
824 825
				break;
		};
826 827
	}

828 829 830
	public onWindowTitleDoubleClick(): void {

		// Respect system settings on mac with regards to title click on windows title
831
		if (isMacintosh) {
832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854
			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();
			}
		}
	}

855 856 857 858 859 860
	public close(): void {
		if (this._win) {
			this._win.close();
		}
	}

861 862 863 864 865 866 867 868 869 870
	public sendWhenReady(channel: string, ...args: any[]): void {
		this.ready().then(() => {
			this.send(channel, ...args);
		});
	}

	public send(channel: string, ...args: any[]): void {
		this._win.webContents.send(channel, ...args);
	}

E
Erich Gamma 已提交
871 872 873 874 875
	public dispose(): void {
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

876 877
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
878 879
		this._win = null; // Important to dereference the window object to allow for GC
	}
880
}