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

6
import * as path from 'vs/base/common/path';
J
Joao Moreno 已提交
7
import * as objects from 'vs/base/common/objects';
8
import * as nls from 'vs/nls';
9
import { Emitter } from 'vs/base/common/event';
10
import { URI } from 'vs/base/common/uri';
R
Robo 已提交
11
import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron';
12 13
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
14
import { ILogService } from 'vs/platform/log/common/log';
15
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
16
import { parseArgs, OPTIONS, ParsedArgs } from 'vs/platform/environment/node/argv';
17
import product from 'vs/platform/product/common/product';
18
import { IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
B
Benjamin Pasero 已提交
19
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
B
Benjamin Pasero 已提交
20
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
21
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
22
import { INativeWindowConfiguration } from 'vs/platform/windows/node/window';
B
Benjamin Pasero 已提交
23 24
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
25
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
B
Benjamin Pasero 已提交
26
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
27
import * as perf from 'vs/base/common/performance';
28
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService';
29 30
import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService';
import { RunOnceScheduler } from 'vs/base/common/async';
31 32 33
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
34
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
35
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
36 37
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
import { IFileService } from 'vs/platform/files/common/files';
38

39 40
const RUN_TEXTMATE_IN_WORKER = false;

41 42
export interface IWindowCreationOptions {
	state: IWindowState;
43
	extensionDevelopmentPath?: string[];
44
	isExtensionTestHost?: boolean;
45 46
}

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

55
interface ITouchBarSegment extends SegmentedControlSegment {
B
Benjamin Pasero 已提交
56 57 58
	id: string;
}

59 60 61 62 63
const enum WindowError {
	UNRESPONSIVE = 1,
	CRASHED = 2
}

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
const enum ReadyState {

	/**
	 * This window has not loaded any HTML yet
	 */
	NONE,

	/**
	 * This window is loading HTML
	 */
	LOADING,

	/**
	 * This window is navigating to another HTML
	 */
	NAVIGATING,

	/**
	 * This window is done loading HTML
	 */
	READY
}

B
Benjamin Pasero 已提交
87
export class CodeWindow extends Disposable implements ICodeWindow {
E
Erich Gamma 已提交
88

89
	private static readonly MIN_WIDTH = 600;
S
SteVen Batten 已提交
90
	private static readonly MIN_HEIGHT = 270;
E
Erich Gamma 已提交
91

B
Benjamin Pasero 已提交
92
	private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32
93

94
	private readonly _onClose = this._register(new Emitter<void>());
95
	readonly onClose = this._onClose.event;
96 97

	private readonly _onDestroy = this._register(new Emitter<void>());
98
	readonly onDestroy = this._onDestroy.event;
99

100
	private readonly _onLoad = this._register(new Emitter<void>());
101
	readonly onLoad = this._onLoad.event;
102

103 104
	private hiddenTitleBarStyle: boolean | undefined;
	private showTimeoutHandle: NodeJS.Timeout | undefined;
E
Erich Gamma 已提交
105 106 107
	private _lastFocusTime: number;
	private _readyState: ReadyState;
	private windowState: IWindowState;
108
	private currentMenuBarVisibility: MenuBarVisibility | undefined;
109

110
	private representedFilename: string | undefined;
111
	private documentEdited: boolean | undefined;
E
Erich Gamma 已提交
112

113
	private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[];
E
Erich Gamma 已提交
114

115
	private pendingLoadConfig?: INativeWindowConfiguration;
E
Erich Gamma 已提交
116

J
Johannes Rieken 已提交
117
	private marketplaceHeadersPromise: Promise<object>;
118

119
	private readonly touchBarGroups: TouchBarSegmentedControl[];
120

121
	private currentHttpProxy?: string;
122
	private currentNoProxy?: string;
123

J
Joao Moreno 已提交
124 125
	constructor(
		config: IWindowCreationOptions,
126
		@ILogService private readonly logService: ILogService,
127
		@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
128
		@IFileService private readonly fileService: IFileService,
129
		@IStorageMainService private readonly storageService: IStorageMainService,
130
		@IConfigurationService private readonly configurationService: IConfigurationService,
131
		@IThemeMainService private readonly themeMainService: IThemeMainService,
132 133
		@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
		@IBackupMainService private readonly backupMainService: IBackupMainService,
134
		@ITelemetryService private readonly telemetryService: ITelemetryService,
135 136
		@IDialogMainService private readonly dialogMainService: IDialogMainService,
		@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService
J
Joao Moreno 已提交
137
	) {
B
Benjamin Pasero 已提交
138 139
		super();

B
Benjamin Pasero 已提交
140
		this.touchBarGroups = [];
E
Erich Gamma 已提交
141 142 143 144
		this._lastFocusTime = -1;
		this._readyState = ReadyState.NONE;
		this.whenReadyCallbacks = [];

145 146 147 148 149
		//#region create browser window
		{
			// Load window state
			const [state, hasMultipleDisplays] = this.restoreWindowState(config.state);
			this.windowState = state;
B
Benjamin Pasero 已提交
150
			this.logService.trace('window#ctor: using window state', state);
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165

			// in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below)
			const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen);

			const options: BrowserWindowConstructorOptions = {
				width: this.windowState.width,
				height: this.windowState.height,
				x: this.windowState.x,
				y: this.windowState.y,
				backgroundColor: this.themeMainService.getBackgroundColor(),
				minWidth: CodeWindow.MIN_WIDTH,
				minHeight: CodeWindow.MIN_HEIGHT,
				show: !isFullscreenOrMaximized,
				title: product.nameLong,
				webPreferences: {
166
					preload: URI.parse(this.doGetPreloadUrl()).fsPath,
167 168
					nodeIntegration: true,
					nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER,
D
deepak1556 已提交
169 170
					webviewTag: true,
					enableWebSQL: false
171 172
				}
			};
B
Benjamin Pasero 已提交
173

174 175 176
			// Apply icon to window
			// Linux: always
			// Windows: only when running out of sources, otherwise an icon is set by us on the executable
177
			if (isLinux) {
178 179 180
				options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png');
			} else if (isWindows && !this.environmentService.isBuilt) {
				options.icon = path.join(this.environmentService.appRoot, 'resources/win32/code_150x150.png');
181
			}
B
Benjamin Pasero 已提交
182

183
			const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
184

185 186 187
			if (isMacintosh && !this.useNativeFullScreen()) {
				options.fullscreenable = false; // enables simple fullscreen mode
			}
188

189 190
			if (isMacintosh) {
				options.acceptFirstMouse = true; // enabled by default
B
Benjamin Pasero 已提交
191

B
Benjamin Pasero 已提交
192
				if (windowConfig?.clickThroughInactive === false) {
193 194
					options.acceptFirstMouse = false;
				}
195
			}
E
Erich Gamma 已提交
196

B
Benjamin Pasero 已提交
197
			const useNativeTabs = isMacintosh && windowConfig?.nativeTabs === true;
198 199 200
			if (useNativeTabs) {
				options.tabbingIdentifier = product.nameShort; // this opts in to sierra tabs
			}
201

202 203 204 205 206 207 208 209
			const useCustomTitleStyle = getTitleBarStyle(this.configurationService, this.environmentService, !!config.extensionDevelopmentPath) === 'custom';
			if (useCustomTitleStyle) {
				options.titleBarStyle = 'hidden';
				this.hiddenTitleBarStyle = true;
				if (!isMacintosh) {
					options.frame = false;
				}
			}
210

211 212 213
			// Create the browser window.
			this._win = new BrowserWindow(options);
			this._id = this._win.id;
214

215 216
			if (isMacintosh && useCustomTitleStyle) {
				this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any
217 218
			}

219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
			// TODO@Ben (Electron 4 regression): when running on multiple displays where the target display
			// to open the window has a larger resolution than the primary display, the window will not size
			// correctly unless we set the bounds again (https://github.com/microsoft/vscode/issues/74872)
			//
			// However, when running with native tabs with multiple windows we cannot use this workaround
			// because there is a potential that the new window will be added as native tab instead of being
			// a window on its own. In that case calling setBounds() would cause https://github.com/microsoft/vscode/issues/75830
			if (isMacintosh && hasMultipleDisplays && (!useNativeTabs || BrowserWindow.getAllWindows().length === 1)) {
				if ([this.windowState.width, this.windowState.height, this.windowState.x, this.windowState.y].every(value => typeof value === 'number')) {
					const ensuredWindowState = this.windowState as Required<IWindowState>;
					this._win.setBounds({
						width: ensuredWindowState.width,
						height: ensuredWindowState.height,
						x: ensuredWindowState.x,
						y: ensuredWindowState.y
					});
				}
R
Ryan Adolf 已提交
236
			}
237

238 239
			if (isFullscreenOrMaximized) {
				this._win.maximize();
E
Erich Gamma 已提交
240

241 242 243
				if (this.windowState.mode === WindowMode.Fullscreen) {
					this.setFullScreen(true);
				}
244

245 246 247
				if (!this._win.isVisible()) {
					this._win.show(); // to reduce flicker from the default window size to maximize, we only show after maximize
				}
B
Benjamin Pasero 已提交
248
			}
249 250

			this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too
B
Benjamin Pasero 已提交
251
		}
252
		//#endregion
B
Benjamin Pasero 已提交
253

254 255
		// respect configured menu bar visibility
		this.onConfigurationUpdated();
E
Erich Gamma 已提交
256

257 258
		// macOS: touch bar support
		this.createTouchBar();
259

260
		// Request handling
261 262 263 264 265
		const that = this;
		this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService, {
			get(key) { return that.storageService.get(key); },
			store(key, value) { that.storageService.store(key, value); }
		});
E
Erich Gamma 已提交
266

267 268
		// Eventing
		this.registerListeners();
E
Erich Gamma 已提交
269 270
	}

