window.ts 26.3 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Joao Moreno 已提交
8 9
import * as path from 'path';
import * as objects from 'vs/base/common/objects';
10
import { stopProfiling } from 'vs/base/node/profiler';
11
import nls = require('vs/nls');
12
import URI from "vs/base/common/uri";
13
import { IStorageService } from 'vs/platform/storage/node/storage';
14
import { shell, screen, BrowserWindow, systemPreferences, app } from 'electron';
J
Joao Moreno 已提交
15
import { TPromise, TValueCallback } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
16
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
17
import { ILogService } from 'vs/platform/log/common/log';
18
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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';
B
Benjamin Pasero 已提交
22
import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState } from 'vs/platform/windows/common/windows';
23
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
24
import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard';
B
Benjamin Pasero 已提交
25 26
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow } from "vs/platform/windows/electron-main/windows";
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
	};
};

B
Benjamin Pasero 已提交
58 59 60 61 62 63 64 65
interface IWorkbenchEditorConfiguration {
	workbench: {
		editor: {
			swipeToNavigate: boolean
		}
	};
}

B
Benjamin Pasero 已提交
66
export class CodeWindow implements ICodeWindow {
E
Erich Gamma 已提交
67

68
	public static themeStorageKey = 'theme';
69
	public static themeBackgroundStorageKey = 'themeBackground';
70

E
Erich Gamma 已提交
71 72 73
	private static MIN_WIDTH = 200;
	private static MIN_HEIGHT = 120;

74
	private options: IWindowCreationOptions;
B
Benjamin Pasero 已提交
75
	private hiddenTitleBarStyle: boolean;
E
Erich Gamma 已提交
76
	private showTimeoutHandle: any;
B
Benjamin Pasero 已提交
77
	private _id: number;
78
	private _win: Electron.BrowserWindow;
E
Erich Gamma 已提交
79 80
	private _lastFocusTime: number;
	private _readyState: ReadyState;
81
	private _extensionDevelopmentPath: string;
82
	private _isExtensionTestHost: boolean;
E
Erich Gamma 已提交
83
	private windowState: IWindowState;
84
	private currentMenuBarVisibility: MenuBarVisibility;
85
	private toDispose: IDisposable[];
86
	private representedFilename: string;
E
Erich Gamma 已提交
87

B
Benjamin Pasero 已提交
88
	private whenReadyCallbacks: TValueCallback<CodeWindow>[];
E
Erich Gamma 已提交
89 90 91 92

	private currentConfig: IWindowConfiguration;
	private pendingLoadConfig: IWindowConfiguration;

J
Joao Moreno 已提交
93 94 95
	constructor(
		config: IWindowCreationOptions,
		@ILogService private logService: ILogService,
96
		@IEnvironmentService private environmentService: IEnvironmentService,
97 98
		@IConfigurationService private configurationService: IConfigurationService,
		@IStorageService private storageService: IStorageService
J
Joao Moreno 已提交
99
	) {
100
		this.options = config;
E
Erich Gamma 已提交
101 102
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
103
		this._extensionDevelopmentPath = config.extensionDevelopmentPath;
104
		this._isExtensionTestHost = config.isExtensionTestHost;
E
Erich Gamma 已提交
105
		this.whenReadyCallbacks = [];
106
		this.toDispose = [];
E
Erich Gamma 已提交
107

B
Benjamin Pasero 已提交
108 109 110 111 112 113 114 115 116 117 118 119
		// create browser window
		this.createBrowserWindow(config);

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

		// Eventing
		this.registerListeners();
	}

	private createBrowserWindow(config: IWindowCreationOptions): void {

E
Erich Gamma 已提交
120
		// Load window state
B
Benjamin Pasero 已提交
121
		this.windowState = this.restoreWindowState(config.state);
E
Erich Gamma 已提交
122

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

126
		const options: Electron.BrowserWindowOptions = {
E
Erich Gamma 已提交
127 128 129 130
			width: this.windowState.width,
			height: this.windowState.height,
			x: this.windowState.x,
			y: this.windowState.y,
131
			backgroundColor: this.getBackgroundColor(),
B
Benjamin Pasero 已提交
132 133
			minWidth: CodeWindow.MIN_WIDTH,
			minHeight: CodeWindow.MIN_HEIGHT,
134
			show: !isFullscreenOrMaximized,
B
Benjamin Pasero 已提交
135
			title: product.nameLong,
136
			webPreferences: {
137 138
				'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)
139
			}
E
Erich Gamma 已提交
140 141
		};

142
		if (isLinux) {
143
			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 已提交
144 145
		}

146 147 148 149 150 151 152 153
		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;
		}

154
		let useCustomTitleStyle = false;
155
		if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) {
156
			const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath;
B
Benjamin Pasero 已提交
157
			if (!isDev) {
158
				useCustomTitleStyle = true; // not enabled when developing due to https://github.com/electron/electron/issues/3647
B
Benjamin Pasero 已提交
159 160 161
			}
		}

