window.ts 27.4 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
import { IWorkspaceIdentifier, IWorkspacesMainService, IWorkspaceSavedEvent } from "vs/platform/workspaces/common/workspaces";
28

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

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

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

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

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

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

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

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

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

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

	private currentConfig: IWindowConfiguration;
	private pendingLoadConfig: IWindowConfiguration;

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

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

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

		// Eventing
		this.registerListeners();
	}

	private createBrowserWindow(config: IWindowCreationOptions): void {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		return this.representedFilename;
	}

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

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

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

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

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

269
	public get openedWorkspace(): IWorkspaceIdentifier {
270
		return this.currentConfig ? this.currentConfig.workspace : void 0;
271 272
	}

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

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

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

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 341 342
		// 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 已提交
343 344 345 346 347 348 349 350
		// 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 已提交
351
				this.pendingLoadConfig = null;
E
Erich Gamma 已提交
352 353
			}

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

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

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

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

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

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

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

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

		// Handle configuration changes
B
Benjamin Pasero 已提交
406
		this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated()));
407

408
		// Handle Workspace events
409
		this.toDispose.push(this.workspaceService.onWorkspaceSaved(e => this.onWorkspaceSaved(e)));
410
		this.toDispose.push(this.workspaceService.onWorkspaceDeleted(e => this.onWorkspaceDeleted(e)));
411 412 413 414 415 416 417
	}

	private onWorkspaceSaved(e: IWorkspaceSavedEvent): void {

		// Make sure to update our workspace config if we detect that it
		// was saved to a new config location
		if (this.openedWorkspace && this.openedWorkspace.id === e.workspace.id && this.openedWorkspace.configPath !== e.workspace.configPath) {
418 419 420 421 422 423 424 425 426 427
			this.currentConfig.workspace.configPath = e.workspace.configPath;
		}
	}

	private onWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {

		// Make sure to update our workspace config if we detect that it
		// was deleted
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id && this.openedWorkspace.configPath === workspace.configPath) {
			this.currentConfig.workspace = void 0;
428
		}
E
Erich Gamma 已提交
429 430
	}

B
Benjamin Pasero 已提交
431
	private onConfigurationUpdated(): void {
432
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
433 434 435 436
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
437 438

		// Swipe command support (macOS)
439
		if (isMacintosh) {
B
Benjamin Pasero 已提交
440 441
			const config = this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>();
			if (config && config.workbench && config.workbench.editor && config.workbench.editor.swipeToNavigate) {
442
				this.registerNavigationListenerOn('swipe', 'left', 'right', true);
B
Benjamin Pasero 已提交
443 444 445
			} else {
				this._win.removeAllListeners('swipe');
			}
446
		}
B
Benjamin Pasero 已提交
447 448
	};

B
Benjamin Pasero 已提交
449 450 451 452 453 454 455 456 457 458 459 460 461 462
	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 已提交
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
	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;
		}

480
		// Make sure to clear any previous edited state
481
		if (isMacintosh && this._win.isDocumentEdited()) {
482 483 484
			this._win.setDocumentEdited(false);
		}

E
Erich Gamma 已提交
485
		// Load URL
B
Benjamin Pasero 已提交
486
		this._win.loadURL(this.getUrl(config));
E
Erich Gamma 已提交
487 488

		// Make window visible if it did not open in N seconds because this indicates an error
489 490
		// Only do this when running out of sources and not when running tests
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) {
E
Erich Gamma 已提交
491 492 493 494
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
495
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
496 497 498
				}
			}, 10000);
		}
499

J
Johannes Rieken 已提交
500 501 502
		// (--prof-startup) save profile to disk
		const { profileStartup } = this.environmentService;
		if (profileStartup) {
B
Benjamin Pasero 已提交
503
			stopProfiling(profileStartup.dir, profileStartup.prefix).done(undefined, err => this.logService.error(err));
504
		}
E
Erich Gamma 已提交
505 506
	}