271 272
	private currentConfig: INativeWindowConfiguration | undefined;
	get config(): INativeWindowConfiguration | undefined { return this.currentConfig; }
273

274 275
	private _id: number;
	get id(): number { return this._id; }
276

277 278
	private _win: BrowserWindow;
	get win(): BrowserWindow { return this._win; }
279

280
	get hasHiddenTitleBarStyle(): boolean { return !!this.hiddenTitleBarStyle; }
E
Erich Gamma 已提交
281

282
	get isExtensionDevelopmentHost(): boolean { return !!(this.config && this.config.extensionDevelopmentPath); }
B
Benjamin Pasero 已提交
283

284
	get isExtensionTestHost(): boolean { return !!(this.config && this.config.extensionTestsPath); }
E
Erich Gamma 已提交
285

286 287
	get isExtensionDevelopmentTestFromCli(): boolean { return this.isExtensionDevelopmentHost && this.isExtensionTestHost && !this.config?.debugId; }

B
Benjamin Pasero 已提交
288
	setRepresentedFilename(filename: string): void {
289
		if (isMacintosh) {
290 291 292 293 294 295
			this.win.setRepresentedFilename(filename);
		} else {
			this.representedFilename = filename;
		}
	}

296
	getRepresentedFilename(): string | undefined {
297
		if (isMacintosh) {
298 299 300 301 302 303
			return this.win.getRepresentedFilename();
		}

		return this.representedFilename;
	}

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
	setDocumentEdited(edited: boolean): void {
		if (isMacintosh) {
			this._win.setDocumentEdited(edited);
		}

		this.documentEdited = edited;
	}

	isDocumentEdited(): boolean {
		if (isMacintosh) {
			return this._win.isDocumentEdited();
		}

		return !!this.documentEdited;
	}

B
Benjamin Pasero 已提交
320
	focus(): void {
E
Erich Gamma 已提交
321 322 323 324
		if (!this._win) {
			return;
		}

B
Benjamin Pasero 已提交
325 326
		if (this._win.isMinimized()) {
			this._win.restore();
E
Erich Gamma 已提交
327 328
		}

B
Benjamin Pasero 已提交
329
		this._win.focus();
E
Erich Gamma 已提交
330 331
	}