162 163 164 165
		if (useNativeTabs) {
			useCustomTitleStyle = false; // native tabs on sierra do not work with custom title style
		}

166 167 168 169 170
		if (useCustomTitleStyle) {
			options.titleBarStyle = 'hidden';
			this.hiddenTitleBarStyle = true;
		}

E
Erich Gamma 已提交
171 172
		// Create the browser window.
		this._win = new BrowserWindow(options);
B
Benjamin Pasero 已提交
173
		this._id = this._win.id;
E
Erich Gamma 已提交
174

175 176 177 178
		if (useCustomTitleStyle) {
			this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
		}

179
		// Set relaunch command
180
		if (isWindows && product.win32AppUserModelId && typeof this._win.setAppDetails === 'function') {
181 182 183 184 185 186 187
			this._win.setAppDetails({
				appId: product.win32AppUserModelId,
				relaunchCommand: `"${process.execPath}" -n`,
				relaunchDisplayName: product.nameLong
			});
		}

188
		if (isFullscreenOrMaximized) {
B
Benjamin Pasero 已提交
189
			this._win.maximize();
E
Erich Gamma 已提交
190

B
Benjamin Pasero 已提交
191
			if (this.windowState.mode === WindowMode.Fullscreen) {
B
Benjamin Pasero 已提交
192
				this._win.setFullScreen(true);
193 194
			}

B
Benjamin Pasero 已提交
195 196
			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 已提交
197 198 199
			}
		}

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

203
	public hasHiddenTitleBarStyle(): boolean {
B
Benjamin Pasero 已提交
204
		return this.hiddenTitleBarStyle;
205 206
	}

207
	public get isExtensionDevelopmentHost(): boolean {
208 209 210
		return !!this._extensionDevelopmentPath;
	}

211 212 213 214
	public get isExtensionTestHost(): boolean {
		return this._isExtensionTestHost;
	}

215 216
	public get extensionDevelopmentPath(): string {
		return this._extensionDevelopmentPath;
E
Erich Gamma 已提交
217 218 219 220 221 222
	}

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

B
Benjamin Pasero 已提交
223 224 225 226
	public get id(): number {
		return this._id;
	}

227
	public get win(): Electron.BrowserWindow {
E
Erich Gamma 已提交
228 229 230
		return this._win;
	}

231
	public setRepresentedFilename(filename: string): void {
232
		if (isMacintosh) {
233 234 235 236 237 238 239
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

	public getRepresentedFilename(): string {
240
		if (isMacintosh) {
241 242 243 244 245 246
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

B
Benjamin Pasero 已提交
247
	public focus(): void {
E
Erich Gamma 已提交
248 249 250 251
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
252 253
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
254 255
		}

B
Benjamin Pasero 已提交
256
		this._win.focus();
E
Erich Gamma 已提交
257 258 259 260 261 262
	}

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

263 264
	public get backupPath(): string {
		return this.currentConfig ? this.currentConfig.backupPath : void 0;
E
Erich Gamma 已提交
265 266
	}

267 268 269 270
	public get openedWorkspaceConfigPath(): string {
		return this.currentConfig ? this.currentConfig.workspaceConfigPath : void 0;
	}

271 272 273 274
	public get openedFolderPath(): string {
		return this.currentConfig ? this.currentConfig.folderPath : void 0;
	}

E
Erich Gamma 已提交
275
	public get openedFilePath(): string {
276
		return this.currentConfig && this.currentConfig.filesToOpen && this.currentConfig.filesToOpen[0] && this.currentConfig.filesToOpen[0].filePath;
E
Erich Gamma 已提交
277 278 279 280 281 282 283 284 285 286 287
	}

	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 已提交
288 289
	public ready(): TPromise<CodeWindow> {
		return new TPromise<CodeWindow>((c) => {
E
Erich Gamma 已提交
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
			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 {

305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
		// Set common HTTP headers
		// 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
		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) });
			});
		});

		// Prevent loading of svgs
		this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
			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({});
		});

		this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
			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 已提交
341 342 343 344 345 346 347 348
		// 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 已提交
349
				this.pendingLoadConfig = null;
E
Erich Gamma 已提交
350 351
			}

B
Benjamin Pasero 已提交
352
			// To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded
B
Benjamin Pasero 已提交
353
			if (!this._win.isVisible()) {
B
Benjamin Pasero 已提交
354
				if (this.windowState.mode === WindowMode.Maximized) {
B
Benjamin Pasero 已提交
355
					this._win.maximize();
E
Erich Gamma 已提交
356 357
				}

B
Benjamin Pasero 已提交
358 359
				if (!this._win.isVisible()) { // maximize also makes visible
					this._win.show();
E
Erich Gamma 已提交
360 361 362 363 364
				}
			}
		});

		// App commands support
365
		this.registerNavigationListenerOn('app-command', 'browser-backward', 'browser-forward', false);
E
Erich Gamma 已提交
366 367 368 369 370

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

B
Benjamin Pasero 已提交
371
			shell.openExternal(url);
E
Erich Gamma 已提交
372 373 374 375
		});

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

379 380 381 382 383 384 385 386 387
		// 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 已提交
388 389
		// Window Failed to load
		this._win.webContents.on('did-fail-load', (event: Event, errorCode: string, errorDescription: string) => {
B
Benjamin Pasero 已提交
390
			this.logService.warn('[electron event]: fail to load, ', errorDescription);
E
Erich Gamma 已提交
391 392 393 394
		});

		// 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
395
		if (this.environmentService.isBuilt) {
E
Erich Gamma 已提交
396 397 398 399 400 401
			this._win.webContents.on('will-navigate', (event: Event) => {
				if (event) {
					event.preventDefault();
				}
			});
		}
B
Benjamin Pasero 已提交
402 403

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

B
Benjamin Pasero 已提交
407
	private onConfigurationUpdated(): void {
408
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
409 410 411 412
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
413 414

		// Swipe command support (macOS)
415
		if (isMacintosh) {
B
Benjamin Pasero 已提交
416 417
			const config = this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>();
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
418
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
419 420 421
			} else {
				this._win.removeAllListeners('swipe');
			}
422
		}
B
Benjamin Pasero 已提交
423 424
	};

B
Benjamin Pasero 已提交
425 426 427 428 429 430 431 432 433 434 435 436 437 438
	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 已提交
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
	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;
		}

456
		// Make sure to clear any previous edited state
457
		if (isMacintosh && this._win.isDocumentEdited()) {
458 459 460
			this._win.setDocumentEdited(false);
		}

E
Erich Gamma 已提交
461
		// Load URL
B
Benjamin Pasero 已提交
462
		this._win.loadURL(this.getUrl(config));
E
Erich Gamma 已提交
463 464

		// Make window visible if it did not open in N seconds because this indicates an error
465 466
		// Only do this when running out of sources and not when running tests
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) {
E
Erich Gamma 已提交
467 468 469 470
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
471
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
472 473 474
				}
			}, 10000);
		}
475

J
Johannes Rieken 已提交
476 477 478
		// (--prof-startup) save profile to disk
		const { profileStartup } = this.environmentService;
		if (profileStartup) {
B
Benjamin Pasero 已提交
479
			stopProfiling(profileStartup.dir, profileStartup.prefix).done(undefined, err => this.logService.error(err));
480
		}
E
Erich Gamma 已提交
481 482
	}

B
Benjamin Pasero 已提交
483
	public reload(cli?: ParsedArgs): void {
E
Erich Gamma 已提交
484 485

		// Inherit current properties but overwrite some
486
		const configuration: IWindowConfiguration = objects.mixin({}, this.currentConfig);
E
Erich Gamma 已提交
487 488
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
489
		delete configuration.filesToDiff;
490

E
Erich Gamma 已提交
491
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
492
		// in extension development mode. These options are all development related.
493
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
494
			configuration.verbose = cli.verbose;
495
			configuration.debugPluginHost = cli.debugPluginHost;
496
			configuration.debugBrkPluginHost = cli.debugBrkPluginHost;
497
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
498 499
		}

500 501
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
502 503 504 505
		// Load config
		this.load(configuration);
	}

506
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
507

508
		// Set zoomlevel
509 510
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
511 512 513 514
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

515 516 517
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

518
		// Set Accessibility Config
519
		windowConfiguration.highContrast = isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast);
520 521
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

522 523 524
		// Set Keyboard Config
		windowConfiguration.isISOKeyboard = KeyboardLayoutMonitor.INSTANCE.isISOKeyboard();

525 526
		// Theme
		windowConfiguration.baseTheme = this.getBaseTheme();
527
		windowConfiguration.backgroundColor = this.getBackgroundColor();
M
Martin Aeschlimann 已提交
528

529 530
		// Perf Counters
		windowConfiguration.perfStartTime = global.perfStartTime;
