Path: blob/main/src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindowsMainService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { BrowserWindow, BrowserWindowConstructorOptions, HandlerDetails, WebContents, app } from 'electron';6import { Emitter, Event } from '../../../base/common/event.js';7import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';8import { FileAccess } from '../../../base/common/network.js';9import { validatedIpcMain } from '../../../base/parts/ipc/electron-main/ipcMain.js';10import { AuxiliaryWindow, IAuxiliaryWindow } from './auxiliaryWindow.js';11import { IAuxiliaryWindowsMainService } from './auxiliaryWindows.js';12import { IInstantiationService } from '../../instantiation/common/instantiation.js';13import { ILogService } from '../../log/common/log.js';14import { IWindowState, WindowMode, defaultAuxWindowState } from '../../window/electron-main/window.js';15import { IDefaultBrowserWindowOptionsOverrides, WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from '../../windows/electron-main/windows.js';1617export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService {1819declare readonly _serviceBrand: undefined;2021private readonly _onDidMaximizeWindow = this._register(new Emitter<IAuxiliaryWindow>());22readonly onDidMaximizeWindow = this._onDidMaximizeWindow.event;2324private readonly _onDidUnmaximizeWindow = this._register(new Emitter<IAuxiliaryWindow>());25readonly onDidUnmaximizeWindow = this._onDidUnmaximizeWindow.event;2627private readonly _onDidChangeFullScreen = this._register(new Emitter<{ window: IAuxiliaryWindow; fullscreen: boolean }>());28readonly onDidChangeFullScreen = this._onDidChangeFullScreen.event;2930private readonly _onDidChangeAlwaysOnTop = this._register(new Emitter<{ window: IAuxiliaryWindow; alwaysOnTop: boolean }>());31readonly onDidChangeAlwaysOnTop = this._onDidChangeAlwaysOnTop.event;3233private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: IAuxiliaryWindow; x: number; y: number }>());34readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event;3536private readonly windows = new Map<number /* webContents ID */, AuxiliaryWindow>();3738constructor(39@IInstantiationService private readonly instantiationService: IInstantiationService,40@ILogService private readonly logService: ILogService41) {42super();4344this.registerListeners();45}4647private registerListeners(): void {4849// We have to ensure that an auxiliary window gets to know its50// containing `BrowserWindow` so that it can apply listeners to it51// Unfortunately we cannot rely on static `BrowserWindow` methods52// because we might call the methods too early before the window53// is created.5455app.on('browser-window-created', (_event, browserWindow) => {5657// This is an auxiliary window, try to claim it58const auxiliaryWindow = this.getWindowByWebContents(browserWindow.webContents);59if (auxiliaryWindow) {60this.logService.trace('[aux window] app.on("browser-window-created"): Trying to claim auxiliary window');6162auxiliaryWindow.tryClaimWindow();63}6465// This is a main window, listen to child windows getting created to claim it66else {67const disposables = new DisposableStore();68disposables.add(Event.fromNodeEventEmitter(browserWindow.webContents, 'did-create-window', (browserWindow, details) => ({ browserWindow, details }))(({ browserWindow, details }) => {69const auxiliaryWindow = this.getWindowByWebContents(browserWindow.webContents);70if (auxiliaryWindow) {71this.logService.trace('[aux window] window.on("did-create-window"): Trying to claim auxiliary window');7273auxiliaryWindow.tryClaimWindow(details.options);74}75}));76disposables.add(Event.fromNodeEventEmitter(browserWindow, 'closed')(() => disposables.dispose()));77}78});7980validatedIpcMain.handle('vscode:registerAuxiliaryWindow', async (event, mainWindowId: number) => {81const auxiliaryWindow = this.getWindowByWebContents(event.sender);82if (auxiliaryWindow) {83this.logService.trace('[aux window] vscode:registerAuxiliaryWindow: Registering auxiliary window to main window');8485auxiliaryWindow.parentId = mainWindowId;86}8788return event.sender.id;89});90}9192createWindow(details: HandlerDetails): BrowserWindowConstructorOptions {93const { state, overrides } = this.computeWindowStateAndOverrides(details);94return this.instantiationService.invokeFunction(defaultBrowserWindowOptions, state, overrides, {95preload: FileAccess.asFileUri('vs/base/parts/sandbox/electron-browser/preload-aux.js').fsPath96});97}9899private computeWindowStateAndOverrides(details: HandlerDetails): { readonly state: IWindowState; readonly overrides: IDefaultBrowserWindowOptionsOverrides } {100const windowState: IWindowState = {};101const overrides: IDefaultBrowserWindowOptionsOverrides = {};102103const features = details.features.split(','); // for example: popup=yes,left=270,top=14.5,width=1024,height=768104for (const feature of features) {105const [key, value] = feature.split('=');106switch (key) {107case 'width':108windowState.width = parseInt(value, 10);109break;110case 'height':111windowState.height = parseInt(value, 10);112break;113case 'left':114windowState.x = parseInt(value, 10);115break;116case 'top':117windowState.y = parseInt(value, 10);118break;119case 'window-maximized':120windowState.mode = WindowMode.Maximized;121break;122case 'window-fullscreen':123windowState.mode = WindowMode.Fullscreen;124break;125case 'window-disable-fullscreen':126overrides.disableFullscreen = true;127break;128case 'window-native-titlebar':129overrides.forceNativeTitlebar = true;130break;131case 'window-always-on-top':132overrides.alwaysOnTop = true;133break;134}135}136137const state = WindowStateValidator.validateWindowState(this.logService, windowState) ?? defaultAuxWindowState();138139this.logService.trace('[aux window] using window state', state);140141return { state, overrides };142}143144registerWindow(webContents: WebContents): void {145const disposables = new DisposableStore();146147const auxiliaryWindow = this.instantiationService.createInstance(AuxiliaryWindow, webContents);148149this.windows.set(auxiliaryWindow.id, auxiliaryWindow);150disposables.add(toDisposable(() => this.windows.delete(auxiliaryWindow.id)));151152disposables.add(auxiliaryWindow.onDidMaximize(() => this._onDidMaximizeWindow.fire(auxiliaryWindow)));153disposables.add(auxiliaryWindow.onDidUnmaximize(() => this._onDidUnmaximizeWindow.fire(auxiliaryWindow)));154disposables.add(auxiliaryWindow.onDidEnterFullScreen(() => this._onDidChangeFullScreen.fire({ window: auxiliaryWindow, fullscreen: true })));155disposables.add(auxiliaryWindow.onDidLeaveFullScreen(() => this._onDidChangeFullScreen.fire({ window: auxiliaryWindow, fullscreen: false })));156disposables.add(auxiliaryWindow.onDidChangeAlwaysOnTop(alwaysOnTop => this._onDidChangeAlwaysOnTop.fire({ window: auxiliaryWindow, alwaysOnTop })));157disposables.add(auxiliaryWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: auxiliaryWindow, x, y })));158159Event.once(auxiliaryWindow.onDidClose)(() => disposables.dispose());160}161162getWindowByWebContents(webContents: WebContents): AuxiliaryWindow | undefined {163const window = this.windows.get(webContents.id);164165return window?.matches(webContents) ? window : undefined;166}167168getFocusedWindow(): IAuxiliaryWindow | undefined {169const window = BrowserWindow.getFocusedWindow();170if (window) {171return this.getWindowByWebContents(window.webContents);172}173174return undefined;175}176177getLastActiveWindow(): IAuxiliaryWindow | undefined {178return getLastFocused(Array.from(this.windows.values()));179}180181getWindows(): readonly IAuxiliaryWindow[] {182return Array.from(this.windows.values());183}184}185186187