Skip to content

Commit

Permalink
aux window - try to preserve maximised state (fix #209917)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero authored Apr 9, 2024
2 parents f8d35f6 + 633262d commit e80ba87
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 47 deletions.
15 changes: 5 additions & 10 deletions src/vs/platform/auxiliaryWindow/electron-main/auxiliaryWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,11 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
y: options.y,
width: options.width,
height: options.height,
// We currently do not support restoring fullscreen state for auxiliary windows
// but we can probe the `options.show` value for whether the window should be
// maximized or not.
//
// This is a bit of a hack but we know that `show: false` for when the window
// should restore maximised or fullscreen.
//
// Note: as of April 2024 we currently do not support restoring maximized state
// for auxiliary windows because we currently do not have a good way of detecting
// maximized state in the renderer using DOM APIs.
// TODO@bpasero We currently do not support restoring fullscreen state for
// auxiliary windows because we do not get hold of the original `features`
// string that contains that info in `window-fullscreen`. However, we can
// probe the `options.show` value for whether the window should be maximized
// or not because we never show maximized windows initially to reduce flicker.
mode: options.show === false ? WindowMode.Maximized : WindowMode.Normal
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { AuxiliaryWindow, IAuxiliaryWindow } from 'vs/platform/auxiliaryWindow/e
import { IAuxiliaryWindowsMainService } from 'vs/platform/auxiliaryWindow/electron-main/auxiliaryWindows';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { IWindowState, defaultAuxWindowState } from 'vs/platform/window/electron-main/window';
import { IWindowState, WindowMode, defaultAuxWindowState } from 'vs/platform/window/electron-main/window';
import { WindowStateValidator, defaultBrowserWindowOptions, getLastFocused } from 'vs/platform/windows/electron-main/windows';

export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliaryWindowsMainService {
Expand Down Expand Up @@ -110,6 +110,12 @@ export class AuxiliaryWindowsMainService extends Disposable implements IAuxiliar
case 'top':
windowState.y = parseInt(value, 10);
break;
case 'window-maximized':
windowState.mode = WindowMode.Maximized;
break;
case 'window-fullscreen':
windowState.mode = WindowMode.Fullscreen;
break;
}
}