531
		windowConfiguration.perfAppReady = global.perfAppReady;
532
		windowConfiguration.perfWindowLoadTime = Date.now();
533

534 535 536
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
537 538 539 540 541
		for (let key in config) {
			if (!config[key]) {
				delete config[key]; // only send over properties that have a true value
			}
		}
542

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

546
	private getBaseTheme(): string {
547
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
548 549
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
550

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

553 554 555
		return theme.split(' ')[0];
	}

556
	private getBackgroundColor(): string {
557
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
558 559 560
			return '#000000';
		}

B
Benjamin Pasero 已提交
561
		const background = this.storageService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
562
		if (!background) {
B
Benjamin Pasero 已提交
563 564
			const baseTheme = this.getBaseTheme();

565
			return baseTheme === 'hc-black' ? '#000000' : (baseTheme === 'vs' ? '#FFFFFF' : (isMacintosh ? '#171717' : '#1E1E1E')); // https://github.com/electron/electron/issues/5150
566 567 568 569 570
		}

		return background;
	}

E
Erich Gamma 已提交
571
	public serializeWindowState(): IWindowState {
572 573

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

577
			return {
578
				mode: WindowMode.Fullscreen,
579 580
				display: display ? display.id : void 0,

581 582 583 584 585
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
586
			};
E
Erich Gamma 已提交
587 588
		}

589
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
590 591 592
		let mode: WindowMode;

		// get window mode
593
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
594 595 596 597 598 599 600 601
			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;
602
		} else {
E
Erich Gamma 已提交
603 604 605 606 607
			state.mode = WindowMode.Normal;
		}

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

610 611 612 613
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
614 615 616 617 618
		}

		return state;
	}

B
Benjamin Pasero 已提交
619
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
620 621 622 623
		if (state) {
			try {
				state = this.validateWindowState(state);
			} catch (err) {
J
Joao Moreno 已提交
624
				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 已提交
625 626 627 628 629 630 631
			}
		}

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

B
Benjamin Pasero 已提交
632
		return state;
E
Erich Gamma 已提交
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
	}

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

648
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
649 650 651

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
652
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687

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

688 689 690 691 692 693 694 695 696 697 698 699 700
		// 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
701 702
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
E
Erich Gamma 已提交
703 704
		if (display && display.bounds.x + display.bounds.width > bounds.x && display.bounds.y + display.bounds.height > bounds.y) {
			if (state.mode === WindowMode.Maximized) {
705
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
706 707 708 709 710 711 712 713 714 715 716 717
				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 已提交
718
	public getBounds(): Electron.Rectangle {
B
Benjamin Pasero 已提交
719 720
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
721 722 723 724 725

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

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

B
Benjamin Pasero 已提交
728
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
729
		this._win.setFullScreen(willBeFullScreen);
730

731
		// respect configured menu bar visibility or default to toggle if not set
732
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
733 734
	}

735
	private getMenuBarVisibility(): MenuBarVisibility {
736
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
B
Benjamin Pasero 已提交
737
		if (!windowConfig || !windowConfig.menuBarVisibility) {
738
			return 'default';
B
Benjamin Pasero 已提交
739 740 741 742
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
743
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
744 745 746
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
747 748
	}

749
	public setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
750
		if (isMacintosh) {
B
Benjamin Pasero 已提交
751 752 753
			return; // ignore for macOS platform
		}

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

756
		switch (visibility) {
757
			case ('default'):
B
Benjamin Pasero 已提交
758 759
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
760 761
				break;

D
David Terry 已提交
762
			case ('visible'):
B
Benjamin Pasero 已提交
763 764
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
765
				break;
D
David Terry 已提交
766 767

			case ('toggle'):
B
Benjamin Pasero 已提交
768 769
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
770

771 772 773
				if (notify) {
					this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
				};
774
				break;
D
David Terry 已提交
775 776

			case ('hidden'):
777 778 779 780 781 782
				// 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 已提交
783 784
					this._win.setMenuBarVisibility(false);
					this._win.setAutoHideMenuBar(false);
785
				});
786 787
				break;
		};
788 789
	}

790 791 792
	public onWindowTitleDoubleClick(): void {

		// Respect system settings on mac with regards to title click on windows title
793
		if (isMacintosh) {
794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
			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();
			}
		}
	}

817 818 819 820 821 822
	public close(): void {
		if (this._win) {
			this._win.close();
		}
	}

823 824 825 826 827 828 829 830 831 832
	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 已提交
833 834 835 836 837
	public dispose(): void {
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

838 839
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
840 841
		this._win = null; // Important to dereference the window object to allow for GC
	}
842
}