B
Benjamin Pasero 已提交
507
	public reload(cli?: ParsedArgs): void {
E
Erich Gamma 已提交
508 509

		// Inherit current properties but overwrite some
510
		const configuration: IWindowConfiguration = objects.mixin({}, this.currentConfig);
E
Erich Gamma 已提交
511 512
		delete configuration.filesToOpen;
		delete configuration.filesToCreate;
513
		delete configuration.filesToDiff;
514

E
Erich Gamma 已提交
515
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
516
		// in extension development mode. These options are all development related.
517
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
518
			configuration.verbose = cli.verbose;
519
			configuration.debugPluginHost = cli.debugPluginHost;
520
			configuration.debugBrkPluginHost = cli.debugBrkPluginHost;
521
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
522 523
		}

524 525
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
526 527 528 529
		// Load config
		this.load(configuration);
	}

530
	private getUrl(windowConfiguration: IWindowConfiguration): string {
E
Erich Gamma 已提交
531

532
		// Set zoomlevel
533 534
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
		const zoomLevel = windowConfig && windowConfig.zoomLevel;
535 536 537 538
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

539 540 541
		// Set fullscreen state
		windowConfiguration.fullscreen = this._win.isFullScreen();

542
		// Set Accessibility Config
543
		windowConfiguration.highContrast = isWindows && systemPreferences.isInvertedColorScheme() && (!windowConfig || windowConfig.autoDetectHighContrast);
544 545
		windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();

546 547 548
		// Set Keyboard Config
		windowConfiguration.isISOKeyboard = KeyboardLayoutMonitor.INSTANCE.isISOKeyboard();

549 550
		// Theme
		windowConfiguration.baseTheme = this.getBaseTheme();
551
		windowConfiguration.backgroundColor = this.getBackgroundColor();
M
Martin Aeschlimann 已提交
552

553 554
		// Perf Counters
		windowConfiguration.perfStartTime = global.perfStartTime;
555
		windowConfiguration.perfAppReady = global.perfAppReady;
556
		windowConfiguration.perfWindowLoadTime = Date.now();
557

558 559 560
		// Config (combination of process.argv and window configuration)
		const environment = parseArgs(process.argv);
		const config = objects.assign(environment, windowConfiguration);
561 562 563 564 565
		for (let key in config) {
			if (!config[key]) {
				delete config[key]; // only send over properties that have a true value
			}
		}
566

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

570
	private getBaseTheme(): string {
571
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
572 573
			return 'hc-black';
		}
B
Benjamin Pasero 已提交
574

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

577 578 579
		return theme.split(' ')[0];
	}

580
	private getBackgroundColor(): string {
581
		if (isWindows && systemPreferences.isInvertedColorScheme()) {
582 583 584
			return '#000000';
		}

B
Benjamin Pasero 已提交
585
		const background = this.storageService.getItem<string>(CodeWindow.themeBackgroundStorageKey, null);
586
		if (!background) {
B
Benjamin Pasero 已提交
587 588
			const baseTheme = this.getBaseTheme();

589
			return baseTheme === 'hc-black' ? '#000000' : (baseTheme === 'vs' ? '#FFFFFF' : (isMacintosh ? '#171717' : '#1E1E1E')); // https://github.com/electron/electron/issues/5150
590 591 592 593 594
		}

		return background;
	}

E
Erich Gamma 已提交
595
	public serializeWindowState(): IWindowState {
596 597

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

601
			return {
602
				mode: WindowMode.Fullscreen,
603 604
				display: display ? display.id : void 0,

605 606 607 608 609
				// still carry over window dimensions from previous sessions!
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y
610
			};
E
Erich Gamma 已提交
611 612
		}

613
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
614 615 616
		let mode: WindowMode;

		// get window mode
617
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
618 619 620 621 622 623 624 625
			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;
626
		} else {
E
Erich Gamma 已提交
627 628 629 630 631
			state.mode = WindowMode.Normal;
		}

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

634 635 636 637
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
638 639 640 641 642
		}

		return state;
	}