Expand Down
41 changes: 8 additions & 33 deletions src/vs/workbench/browser/parts/editor/editorParts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,15 @@ import { MultiWindowParts } from 'vs/workbench/browser/part';
import { DeferredPromise } from 'vs/base/common/async';
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IRectangle } from 'vs/platform/window/common/window';
import { getWindow } from 'vs/base/browser/dom';
import { getZoomLevel } from 'vs/base/browser/browser';
import { IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';

interface IEditorPartsUIState {
readonly auxiliary: IAuxiliaryEditorPartState[];
readonly mru: number[];
}

interface IAuxiliaryEditorPartState {
interface IAuxiliaryEditorPartState extends IAuxiliaryWindowOpenOptions {
readonly state: IEditorPartUIState;
readonly bounds?: IRectangle;
readonly zoomLevel?: number;
}

export class EditorParts extends MultiWindowParts<EditorPart> implements IEditorGroupsService, IEditorPartsView {
Expand All @@ -44,7 +40,8 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IAuxiliaryWindowService private readonly auxiliaryWindowService: IAuxiliaryWindowService
) {
super('workbench.editorParts', themeService, storageService);

Expand Down Expand Up @@ -242,29 +239,11 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor
private createState(): IEditorPartsUIState {
return {
auxiliary: this.parts.filter(part => part !== this.mainPart).map(part => {
const auxiliaryWindow = this.auxiliaryWindowService.getWindow(part.windowId);

return {
state: part.createState(),
bounds: (() => {
const auxiliaryWindow = getWindow(part.getContainer());
if (auxiliaryWindow) {
return {
x: auxiliaryWindow.screenX,
y: auxiliaryWindow.screenY,
width: auxiliaryWindow.outerWidth,
height: auxiliaryWindow.outerHeight
};
}

return undefined;
})(),
zoomLevel: (() => {
const auxiliaryWindow = getWindow(part.getContainer());
if (auxiliaryWindow) {
return getZoomLevel(auxiliaryWindow);
}

return undefined;
})()
...auxiliaryWindow?.createState()
};
}),
mru: this.mostRecentActiveParts.map(part => this.parts.indexOf(part))
Expand All @@ -277,11 +256,7 @@ export class EditorParts extends MultiWindowParts<EditorPart> implements IEditor

// Create auxiliary editor parts
for (const auxiliaryEditorPartState of state.auxiliary) {
auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart({
bounds: auxiliaryEditorPartState.bounds,
state: auxiliaryEditorPartState.state,
zoomLevel: auxiliaryEditorPartState.zoomLevel
}));
auxiliaryEditorPartPromises.push(this.createAuxiliaryEditorPart(auxiliaryEditorPartState));
}

// Await creation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Barrier } from 'vs/base/common/async';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { coalesce } from 'vs/base/common/arrays';
import { getZoomLevel } from 'vs/base/browser/browser';

export const IAuxiliaryWindowService = createDecorator<IAuxiliaryWindowService>('auxiliaryWindowService');

Expand All @@ -31,8 +33,15 @@ export interface IAuxiliaryWindowOpenEvent {
readonly disposables: DisposableStore;
}

export enum AuxiliaryWindowMode {
Maximized,
Normal,
Fullscreen
}

export interface IAuxiliaryWindowOpenOptions {
readonly bounds?: Partial<IRectangle>;
readonly mode?: AuxiliaryWindowMode;
readonly zoomLevel?: number;
}

Expand All @@ -43,6 +52,8 @@ export interface IAuxiliaryWindowService {
readonly onDidOpenAuxiliaryWindow: Event<IAuxiliaryWindowOpenEvent>;

open(options?: IAuxiliaryWindowOpenOptions): Promise<IAuxiliaryWindow>;

getWindow(windowId: number): IAuxiliaryWindow | undefined;
}

export interface BeforeAuxiliaryWindowUnloadEvent {
Expand All @@ -63,6 +74,8 @@ export interface IAuxiliaryWindow extends IDisposable {
readonly container: HTMLElement;

layout(): void;

createState(): IAuxiliaryWindowOpenOptions;
}

export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
Expand Down Expand Up @@ -180,6 +193,18 @@ export class AuxiliaryWindow extends BaseWindow implements IAuxiliaryWindow {
this._onDidLayout.fire(dimension);
}

createState(): IAuxiliaryWindowOpenOptions {
return {
bounds: {
x: this.window.screenX,
y: this.window.screenY,
width: this.window.outerWidth,
height: this.window.outerHeight
},
zoomLevel: getZoomLevel(this.window)
};
}

override dispose(): void {
if (this._store.isDisposed) {
return;
Expand Down Expand Up @@ -297,7 +322,17 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili
};
}

const auxiliaryWindow = mainWindow.open('about:blank', undefined, `popup=yes,left=${newWindowBounds.x},top=${newWindowBounds.y},width=${newWindowBounds.width},height=${newWindowBounds.height}`);
const features = coalesce([
'popup=yes',
`left=${newWindowBounds.x}`,
`top=${newWindowBounds.y}`,
`width=${newWindowBounds.width}`,
`height=${newWindowBounds.height}`,
options?.mode === AuxiliaryWindowMode.Maximized ? 'window-maximized=yes' : undefined, // non-standard property
options?.mode === AuxiliaryWindowMode.Fullscreen ? 'window-fullscreen=yes' : undefined // non-standard property
]);

const auxiliaryWindow = mainWindow.open('about:blank', undefined, features.join(','));
if (!auxiliaryWindow && isWeb) {
return (await this.dialogService.prompt({
type: Severity.Warning,
Expand Down Expand Up @@ -475,6 +510,10 @@ export class BrowserAuxiliaryWindowService extends Disposable implements IAuxili

return container;
}

getWindow(windowId: number): IAuxiliaryWindow | undefined {
return this.windows.get(windowId);
}
}

registerSingleton(IAuxiliaryWindowService, BrowserAuxiliaryWindowService, InstantiationType.Delayed);
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { localize } from 'vs/nls';
import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { AuxiliaryWindow, BrowserAuxiliaryWindowService, IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
import { AuxiliaryWindow, AuxiliaryWindowMode, BrowserAuxiliaryWindowService, IAuxiliaryWindowOpenOptions, IAuxiliaryWindowService } from 'vs/workbench/services/auxiliaryWindow/browser/auxiliaryWindowService';
import { ISandboxGlobals } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { DisposableStore } from 'vs/base/common/lifecycle';
Expand All @@ -20,9 +20,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Barrier } from 'vs/base/common/async';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { applyZoom } from 'vs/platform/window/electron-sandbox/window';
import { getZoomLevel } from 'vs/base/browser/browser';
import { getZoomLevel, isFullscreen } from 'vs/base/browser/browser';
import { getActiveWindow } from 'vs/base/browser/dom';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { isMacintosh } from 'vs/base/common/platform';

type NativeCodeWindow = CodeWindow & {
readonly vscode: ISandboxGlobals;
Expand All @@ -32,6 +33,8 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow {

private skipUnloadConfirmation = false;

private maximized = false;

constructor(
window: CodeWindow,
container: HTMLElement,
Expand All @@ -44,6 +47,30 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow {
@IDialogService private readonly dialogService: IDialogService
) {
super(window, container, stylesHaveLoaded, configurationService, hostService, environmentService);

if (!isMacintosh) {
// For now, limit this to platforms that have clear maximised
// transitions (Windows, Linux) via window buttons.
this.handleMaximizedState();
}
}

private handleMaximizedState(): void {
(async () => {
this.maximized = await this.nativeHostService.isMaximized({ targetWindowId: this.window.vscodeWindowId });
})();

this._register(this.nativeHostService.onDidMaximizeWindow(windowId => {
if (windowId === this.window.vscodeWindowId) {
this.maximized = true;
}
}));

this._register(this.nativeHostService.onDidUnmaximizeWindow(windowId => {
if (windowId === this.window.vscodeWindowId) {
this.maximized = false;
}
}));
}

protected override async handleVetoBeforeClose(e: BeforeUnloadEvent, veto: string): Promise<void> {
Expand All @@ -70,6 +97,16 @@ export class NativeAuxiliaryWindow extends AuxiliaryWindow {
e.preventDefault();
e.returnValue = true;
}

override createState(): IAuxiliaryWindowOpenOptions {
const state = super.createState();
const fullscreen = isFullscreen(this.window);
return {
...state,
bounds: this.maximized ? undefined : state.bounds, // ignore if maximized (fullscreen is not yet supported!)
mode: this.maximized ? AuxiliaryWindowMode.Maximized : fullscreen ? AuxiliaryWindowMode.Fullscreen : AuxiliaryWindowMode.Normal
};
}
}

export class NativeAuxiliaryWindowService extends BrowserAuxiliaryWindowService {
Expand Down

0 comments on commit e80ba87

Please sign in to comment.