332
	get lastFocusTime(): number { return this._lastFocusTime; }
E
Erich Gamma 已提交
333

334
	get backupPath(): string | undefined { return this.currentConfig ? this.currentConfig.backupPath : undefined; }
E
Erich Gamma 已提交
335

336
	get openedWorkspace(): IWorkspaceIdentifier | undefined { return this.currentConfig ? this.currentConfig.workspace : undefined; }
337

338
	get openedFolderUri(): URI | undefined { return this.currentConfig ? this.currentConfig.folderUri : undefined; }
339

340
	get remoteAuthority(): string | undefined { return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; }
M
Martin Aeschlimann 已提交
341

B
Benjamin Pasero 已提交
342
	setReady(): void {
E
Erich Gamma 已提交
343 344 345 346
		this._readyState = ReadyState.READY;

		// inform all waiting promises that we are ready now
		while (this.whenReadyCallbacks.length) {
347
			this.whenReadyCallbacks.pop()!(this);
E
Erich Gamma 已提交
348 349 350
		}
	}

J
Johannes Rieken 已提交
351
	ready(): Promise<ICodeWindow> {
B
Benjamin Pasero 已提交
352 353 354
		return new Promise<ICodeWindow>(resolve => {
			if (this.isReady) {
				return resolve(this);
E
Erich Gamma 已提交
355 356 357
			}

			// otherwise keep and call later when we are ready
B
Benjamin Pasero 已提交
358
			this.whenReadyCallbacks.push(resolve);
E
Erich Gamma 已提交
359 360 361
		});
	}

B
Benjamin Pasero 已提交
362 363
	get isReady(): boolean {
		return this._readyState === ReadyState.READY;
E
Erich Gamma 已提交
364 365
	}

366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
	get whenClosedOrLoaded(): Promise<void> {
		return new Promise<void>(resolve => {

			function handle() {
				closeListener.dispose();
				loadListener.dispose();

				resolve();
			}

			const closeListener = this.onClose(() => handle());
			const loadListener = this.onLoad(() => handle());
		});
	}

381
	private registerListeners(): void {
382

383 384 385 386 387
		// Crashes & Unrsponsive
		this._win.webContents.on('crashed', () => this.onWindowError(WindowError.CRASHED));
		this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE));

		// Window close
388 389 390 391 392
		this._win.on('closed', () => {
			this._onClose.fire();

			this.dispose();
		});
393

394
		// Prevent loading of svgs
395
		this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
396 397
			if (details.url.indexOf('.svg') > 0) {
				const uri = URI.parse(details.url);
B
Benjamin Pasero 已提交
398
				if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) {
399 400 401 402 403 404 405
					return callback({ cancel: true });
				}
			}

			return callback({});
		});

406
		this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
R
Robo 已提交
407
			const responseHeaders = details.responseHeaders as Record<string, (string) | (string[])>;
B
Benjamin Pasero 已提交
408

R
Robo 已提交
409
			const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
410 411 412 413
			if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
				return callback({ cancel: true });
			}

C
Christof Marti 已提交
414
			return callback({ cancel: false });
415 416
		});

E
Erich Gamma 已提交
417 418 419 420 421 422 423 424
		// 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;

425
				this.pendingLoadConfig = undefined;
E
Erich Gamma 已提交
426 427 428 429 430
			}
		});

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

B
Benjamin Pasero 已提交
434 435 436
		// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
		// we need to detect when display metrics change or displays are added/removed and toggle the
		// fullscreen manually.
B
Benjamin Pasero 已提交
437
		if (isMacintosh) {
B
Benjamin Pasero 已提交
438 439 440 441 442
			const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
				if (!this._win) {
					return; // disposed
				}

443
				if (!this.useNativeFullScreen() && this.isFullScreen) {
B
Benjamin Pasero 已提交
444 445 446
					this.setFullScreen(false);
					this.setFullScreen(true);
				}
447 448

				this.sendWhenReady('vscode:displayChanged');
B
Benjamin Pasero 已提交
449 450 451 452 453 454 455 456 457
			}, 100));

			const displayChangedListener = () => simpleFullScreenScheduler.schedule();

			screen.on('display-metrics-changed', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));

			screen.on('display-added', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-added', displayChangedListener)));
B
Benjamin Pasero 已提交
458

B
Benjamin Pasero 已提交
459 460
			screen.on('display-removed', displayChangedListener);
			this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener)));
B
Benjamin Pasero 已提交
461
		}
462

S
SteVen Batten 已提交
463
		// Window (Un)Maximize
464
		this._win.on('maximize', (e: Event) => {
B
Benjamin Pasero 已提交
465 466 467 468
			if (this.currentConfig) {
				this.currentConfig.maximized = true;
			}

469 470 471
			app.emit('browser-window-maximize', e, this._win);
		});

472
		this._win.on('unmaximize', (e: Event) => {
B
Benjamin Pasero 已提交
473 474 475 476
			if (this.currentConfig) {
				this.currentConfig.maximized = false;
			}

477 478
			app.emit('browser-window-unmaximize', e, this._win);
		});
S
SteVen Batten 已提交
479

480 481 482 483 484 485 486 487 488
		// 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 已提交
489
		// Window Failed to load
490
		this._win.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
B
Benjamin Pasero 已提交
491
			this.logService.warn('[electron event]: fail to load, ', errorDescription);
E
Erich Gamma 已提交
492 493
		});

B
Benjamin Pasero 已提交
494
		// Handle configuration changes
B
Benjamin Pasero 已提交
495
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated()));
496

497
		// Handle Workspace events
B
Benjamin Pasero 已提交
498
		this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
499 500 501

		// Inject headers when requests are incoming
		const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*'];
502
		this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) =>
503
			this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: Object.assign(details.requestHeaders, headers) })));
504 505
	}