B
Benjamin Pasero 已提交
643
	private restoreWindowState(state?: IWindowState): IWindowState {
E
Erich Gamma 已提交
644 645 646 647
		if (state) {
			try {
				state = this.validateWindowState(state);
			} catch (err) {
J
Joao Moreno 已提交
648
				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 已提交
649 650 651 652 653 654 655
			}
		}

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

B
Benjamin Pasero 已提交
656
		return state;
E
Erich Gamma 已提交
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
	}

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

672
		const displays = screen.getAllDisplays();
E
Erich Gamma 已提交
673 674 675

		// Single Monitor: be strict about x/y positioning
		if (displays.length === 1) {
676
			const displayBounds = displays[0].bounds;
E
Erich Gamma 已提交
677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711

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

712 713 714 715 716 717 718 719 720 721 722 723 724
		// 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
725 726
		const bounds = { x: state.x, y: state.y, width: state.width, height: state.height };
		const display = screen.getDisplayMatching(bounds);
E
Erich Gamma 已提交
727 728
		if (display && display.bounds.x + display.bounds.width > bounds.x && display.bounds.y + display.bounds.height > bounds.y) {
			if (state.mode === WindowMode.Maximized) {
729
				const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window
E
Erich Gamma 已提交
730 731 732 733 734 735 736 737 738 739 740 741
				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 已提交
742
	public getBounds(): Electron.Rectangle {
B
Benjamin Pasero 已提交
743 744
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
745 746 747 748 749

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

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

B
Benjamin Pasero 已提交
752
		// set fullscreen flag on window
B
Benjamin Pasero 已提交
753
		this._win.setFullScreen(willBeFullScreen);
754

755
		// respect configured menu bar visibility or default to toggle if not set
756
		this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
B
Benjamin Pasero 已提交
757 758
	}

759
	private getMenuBarVisibility(): MenuBarVisibility {
760
		const windowConfig = this.configurationService.getConfiguration<IWindowSettings>('window');
B
Benjamin Pasero 已提交
761
		if (!windowConfig || !windowConfig.menuBarVisibility) {
762
			return 'default';
B
Benjamin Pasero 已提交
763 764 765 766
		}

		let menuBarVisibility = windowConfig.menuBarVisibility;
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
767
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
768 769 770
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
771 772
	}

773
	public setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
774
		if (isMacintosh) {
B
Benjamin Pasero 已提交
775 776 777
			return; // ignore for macOS platform
		}

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

780
		switch (visibility) {
781
			case ('default'):
B
Benjamin Pasero 已提交
782 783
				this._win.setMenuBarVisibility(!isFullscreen);
				this._win.setAutoHideMenuBar(isFullscreen);
784 785
				break;

D
David Terry 已提交
786
			case ('visible'):
B
Benjamin Pasero 已提交
787 788
				this._win.setMenuBarVisibility(true);
				this._win.setAutoHideMenuBar(false);
789
				break;
D
David Terry 已提交
790 791

			case ('toggle'):
B
Benjamin Pasero 已提交
792 793
				this._win.setMenuBarVisibility(false);
				this._win.setAutoHideMenuBar(true);
794

795 796 797
				if (notify) {
					this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key."));
				};
798
				break;
D
David Terry 已提交
799 800

			case ('hidden'):
801 802 803 804 805 806
				// 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 已提交
807 808
					this._win.setMenuBarVisibility(false);
					this._win.setAutoHideMenuBar(false);
809
				});
810 811
				break;
		};
812 813
	}

814 815 816
	public onWindowTitleDoubleClick(): void {

		// Respect system settings on mac with regards to title click on windows title
817
		if (isMacintosh) {
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840
			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();
			}
		}
	}

841 842 843 844 845 846
	public close(): void {
		if (this._win) {
			this._win.close();
		}
	}

847 848 849 850 851 852 853 854 855 856
	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 已提交
857 858 859 860 861
	public dispose(): void {
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

862 863
		this.toDispose = dispose(this.toDispose);

E
Erich Gamma 已提交
864 865
		this._win = null; // Important to dereference the window object to allow for GC
	}
866
}