Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aux window - support a custom title bar #198836

Merged
merged 29 commits into from Nov 23, 2023
5 changes: 4 additions & 1 deletion src/vs/base/browser/dom.ts
Expand Up @@ -24,7 +24,7 @@ export interface IRegisteredCodeWindow {
readonly disposables: DisposableStore;
}

export const { registerWindow, getWindows, getWindowsCount, getWindowId, onDidRegisterWindow, onWillUnregisterWindow, onDidUnregisterWindow } = (function () {
export const { registerWindow, getWindows, getWindowsCount, getWindowId, hasWindow, onDidRegisterWindow, onWillUnregisterWindow, onDidUnregisterWindow } = (function () {
const windows = new Map<number, IRegisteredCodeWindow>();

ensureCodeWindow(mainWindow, 1);
Expand Down Expand Up @@ -72,6 +72,9 @@ export const { registerWindow, getWindows, getWindowsCount, getWindowId, onDidRe
},
getWindowId(targetWindow: Window): number {
return (targetWindow as CodeWindow).vscodeWindowId;
},
hasWindow(windowId: number): boolean {
return windows.has(windowId);
}
};
})();
Expand Down
4 changes: 4 additions & 0 deletions src/vs/base/common/platform.ts
Expand Up @@ -289,3 +289,7 @@ export const isFirefox = !!(userAgent && userAgent.indexOf('Firefox') >= 0);
export const isSafari = !!(!isChrome && (userAgent && userAgent.indexOf('Safari') >= 0));
export const isEdge = !!(userAgent && userAgent.indexOf('Edg/') >= 0);
export const isAndroid = !!(userAgent && userAgent.indexOf('Android') >= 0);

export function isBigSurOrNewer(osVersion: string): boolean {
return parseFloat(osVersion) >= 20;
}
16 changes: 9 additions & 7 deletions src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts
Expand Up @@ -8,6 +8,7 @@ import { Emitter } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { IBaseWindow } from 'vs/platform/window/electron-main/window';
import { BaseWindow } from 'vs/platform/windows/electron-main/windowImpl';

Expand All @@ -23,13 +24,12 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
readonly id = this.contents.id;
parentId = -1;

private _win: BrowserWindow | null = null;
get win() {
if (!this._win) {
override get win() {
if (!super.win) {
this.tryClaimWindow();
}

return this._win;
return super.win;
}

private _lastFocusTime = Date.now(); // window is shown on creation so take current time
Expand All @@ -39,9 +39,10 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
private readonly contents: WebContents,
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@ILogService private readonly logService: ILogService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IStateService stateService: IStateService
) {
super(configurationService);
super(configurationService, stateService);

this.create();
}
Expand Down Expand Up @@ -70,7 +71,8 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
if (window) {
this.logService.trace('[aux window] Claimed browser window instance');

this._win = window;
// Remember
this.setWin(window);

// Disable Menu
window.setMenu(null);
Expand Down
Expand Up @@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { BrowserWindowConstructorOptions, WebContents } from 'electron';
import { Event } from 'vs/base/common/event';
import { Schemas, VSCODE_AUTHORITY } from 'vs/base/common/network';
import { IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
Expand All @@ -14,6 +15,8 @@ export interface IAuxiliaryWindowsMainService {

readonly _serviceBrand: undefined;

readonly onDidTriggerSystemContextMenu: Event<{ readonly window: IAuxiliaryWindow; readonly x: number; readonly y: number }>;

createWindow(): BrowserWindowConstructorOptions;
registerWindow(webContents: WebContents): void;

Expand Down
Expand Up @@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import { BrowserWindow, BrowserWindowConstructorOptions, WebContents, app } from 'electron';
import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { FileAccess } from 'vs/base/common/network';
import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow';
Expand All @@ -13,16 +14,21 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ILogService } from 'vs/platform/log/common/log';
import { defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows';

export class AuxiliaryWindowsMainService implements IAuxiliaryWindowsMainService {
export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService {

declare readonly _serviceBrand: undefined;

private readonly _onDidTriggerSystemContextMenu = this._register(new Emitter<{ window: IAuxiliaryWindow; x: number; y: number }>());
readonly onDidTriggerSystemContextMenu = this._onDidTriggerSystemContextMenu.event;

private readonly windows = new Map<number, AuxiliaryWindow>();

constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService
) {
super();

this.registerListeners();
}

Expand Down Expand Up @@ -67,7 +73,11 @@ export class AuxiliaryWindowsMainService implements IAuxiliaryWindowsMainService
const auxiliaryWindow = this.instantiationService.createInstance(AuxiliaryWindow, webContents);
this.windows.set(auxiliaryWindow.id, auxiliaryWindow);

Event.once(auxiliaryWindow.onDidClose)(() => this.windows.delete(auxiliaryWindow.id));
const disposables = new DisposableStore();
disposables.add(toDisposable(() => this.windows.delete(auxiliaryWindow.id)));

disposables.add(auxiliaryWindow.onDidTriggerSystemContextMenu(({ x, y }) => this._onDidTriggerSystemContextMenu.fire({ window: auxiliaryWindow, x, y })));
Event.once(auxiliaryWindow.onDidClose)(() => disposables.dispose());
}

getWindowById(windowId: number): AuxiliaryWindow | undefined {
Expand Down
23 changes: 11 additions & 12 deletions src/vs/platform/native/common/native.ts
Expand Up @@ -47,8 +47,8 @@ export interface ICommonNativeHostService {
// Events
readonly onDidOpenMainWindow: Event<number>;

readonly onDidMaximizeMainWindow: Event<number>;
readonly onDidUnmaximizeMainWindow: Event<number>;
readonly onDidMaximizeWindow: Event<number>;
readonly onDidUnmaximizeWindow: Event<number>;

readonly onDidFocusMainWindow: Event<number>;
readonly onDidBlurMainWindow: Event<number>;
Expand All @@ -62,9 +62,9 @@ export interface ICommonNativeHostService {

readonly onDidChangeColorScheme: Event<IColorScheme>;

readonly onDidChangePassword: Event<{ service: string; account: string }>;
readonly onDidChangePassword: Event<{ readonly service: string; readonly account: string }>;

readonly onDidTriggerMainWindowSystemContextMenu: Event<{ windowId: number; x: number; y: number }>;
readonly onDidTriggerWindowSystemContextMenu: Event<{ readonly windowId: number; readonly x: number; readonly y: number }>;

// Window
getWindows(options: { includeAuxiliaryWindows: true }): Promise<Array<IOpenedMainWindow | IOpenedAuxiliaryWindow>>;
Expand All @@ -77,14 +77,14 @@ export interface ICommonNativeHostService {

toggleFullScreen(options?: INativeOptions): Promise<void>;

handleTitleDoubleClick(): Promise<void>;
handleTitleDoubleClick(options?: INativeOptions): Promise<void>;

getCursorScreenPoint(): Promise<{ readonly point: IPoint; readonly display: IRectangle }>;

isMaximized(): Promise<boolean>;
maximizeWindow(): Promise<void>;
unmaximizeWindow(): Promise<void>;
minimizeWindow(): Promise<void>;
isMaximized(options?: INativeOptions): Promise<boolean>;
maximizeWindow(options?: INativeOptions): Promise<void>;
unmaximizeWindow(options?: INativeOptions): Promise<void>;
minimizeWindow(options?: INativeOptions): Promise<void>;
moveWindowTop(options?: INativeOptions): Promise<void>;
positionWindow(position: IRectangle, options?: INativeOptions): Promise<void>;

Expand All @@ -93,7 +93,7 @@ export interface ICommonNativeHostService {
*
* @param options `backgroundColor` and `foregroundColor` are only supported on Windows
*/
updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise<void>;
updateWindowControls(options: INativeOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise<void>;

setMinimumSize(width: number | undefined, height: number | undefined): Promise<void>;

Expand Down Expand Up @@ -167,8 +167,7 @@ export interface ICommonNativeHostService {
notifyReady(): Promise<void>;
relaunch(options?: { addArgs?: string[]; removeArgs?: string[] }): Promise<void>;
reload(options?: { disableExtensions?: boolean }): Promise<void>;
closeWindow(): Promise<void>;
closeWindowById(windowId: number): Promise<void>;
closeWindow(options?: INativeOptions): Promise<void>;
quit(): Promise<void>;
exit(code: number): Promise<void>;

Expand Down
68 changes: 27 additions & 41 deletions src/vs/platform/native/electron-main/nativeHostMainService.ts
Expand Up @@ -77,10 +77,13 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
//#region Events

readonly onDidOpenMainWindow = Event.map(this.windowsMainService.onDidOpenWindow, window => window.id);
readonly onDidTriggerMainWindowSystemContextMenu = Event.filter(Event.map(this.windowsMainService.onDidTriggerSystemContextMenu, ({ window, x, y }) => { return { windowId: window.id, x, y }; }), ({ windowId }) => !!this.windowsMainService.getWindowById(windowId));
readonly onDidTriggerWindowSystemContextMenu = Event.any(
Event.filter(Event.map(this.windowsMainService.onDidTriggerSystemContextMenu, ({ window, x, y }) => { return { windowId: window.id, x, y }; }), ({ windowId }) => !!this.windowsMainService.getWindowById(windowId)),
Event.filter(Event.map(this.auxiliaryWindowsMainService.onDidTriggerSystemContextMenu, ({ window, x, y }) => { return { windowId: window.id, x, y }; }), ({ windowId }) => !!this.auxiliaryWindowsMainService.getWindowById(windowId))
);

readonly onDidMaximizeMainWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId));
readonly onDidUnmaximizeMainWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId));
readonly onDidMaximizeWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId) || !!this.auxiliaryWindowsMainService.getWindowById(windowId));
readonly onDidUnmaximizeWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId) || !!this.auxiliaryWindowsMainService.getWindowById(windowId));

readonly onDidBlurMainWindow = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (event, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId));
readonly onDidFocusMainWindow = Event.any(
Expand Down Expand Up @@ -202,8 +205,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
window?.toggleFullScreen();
}

async handleTitleDoubleClick(windowId: number | undefined): Promise<void> {
const window = this.codeWindowById(windowId);
async handleTitleDoubleClick(windowId: number | undefined, options?: INativeOptions): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
window?.handleTitleDoubleClick();
}

Expand All @@ -214,41 +217,29 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
return { point, display: display.bounds };
}

async isMaximized(windowId: number | undefined): Promise<boolean> {
const window = this.codeWindowById(windowId);
if (window?.win) {
return window.win.isMaximized();
}

return false;
async isMaximized(windowId: number | undefined, options?: INativeOptions): Promise<boolean> {
const window = this.windowById(options?.targetWindowId, windowId);
return window?.win?.isMaximized() ?? false;
}

async maximizeWindow(windowId: number | undefined): Promise<void> {
const window = this.codeWindowById(windowId);
if (window?.win) {
window.win.maximize();
}
async maximizeWindow(windowId: number | undefined, options?: INativeOptions): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
window?.win?.maximize();
}

async unmaximizeWindow(windowId: number | undefined): Promise<void> {
const window = this.codeWindowById(windowId);
if (window?.win) {
window.win.unmaximize();
}
async unmaximizeWindow(windowId: number | undefined, options?: INativeOptions): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
window?.win?.unmaximize();
}

async minimizeWindow(windowId: number | undefined): Promise<void> {
const window = this.codeWindowById(windowId);
if (window?.win) {
window.win.minimize();
}
async minimizeWindow(windowId: number | undefined, options?: INativeOptions): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
window?.win?.minimize();
}

async moveWindowTop(windowId: number | undefined, options?: INativeOptions): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
if (window?.win) {
window.win.moveTop();
}
window?.win?.moveTop();
}

async positionWindow(windowId: number | undefined, position: IRectangle, options?: INativeOptions): Promise<void> {
Expand All @@ -264,11 +255,9 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}
}

async updateWindowControls(windowId: number | undefined, options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise<void> {
const window = this.codeWindowById(windowId);
if (window) {
window.updateWindowControls(options);
}
async updateWindowControls(windowId: number | undefined, options: INativeOptions & { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
window?.updateWindowControls(options);
}

async focusWindow(windowId: number | undefined, options?: INativeOptions & { force?: boolean }): Promise<void> {
Expand Down Expand Up @@ -539,6 +528,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
if (isLinux || isWindows) {
return false;
}

return app.runningUnderARM64Translation;
}

Expand Down Expand Up @@ -724,12 +714,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}
}

async closeWindow(windowId: number | undefined): Promise<void> {
this.closeWindowById(windowId, windowId);
}

async closeWindowById(windowId: number | undefined, targetWindowId?: number | undefined): Promise<void> {
const window = this.windowById(targetWindowId) ?? this.codeWindowById(targetWindowId);
async closeWindow(windowId: number | undefined, options?: INativeOptions): Promise<void> {
const window = this.windowById(options?.targetWindowId, windowId);
return window?.win?.close();
}

Expand Down
10 changes: 5 additions & 5 deletions src/vs/platform/window/electron-main/window.ts
Expand Up @@ -15,6 +15,7 @@ import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platf

export interface IBaseWindow extends IDisposable {

readonly onDidTriggerSystemContextMenu: Event<{ readonly x: number; readonly y: number }>;
readonly onDidClose: Event<void>;

readonly id: number;
Expand All @@ -29,15 +30,18 @@ export interface IBaseWindow extends IDisposable {
setDocumentEdited(edited: boolean): void;
isDocumentEdited(): boolean;

handleTitleDoubleClick(): void;

readonly isFullScreen: boolean;
toggleFullScreen(): void;

updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): void;
}

export interface ICodeWindow extends IBaseWindow {

readonly onWillLoad: Event<ILoadEvent>;
readonly onDidSignalReady: Event<void>;
readonly onDidTriggerSystemContextMenu: Event<{ x: number; y: number }>;
readonly onDidDestroy: Event<void>;

readonly whenClosedOrLoaded: Promise<void>;
Expand Down Expand Up @@ -71,13 +75,9 @@ export interface ICodeWindow extends IBaseWindow {
send(channel: string, ...args: any[]): void;
sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void;

handleTitleDoubleClick(): void;

updateTouchBar(items: ISerializableCommandAction[][]): void;

serializeWindowState(): IWindowState;

updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): void;
}

export const enum LoadReason {
Expand Down