506
	private onWindowError(error: WindowError): void {
B
Benjamin Pasero 已提交
507
		this.logService.error(error === WindowError.CRASHED ? '[VS Code]: renderer process crashed!' : '[VS Code]: detected unresponsive');
508

509 510 511 512 513 514 515 516 517
		// If we run extension tests from CLI, showing a dialog is not
		// very helpful in this case. Rather, we bring down the test run
		// to signal back a failing run.
		if (this.isExtensionDevelopmentTestFromCli) {
			this.lifecycleMainService.kill(1);
			return;
		}

		// Telemetry
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
		type WindowErrorClassification = {
			type: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
		};
		type WindowErrorEvent = {
			type: WindowError;
		};
		this.telemetryService.publicLog2<WindowErrorEvent, WindowErrorClassification>('windowerror', { type: error });

		// Unresponsive
		if (error === WindowError.UNRESPONSIVE) {
			if (this.isExtensionDevelopmentHost || this.isExtensionTestHost || (this._win && this._win.webContents && this._win.webContents.isDevToolsOpened())) {
				// TODO@Ben Workaround for https://github.com/Microsoft/vscode/issues/56994
				// In certain cases the window can report unresponsiveness because a breakpoint was hit
				// and the process is stopped executing. The most typical cases are:
				// - devtools are opened and debugging happens
				// - window is an extensions development host that is being debugged
				// - window is an extension test development host that is being debugged
				return;
			}

			// Show Dialog
			this.dialogMainService.showMessageBox({
				title: product.nameLong,
				type: 'warning',
				buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
				message: nls.localize('appStalled', "The window is no longer responding"),
				detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
				noLink: true
			}, this._win).then(result => {
				if (!this._win) {
					return; // Return early if the window has been going down already
				}

				if (result.response === 0) {
					this.reload();
				} else if (result.response === 2) {
					this.destroyWindow();
				}
			});
		}

		// Crashed
		else {
			this.dialogMainService.showMessageBox({
				title: product.nameLong,
				type: 'warning',
				buttons: [mnemonicButtonLabel(nls.localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(nls.localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
				message: nls.localize('appCrashed', "The window has crashed"),
				detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
				noLink: true
			}, this._win).then(result => {
				if (!this._win) {
					return; // Return early if the window has been going down already
				}

				if (result.response === 0) {
					this.reload();
				} else if (result.response === 1) {
					this.destroyWindow();
				}
			});
		}
	}

	private destroyWindow(): void {
		this._onDestroy.fire(); // 'close' event will not be fired on destroy(), so signal crash via explicit event
		this._win.destroy(); 	// make sure to destroy the window as it has crashed
	}

587
	private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
588 589 590

		// Make sure to update our workspace config if we detect that it
		// was deleted
591
		if (this.openedWorkspace && this.openedWorkspace.id === workspace.id && this.currentConfig) {
R
Rob Lourens 已提交
592
			this.currentConfig.workspace = undefined;
593
		}
E
Erich Gamma 已提交
594 595
	}

B
Benjamin Pasero 已提交
596
	private onConfigurationUpdated(): void {
597
		const newMenuBarVisibility = this.getMenuBarVisibility();
B
Benjamin Pasero 已提交
598 599 600 601
		if (newMenuBarVisibility !== this.currentMenuBarVisibility) {
			this.currentMenuBarVisibility = newMenuBarVisibility;
			this.setMenuBarVisibility(newMenuBarVisibility);
		}
602
		// Do not set to empty configuration at startup if setting is empty to not override configuration through CLI options:
603 604 605 606 607
		const env = process.env;
		const newHttpProxy = (this.configurationService.getValue<string>('http.proxy') || '').trim()
			|| (env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY || '').trim() // Not standardized.
			|| undefined;
		const newNoProxy = (env.no_proxy || env.NO_PROXY || '').trim() || undefined; // Not standardized.
C
Christof Marti 已提交
608
		if ((newHttpProxy || '').indexOf('@') === -1 && (newHttpProxy !== this.currentHttpProxy || newNoProxy !== this.currentNoProxy)) {
609
			this.currentHttpProxy = newHttpProxy;
610
			this.currentNoProxy = newNoProxy;
C
Christof Marti 已提交
611 612 613
			const proxyRules = newHttpProxy || '';
			const proxyBypassRules = newNoProxy ? `${newNoProxy},<local>` : '<local>';
			this.logService.trace(`Setting proxy to '${proxyRules}', bypassing '${proxyBypassRules}'`);
614
			this._win.webContents.session.setProxy({
C
Christof Marti 已提交
615 616
				proxyRules,
				proxyBypassRules,
617 618 619
				pacScript: '',
			});
		}
B
Benjamin Pasero 已提交
620 621
	}

622 623 624 625 626 627
	addTabbedWindow(window: ICodeWindow): void {
		if (isMacintosh) {
			this._win.addTabbedWindow(window.win);
		}
	}

628
	load(config: INativeWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void {
E
Erich Gamma 已提交
629 630 631

		// 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
B
Benjamin Pasero 已提交
632
		if (this._readyState === ReadyState.NONE) {
E
Erich Gamma 已提交
633 634 635 636 637 638 639 640 641 642 643 644
			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;
		}

645 646
		// Add disable-extensions to the config, but do not preserve it on currentConfig or
		// pendingLoadConfig so that it is applied only on this load
647
		const configuration = { ...config };
648 649 650 651
		if (disableExtensions !== undefined) {
			configuration['disable-extensions'] = disableExtensions;
		}

652
		// Clear Document Edited if needed
653
		if (this.isDocumentEdited()) {
B
Benjamin Pasero 已提交
654
			if (!isReload || !this.backupMainService.isHotExitEnabled()) {
655
				this.setDocumentEdited(false);
656 657 658 659 660 661 662 663 664 665
			}
		}

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

			this._win.setTitle(product.nameLong);
666 667
		}

E
Erich Gamma 已提交
668
		// Load URL
669
		perf.mark('main:loadWindow');
670
		this._win.loadURL(this.getUrl(configuration));
E
Erich Gamma 已提交
671 672

		// Make window visible if it did not open in N seconds because this indicates an error
673
		// Only do this when running out of sources and not when running tests
674
		if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsLocationURI) {
E
Erich Gamma 已提交
675 676 677 678
			this.showTimeoutHandle = setTimeout(() => {
				if (this._win && !this._win.isVisible() && !this._win.isMinimized()) {
					this._win.show();
					this._win.focus();
679
					this._win.webContents.openDevTools();
E
Erich Gamma 已提交
680 681 682
				}
			}, 10000);
		}
683 684 685

		// Event
		this._onLoad.fire();
E
Erich Gamma 已提交
686 687
	}

688
	reload(configurationIn?: INativeWindowConfiguration, cli?: ParsedArgs): void {
B
Benjamin Pasero 已提交
689

690
		// If config is not provided, copy our current one
691
		const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig);
692 693

		// Delete some properties we do not want during reload
694
		delete configuration.filesToOpenOrCreate;
695
		delete configuration.filesToDiff;
696
		delete configuration.filesToWait;
697

E
Erich Gamma 已提交
698
		// Some configuration things get inherited if the window is being reloaded and we are
B
Benjamin Pasero 已提交
699
		// in extension development mode. These options are all development related.
700
		if (this.isExtensionDevelopmentHost && cli) {
B
Benjamin Pasero 已提交
701
			configuration.verbose = cli.verbose;
702 703
			configuration['inspect-extensions'] = cli['inspect-extensions'];
			configuration['inspect-brk-extensions'] = cli['inspect-brk-extensions'];
704
			configuration.debugId = cli.debugId;
705
			configuration['extensions-dir'] = cli['extensions-dir'];
E
Erich Gamma 已提交
706 707
		}

708 709
		configuration.isInitialStartup = false; // since this is a reload

E
Erich Gamma 已提交
710
		// Load config
711 712
		const disableExtensions = cli ? cli['disable-extensions'] : undefined;
		this.load(configuration, true, disableExtensions);
E
Erich Gamma 已提交
713 714
	}

715
	private getUrl(windowConfiguration: INativeWindowConfiguration): string {
E
Erich Gamma 已提交
716

717 718
		// Set window ID
		windowConfiguration.windowId = this._win.id;
J
Joao Moreno 已提交
719
		windowConfiguration.sessionId = `window:${this._win.id}`;
S
Sandeep Somavarapu 已提交
720
		windowConfiguration.logLevel = this.logService.getLevel();
721

722
		// Set zoomlevel
723
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
B
Benjamin Pasero 已提交
724
		const zoomLevel = windowConfig?.zoomLevel;
725 726 727 728
		if (typeof zoomLevel === 'number') {
			windowConfiguration.zoomLevel = zoomLevel;
		}

729
		// Set fullscreen state
730
		windowConfiguration.fullscreen = this.isFullScreen;
731

732
		// Set Accessibility Config
733
		let autoDetectHighContrast = true;
B
Benjamin Pasero 已提交
734
		if (windowConfig?.autoDetectHighContrast === false) {
735 736
			autoDetectHighContrast = false;
		}
R
Robo 已提交
737
		windowConfiguration.highContrast = isWindows && autoDetectHighContrast && nativeTheme.shouldUseInvertedColorScheme;
738
		windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled;
739

B
Benjamin Pasero 已提交
740 741
		// Title style related
		windowConfiguration.maximized = this._win.isMaximized();
M
Martin Aeschlimann 已提交
742

743 744
		// Dump Perf Counters
		windowConfiguration.perfEntries = perf.exportEntries();
745

746
		// Parts splash
747
		windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json');
748

749
		// Config (combination of process.argv and window configuration)
750
		const environment = parseArgs(process.argv, OPTIONS);
751
		const config = Object.assign(environment, windowConfiguration) as unknown as { [key: string]: unknown };
752
		for (const key in config) {
753
			const configValue = config[key];
754
			if (configValue === undefined || configValue === null || configValue === '' || configValue === false) {
755
				delete config[key]; // only send over properties that have a true value
756 757
			}
		}
758

759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777
		// In the unlikely event of the URL becoming larger than 2MB, remove parts of
		// it that are not under our control. Mainly, the user environment can be very
		// large depending on user configuration, so we can only remove it in that case.
		let configUrl = this.doGetUrl(config);
		if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
			delete config.userEnv;
			this.logService.warn('Application URL exceeds maximum of 2MB and was shortened.');

			configUrl = this.doGetUrl(config);

			if (configUrl.length > CodeWindow.MAX_URL_LENGTH) {
				this.logService.error('Application URL exceeds maximum of 2MB and cannot be loaded.');
			}
		}

		return configUrl;
	}

	private doGetUrl(config: object): string {
778
		return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
E
Erich Gamma 已提交
779 780
	}

781
	private doGetPreloadUrl(): string {
782
		return require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js');
783 784
	}

B
Benjamin Pasero 已提交
785
	serializeWindowState(): IWindowState {
786 787 788
		if (!this._win) {
			return defaultWindowState();
		}
789 790

		// fullscreen gets special treatment
791
		if (this.isFullScreen) {
792 793
			const display = screen.getDisplayMatching(this.getBounds());

B
Benjamin Pasero 已提交
794 795 796
			const defaultState = defaultWindowState();

			const res = {
797
				mode: WindowMode.Fullscreen,
R
Rob Lourens 已提交
798
				display: display ? display.id : undefined,
799

B
Benjamin Pasero 已提交
800 801 802 803 804 805 806 807 808
				// Still carry over window dimensions from previous sessions
				// if we can compute it in fullscreen state.
				// does not seem possible in all cases on Linux for example
				// (https://github.com/Microsoft/vscode/issues/58218) so we
				// fallback to the defaults in that case.
				width: this.windowState.width || defaultState.width,
				height: this.windowState.height || defaultState.height,
				x: this.windowState.x || 0,
				y: this.windowState.y || 0
809
			};
B
Benjamin Pasero 已提交
810 811

			return res;
E
Erich Gamma 已提交
812 813
		}

814
		const state: IWindowState = Object.create(null);
E
Erich Gamma 已提交
815 816 817
		let mode: WindowMode;

		// get window mode
818
		if (!isMacintosh && this._win.isMaximized()) {
E
Erich Gamma 已提交
819 820 821 822 823 824 825 826
			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;
827
		} else {
E
Erich Gamma 已提交
828 829 830 831 832
			state.mode = WindowMode.Normal;
		}

		// only consider non-minimized window states
		if (mode === WindowMode.Normal || mode === WindowMode.Maximized) {
833
			let bounds: Rectangle;
834 835 836 837 838
			if (mode === WindowMode.Normal) {
				bounds = this.getBounds();
			} else {
				bounds = this._win.getNormalBounds(); // make sure to persist the normal bounds when maximized to be able to restore them
			}
E
Erich Gamma 已提交
839

840 841 842 843
			state.x = bounds.x;
			state.y = bounds.y;
			state.width = bounds.width;
			state.height = bounds.height;
E
Erich Gamma 已提交
844 845 846 847 848
		}

		return state;
	}

B
Benjamin Pasero 已提交
849 850
	private restoreWindowState(state?: IWindowState): [IWindowState, boolean? /* has multiple displays */] {
		let hasMultipleDisplays = false;
E
Erich Gamma 已提交
851 852
		if (state) {
			try {
B
Benjamin Pasero 已提交
853 854 855 856
				const displays = screen.getAllDisplays();
				hasMultipleDisplays = displays.length > 1;

				state = this.validateWindowState(state, displays);
E
Erich Gamma 已提交
857
			} catch (err) {
J
Joao Moreno 已提交
858
				this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate
E
Erich Gamma 已提交
859 860
			}
		}
B
Benjamin Pasero 已提交
861 862

		return [state || defaultWindowState(), hasMultipleDisplays];
E
Erich Gamma 已提交
863 864
	}

B
Benjamin Pasero 已提交
865
	private validateWindowState(state: IWindowState, displays: Display[]): IWindowState | undefined {
B
Benjamin Pasero 已提交
866 867
		this.logService.trace(`window#validateWindowState: validating window state on ${displays.length} display(s)`, state);

868 869 870 871 872
		if (typeof state.x !== 'number'
			|| typeof state.y !== 'number'
			|| typeof state.width !== 'number'
			|| typeof state.height !== 'number'
		) {
B
Benjamin Pasero 已提交
873
			this.logService.trace('window#validateWindowState: unexpected type of state values');
874
			return undefined;
E
Erich Gamma 已提交
875 876 877
		}

		if (state.width <= 0 || state.height <= 0) {
B
Benjamin Pasero 已提交
878
			this.logService.trace('window#validateWindowState: unexpected negative values');
879
			return undefined;
E
Erich Gamma 已提交
880 881 882
		}

		// Single Monitor: be strict about x/y positioning
883 884 885 886 887
		// macOS & Linux: these OS seem to be pretty good in ensuring that a window is never outside of it's bounds.
		// Windows: it is possible to have a window with a size that makes it fall out of the window. our strategy
		//          is to try as much as possible to keep the window in the monitor bounds. we are not as strict as
		//          macOS and Linux and allow the window to exceed the monitor bounds as long as the window is still
		//          some pixels (128) visible on the screen for the user to drag it back.
E
Erich Gamma 已提交
888
		if (displays.length === 1) {
B
Benjamin Pasero 已提交
889
			const displayWorkingArea = this.getWorkingArea(displays[0]);
890
			if (displayWorkingArea) {
B
Benjamin Pasero 已提交
891
				this.logService.trace('window#validateWindowState: 1 monitor working area', displayWorkingArea);
B
Benjamin Pasero 已提交
892

893 894 895 896 897 898 899 900 901 902 903 904 905 906
				function ensureStateInDisplayWorkingArea(): void {
					if (!state || typeof state.x !== 'number' || typeof state.y !== 'number' || !displayWorkingArea) {
						return;
					}

					if (state.x < displayWorkingArea.x) {
						// prevent window from falling out of the screen to the left
						state.x = displayWorkingArea.x;
					}

					if (state.y < displayWorkingArea.y) {
						// prevent window from falling out of the screen to the top
						state.y = displayWorkingArea.y;
					}
E
Erich Gamma 已提交
907 908
				}

909 910
				// ensure state is not outside display working area (top, left)
				ensureStateInDisplayWorkingArea();
E
Erich Gamma 已提交
911

912 913 914
				if (state.width > displayWorkingArea.width) {
					// prevent window from exceeding display bounds width
					state.width = displayWorkingArea.width;
E
Erich Gamma 已提交
915 916
				}

917 918 919
				if (state.height > displayWorkingArea.height) {
					// prevent window from exceeding display bounds height
					state.height = displayWorkingArea.height;
E
Erich Gamma 已提交
920 921
				}

922 923 924 925 926
				if (state.x > (displayWorkingArea.x + displayWorkingArea.width - 128)) {
					// prevent window from falling out of the screen to the right with
					// 128px margin by positioning the window to the far right edge of
					// the screen
					state.x = displayWorkingArea.x + displayWorkingArea.width - state.width;
E
Erich Gamma 已提交
927 928
				}

929 930 931 932 933
				if (state.y > (displayWorkingArea.y + displayWorkingArea.height - 128)) {
					// prevent window from falling out of the screen to the bottom with
					// 128px margin by positioning the window to the far bottom edge of
					// the screen
					state.y = displayWorkingArea.y + displayWorkingArea.height - state.height;
E
Erich Gamma 已提交
934
				}
935 936 937 938

				// again ensure state is not outside display working area
				// (it may have changed from the previous validation step)
				ensureStateInDisplayWorkingArea();
E
Erich Gamma 已提交
939 940 941 942 943
			}

			return state;
		}

944 945
		// Multi Montior (fullscreen): try to find the previously used display
		if (state.display && state.mode === WindowMode.Fullscreen) {
946
			const display = displays.find(d => d.id === state.display);
B
Benjamin Pasero 已提交
947
			if (display && typeof display.bounds?.x === 'number' && typeof display.bounds?.y === 'number') {
B
Benjamin Pasero 已提交
948 949
				this.logService.trace('window#validateWindowState: restoring fullscreen to previous display');

950 951 952 953 954 955 956 957
				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;
			}
		}

958 959
		// Multi Monitor (non-fullscreen): ensure window is within display bounds
		const display = screen.getDisplayMatching({ x: state.x, y: state.y, width: state.width, height: state.height });
B
Benjamin Pasero 已提交
960
		const displayWorkingArea = this.getWorkingArea(display);
U
unknown 已提交
961
		if (
B
Benjamin Pasero 已提交
962 963
			display &&														// we have a display matching the desired bounds
			displayWorkingArea &&											// we have valid working area bounds
964 965 966 967
			state.x + state.width > displayWorkingArea.x &&					// prevent window from falling out of the screen to the left
			state.y + state.height > displayWorkingArea.y &&				// prevent window from falling out of the screen to the top
			state.x < displayWorkingArea.x + displayWorkingArea.width &&	// prevent window from falling out of the screen to the right
			state.y < displayWorkingArea.y + displayWorkingArea.height		// prevent window from falling out of the screen to the bottom
U
unknown 已提交
968
		) {
B
Benjamin Pasero 已提交
969
			this.logService.trace('window#validateWindowState: multi-monitor working area', displayWorkingArea);
B
Benjamin Pasero 已提交
970

E
Erich Gamma 已提交
971 972 973
			return state;
		}

974
		return undefined;
E
Erich Gamma 已提交
975
	}
B
Benjamin Pasero 已提交
976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993

	private getWorkingArea(display: Display): Rectangle | undefined {

		// Prefer the working area of the display to account for taskbars on the
		// desktop being positioned somewhere (https://github.com/Microsoft/vscode/issues/50830).
		//
		// Linux X11 sessions sometimes report wrong display bounds, so we validate
		// the reported sizes are positive.
		if (display.workArea.width > 0 && display.workArea.height > 0) {
			return display.workArea;
		}

		if (display.bounds.width > 0 && display.bounds.height > 0) {
			return display.bounds;
		}

		return undefined;
	}
E
Erich Gamma 已提交
994

995
	getBounds(): Rectangle {
B
Benjamin Pasero 已提交
996 997
		const pos = this._win.getPosition();
		const dimension = this._win.getSize();
E
Erich Gamma 已提交
998 999 1000 1001

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

B
Benjamin Pasero 已提交
1002
	toggleFullScreen(): void {
1003
		this.setFullScreen(!this.isFullScreen);
1004
	}
E
Erich Gamma 已提交
1005

1006
	private setFullScreen(fullscreen: boolean): void {
1007

1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018
		// Set fullscreen state
		if (this.useNativeFullScreen()) {
			this.setNativeFullScreen(fullscreen);
		} else {
			this.setSimpleFullScreen(fullscreen);
		}

		// Events
		this.sendWhenReady(fullscreen ? 'vscode:enterFullScreen' : 'vscode:leaveFullScreen');

		// Respect configured menu bar visibility or default to toggle if not set
1019 1020 1021
		if (this.currentMenuBarVisibility) {
			this.setMenuBarVisibility(this.currentMenuBarVisibility, false);
		}
B
Benjamin Pasero 已提交
1022 1023
	}

1024
	get isFullScreen(): boolean { return this._win.isFullScreen() || this._win.isSimpleFullScreen(); }
1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043

	private setNativeFullScreen(fullscreen: boolean): void {
		if (this._win.isSimpleFullScreen()) {
			this._win.setSimpleFullScreen(false);
		}

		this._win.setFullScreen(fullscreen);
	}

	private setSimpleFullScreen(fullscreen: boolean): void {
		if (this._win.isFullScreen()) {
			this._win.setFullScreen(false);
		}

		this._win.setSimpleFullScreen(fullscreen);
		this._win.webContents.focus(); // workaround issue where focus is not going into window
	}

	private useNativeFullScreen(): boolean {
1044 1045 1046 1047
		const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
		if (!windowConfig || typeof windowConfig.nativeFullScreen !== 'boolean') {
			return true; // default
		}
1048

1049 1050 1051
		if (windowConfig.nativeTabs) {
			return true; // https://github.com/electron/electron/issues/16142
		}
B
Benjamin Pasero 已提交
1052

1053
		return windowConfig.nativeFullScreen !== false;
1054 1055
	}

B
Benjamin Pasero 已提交
1056 1057 1058 1059
	isMinimized(): boolean {
		return this._win.isMinimized();
	}

1060
	private getMenuBarVisibility(): MenuBarVisibility {
1061
		let menuBarVisibility = getMenuBarVisibility(this.configurationService, this.environmentService, !!this.config?.extensionDevelopmentPath);
B
Benjamin Pasero 已提交
1062
		if (['visible', 'toggle', 'hidden'].indexOf(menuBarVisibility) < 0) {
1063
			menuBarVisibility = 'default';
B
Benjamin Pasero 已提交
1064 1065 1066
		}

		return menuBarVisibility;
E
Erich Gamma 已提交
1067 1068
	}

1069
	private setMenuBarVisibility(visibility: MenuBarVisibility, notify: boolean = true): void {
1070
		if (isMacintosh) {
B
Benjamin Pasero 已提交
1071 1072 1073
			return; // ignore for macOS platform
		}

1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
		if (visibility === 'toggle') {
			if (notify) {
				this.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the Alt-key."));
			}
		}

		if (visibility === 'hidden') {
			// for some weird reason that I have no explanation for, the menu bar is not hiding when calling
			// this without timeout (see https://github.com/Microsoft/vscode/issues/19777). there seems to be
			// a timing issue with us opening the first window and the menu bar getting created. somehow the
			// fact that we want to hide the menu without being able to bring it back via Alt key makes Electron
			// still show the menu. Unable to reproduce from a simple Hello World application though...
			setTimeout(() => {
				this.doSetMenuBarVisibility(visibility);
			});
		} else {
			this.doSetMenuBarVisibility(visibility);
		}
	}

	private doSetMenuBarVisibility(visibility: MenuBarVisibility): void {
1095
		const isFullscreen = this.isFullScreen;
1096

1097
		switch (visibility) {
1098
			case ('default'):
B
Benjamin Pasero 已提交
1099
				this._win.setMenuBarVisibility(!isFullscreen);
R
Robo 已提交
1100
				this._win.autoHideMenuBar = isFullscreen;
1101 1102
				break;

D
David Terry 已提交
1103
			case ('visible'):
B
Benjamin Pasero 已提交
1104
				this._win.setMenuBarVisibility(true);
R
Robo 已提交
1105
				this._win.autoHideMenuBar = false;
1106
				break;
D
David Terry 已提交
1107 1108

			case ('toggle'):
B
Benjamin Pasero 已提交
1109
				this._win.setMenuBarVisibility(false);
R
Robo 已提交
1110
				this._win.autoHideMenuBar = true;
1111
				break;
D
David Terry 已提交
1112 1113

			case ('hidden'):
1114
				this._win.setMenuBarVisibility(false);
R
Robo 已提交
1115
				this._win.autoHideMenuBar = false;
1116
				break;
1117
		}
1118 1119
	}

1120
	handleTitleDoubleClick(): void {
1121 1122

		// Respect system settings on mac with regards to title click on windows title
1123
		if (isMacintosh) {
1124 1125 1126 1127 1128 1129 1130 1131 1132
			const action = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string');
			switch (action) {
				case 'Minimize':
					this.win.minimize();
					break;
				case 'None':
					break;
				case 'Maximize':
				default:
B
Benjamin Pasero 已提交
1133 1134 1135 1136 1137
					if (this.win.isMaximized()) {
						this.win.unmaximize();
					} else {
						this.win.maximize();
					}
1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150
			}
		}

		// Linux/Windows: just toggle maximize/minimized state
		else {
			if (this.win.isMaximized()) {
				this.win.unmaximize();
			} else {
				this.win.maximize();
			}
		}
	}

B
Benjamin Pasero 已提交
1151
	close(): void {
1152 1153 1154 1155 1156
		if (this._win) {
			this._win.close();
		}
	}

B
Benjamin Pasero 已提交
1157
	sendWhenReady(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
1158
		if (this.isReady) {
1159
			this.send(channel, ...args);
B
Benjamin Pasero 已提交
1160 1161 1162
		} else {
			this.ready().then(() => this.send(channel, ...args));
		}
1163 1164
	}

B
Benjamin Pasero 已提交
1165
	send(channel: string, ...args: any[]): void {
B
Benjamin Pasero 已提交
1166 1167 1168
		if (this._win) {
			this._win.webContents.send(channel, ...args);
		}
1169 1170
	}

B
Benjamin Pasero 已提交
1171
	updateTouchBar(groups: ISerializableCommandAction[][]): void {
1172 1173 1174 1175
		if (!isMacintosh) {
			return; // only supported on macOS
		}

B
Benjamin Pasero 已提交
1176 1177 1178 1179 1180
		// Update segments for all groups. Setting the segments property
		// of the group directly prevents ugly flickering from happening
		this.touchBarGroups.forEach((touchBarGroup, index) => {
			const commands = groups[index];
			touchBarGroup.segments = this.createTouchBarGroupSegments(commands);
1181
		});
B
Benjamin Pasero 已提交
1182
	}
1183

B
Benjamin Pasero 已提交
1184 1185 1186 1187
	private createTouchBar(): void {
		if (!isMacintosh) {
			return; // only supported on macOS
		}
1188

B
Benjamin Pasero 已提交
1189 1190 1191 1192 1193 1194 1195
		// To avoid flickering, we try to reuse the touch bar group
		// as much as possible by creating a large number of groups
		// for reusing later.
		for (let i = 0; i < 10; i++) {
			const groupTouchBar = this.createTouchBarGroup();
			this.touchBarGroups.push(groupTouchBar);
		}
1196

B
Benjamin Pasero 已提交
1197
		this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups }));
1198
	}
1199

1200
	private createTouchBarGroup(items: ISerializableCommandAction[] = []): TouchBarSegmentedControl {
1201

B
Benjamin Pasero 已提交
1202 1203
		// Group Segments
		const segments = this.createTouchBarGroupSegments(items);
1204

B
Benjamin Pasero 已提交
1205 1206 1207 1208 1209 1210 1211 1212 1213
		// Group Control
		const control = new TouchBar.TouchBarSegmentedControl({
			segments,
			mode: 'buttons',
			segmentStyle: 'automatic',
			change: (selectedIndex) => {
				this.sendWhenReady('vscode:runAction', { id: (control.segments[selectedIndex] as ITouchBarSegment).id, from: 'touchbar' });
			}
		});
1214

B
Benjamin Pasero 已提交
1215 1216 1217
		return control;
	}

B
Benjamin Pasero 已提交
1218
	private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
B
Benjamin Pasero 已提交
1219
		const segments: ITouchBarSegment[] = items.map(item => {
1220
			let icon: NativeImage | undefined;
1221 1222
			if (item.icon && !ThemeIcon.isThemeIcon(item.icon) && item.icon?.dark?.scheme === 'file') {
				icon = nativeImage.createFromPath(URI.revive(item.icon.dark).fsPath);
1223
				if (icon.isEmpty()) {
R
Rob Lourens 已提交
1224
					icon = undefined;
1225
				}
1226
			}
1227

1228 1229 1230 1231 1232 1233 1234
			let title: string;
			if (typeof item.title === 'string') {
				title = item.title;
			} else {
				title = item.title.value;
			}

1235
			return {
B
Benjamin Pasero 已提交
1236
				id: item.id,
1237
				label: !icon ? title : undefined,
1238 1239
				icon
			};
1240 1241
		});

B
Benjamin Pasero 已提交
1242
		return segments;
1243 1244
	}

B
Benjamin Pasero 已提交
1245
	dispose(): void {
B
Benjamin Pasero 已提交
1246 1247
		super.dispose();

E
Erich Gamma 已提交
1248 1249 1250 1251
		if (this.showTimeoutHandle) {
			clearTimeout(this.showTimeoutHandle);
		}

1252
		this._win = null!; // Important to dereference the window object to allow for GC
E
Erich Gamma 已提交
1253
	}
1254
}