From b7f437d2a1974e160f3ec00b9e72526d27bc4fb9 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 6 Jan 2025 15:37:07 +0100 Subject: [PATCH] Allow custom titlebar on Linux as experiment (microsoft/vscode-internalbacklog#4857) (#237337) --- src/vs/code/electron-main/app.ts | 12 +++++-- src/vs/platform/native/common/native.ts | 9 +++-- .../electron-main/nativeHostMainService.ts | 16 +++++++-- src/vs/platform/window/common/window.ts | 20 ++++++++++- .../electron-sandbox/desktop.contribution.ts | 1 + .../parts/titlebar/titlebarPart.ts | 34 +++++++++++++++++-- .../electron-sandbox/workbenchTestServices.ts | 1 + 7 files changed, 81 insertions(+), 12 deletions(-) diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 11ca66e8767..7c7fc636e21 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -83,7 +83,7 @@ import { NativeURLService } from '../../platform/url/common/urlService.js'; import { ElectronURLListener } from '../../platform/url/electron-main/electronUrlListener.js'; import { IWebviewManagerService } from '../../platform/webview/common/webviewManagerService.js'; import { WebviewMainService } from '../../platform/webview/electron-main/webviewMainService.js'; -import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable } from '../../platform/window/common/window.js'; +import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, TitlebarStyle, overrideDefaultTitlebarStyle } from '../../platform/window/common/window.js'; import { IWindowsMainService, OpenContext } from '../../platform/windows/electron-main/windows.js'; import { ICodeWindow } from '../../platform/window/electron-main/window.js'; import { WindowsMainService } from '../../platform/windows/electron-main/windowsMainService.js'; @@ -593,6 +593,14 @@ export class CodeApplication extends Disposable { // Services const appInstantiationService = await this.initServices(machineId, sqmId, devDeviceId, sharedProcessReady); + // Linux (stable only): custom title default style override + if (isLinux && this.productService.quality === 'stable') { + const titleBarDefaultStyleOverride = this.stateService.getItem('window.titleBarStyleOverride'); + if (titleBarDefaultStyleOverride === TitlebarStyle.CUSTOM || titleBarDefaultStyleOverride === TitlebarStyle.NATIVE) { + overrideDefaultTitlebarStyle(titleBarDefaultStyleOverride); + } + } + // Auth Handler appInstantiationService.invokeFunction(accessor => accessor.get(IProxyAuthService)); @@ -605,7 +613,7 @@ export class CodeApplication extends Disposable { // Setup Protocol URL Handlers const initialProtocolUrls = await appInstantiationService.invokeFunction(accessor => this.setupProtocolUrlHandlers(accessor, mainProcessElectronServer)); - // Setup vscode-remote-resource protocol handler. + // Setup vscode-remote-resource protocol handler this.setupManagedRemoteResourceUrlHandler(mainProcessElectronServer); // Signal phase: ready - before opening first window diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 2fdfb63e0b5..1f8d0adabe7 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -113,6 +113,9 @@ export interface ICommonNativeHostService { */ focusWindow(options?: INativeHostOptions & { force?: boolean }): Promise; + // Titlebar default style override + overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): Promise; + // Dialogs showMessageBox(options: MessageBoxOptions & INativeHostOptions): Promise; showSaveDialog(options: SaveDialogOptions & INativeHostOptions): Promise; @@ -143,10 +146,6 @@ export interface ICommonNativeHostService { hasWSLFeatureInstalled(): Promise; // Screenshots - - /** - * Gets a screenshot of the currently active Electron window. - */ getScreenshot(): Promise; // Process @@ -199,7 +198,7 @@ export interface ICommonNativeHostService { loadCertificates(): Promise; findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride?: number): Promise; - // Registry (windows only) + // Registry (Windows only) windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise; } diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 794c5aed575..8e5c129ea4f 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -33,7 +33,7 @@ import { IProductService } from '../../product/common/productService.js'; import { IPartsSplash } from '../../theme/common/themeService.js'; import { IThemeMainService } from '../../theme/electron-main/themeMainService.js'; import { defaultWindowState, ICodeWindow } from '../../window/electron-main/window.js'; -import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } from '../../window/common/window.js'; +import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable, overrideDefaultTitlebarStyle } from '../../window/common/window.js'; import { defaultBrowserWindowOptions, IWindowsMainService, OpenContext } from '../../windows/electron-main/windows.js'; import { isWorkspaceIdentifier, toWorkspaceIdentifier } from '../../workspace/common/workspace.js'; import { IWorkspacesManagementMainService } from '../../workspaces/electron-main/workspacesManagementMainService.js'; @@ -48,6 +48,7 @@ import { IConfigurationService } from '../../configuration/common/configuration. import { IProxyAuthService } from './auth.js'; import { AuthInfo, Credentials, IRequestService } from '../../request/common/request.js'; import { randomPath } from '../../../base/common/extpath.js'; +import { IStateService } from '../../state/node/state.js'; export interface INativeHostMainService extends AddFirstParameterToFunctions /* only methods, not events */, number | undefined /* window ID */> { } @@ -70,7 +71,8 @@ export class NativeHostMainService extends Disposable implements INativeHostMain @IConfigurationService private readonly configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, @IProxyAuthService private readonly proxyAuthService: IProxyAuthService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IStateService private readonly stateService: IStateService ) { super(); } @@ -324,6 +326,15 @@ export class NativeHostMainService extends Disposable implements INativeHostMain this.themeMainService.saveWindowSplash(windowId, splash); } + async overrideDefaultTitlebarStyle(windowId: number | undefined, style: 'native' | 'custom' | undefined): Promise { + if (typeof style === 'string') { + this.stateService.setItem('window.titleBarStyleOverride', style); + } else { + this.stateService.removeItem('window.titleBarStyleOverride'); + } + overrideDefaultTitlebarStyle(style); + } + //#endregion @@ -697,6 +708,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain async getScreenshot(windowId: number | undefined, options?: INativeHostOptions): Promise { const window = this.windowById(options?.targetWindowId, windowId); const captured = await window?.win?.webContents.capturePage(); + return captured?.toJPEG(95); } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 3a03481e55a..02e4152376d 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -187,10 +187,23 @@ export const enum CustomTitleBarVisibility { NEVER = 'never', } +export let titlebarStyleDefaultOverride: TitlebarStyle | undefined = undefined; +export function overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): void { + switch (style) { + case 'native': + titlebarStyleDefaultOverride = TitlebarStyle.NATIVE; + break; + case 'custom': + titlebarStyleDefaultOverride = TitlebarStyle.CUSTOM; + break; + default: + titlebarStyleDefaultOverride = undefined; + } +} + export function hasCustomTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean { // Returns if it possible to have a custom title bar in the curren session // Does not imply that the title bar is visible - return true; } @@ -198,6 +211,7 @@ export function hasNativeTitlebar(configurationService: IConfigurationService, t if (!titleBarStyle) { titleBarStyle = getTitleBarStyle(configurationService); } + return titleBarStyle === TitlebarStyle.NATIVE; } @@ -224,6 +238,10 @@ export function getTitleBarStyle(configurationService: IConfigurationService): T } } + if (titlebarStyleDefaultOverride) { + return titlebarStyleDefaultOverride; + } + return isLinux && product.quality === 'stable' ? TitlebarStyle.NATIVE : TitlebarStyle.CUSTOM; // default to custom on all OS except Linux stable (for now) } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 19a8cf11e84..da72519dee3 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -234,6 +234,7 @@ import product from '../../platform/product/common/product.js'; 'type': 'string', 'enum': ['native', 'custom'], 'default': isLinux && product.quality === 'stable' ? 'native' : 'custom', + 'tags': isLinux && product.quality === 'stable' ? ['onExP'] : undefined, 'scope': ConfigurationScope.APPLICATION, 'description': localize('titleBarStyle', "Adjust the appearance of the window title bar to be native by the OS or custom. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply."), }, diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 04095aab5d7..0d7a06de173 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -27,6 +27,7 @@ import { IEditorGroupsContainer, IEditorGroupsService } from '../../../services/ import { IEditorService } from '../../../services/editor/common/editorService.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { CodeWindow, mainWindow } from '../../../../base/browser/window.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; export class NativeTitlebarPart extends BrowserTitlebarPart { @@ -70,7 +71,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, - @INativeHostService private readonly nativeHostService: INativeHostService, + @INativeHostService protected readonly nativeHostService: INativeHostService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @@ -287,9 +288,38 @@ export class MainNativeTitlebarPart extends NativeTitlebarPart { @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, - @IKeybindingService keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService ) { super(Parts.TITLEBAR_PART, mainWindow, 'main', contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService); + + if (isLinux && productService.quality === 'stable') { + this.handleDefaultTitlebarStyle(); // TODO@bpasero remove me eventually once settled + } + } + + private handleDefaultTitlebarStyle(): void { + this.updateDefaultTitlebarStyle(); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('window.titleBarStyle')) { + this.updateDefaultTitlebarStyle(); + } + })); + } + + private updateDefaultTitlebarStyle(): void { + const titleBarStyle = this.configurationService.inspect('window.titleBarStyle'); + + let titleBarStyleOverride: 'custom' | undefined; + if (titleBarStyle.applicationValue || titleBarStyle.userValue || titleBarStyle.userLocalValue) { + // configured by user or application: clear override + titleBarStyleOverride = undefined; + } else { + // not configured: set override if experiment is active + titleBarStyleOverride = titleBarStyle.defaultValue === 'native' ? undefined : 'custom'; + } + + this.nativeHostService.overrideDefaultTitlebarStyle(titleBarStyleOverride); } } diff --git a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index 9bc958141ff..ecec6ce983d 100644 --- a/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -162,6 +162,7 @@ export class TestNativeHostService implements INativeHostService { async windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise { return undefined; } async profileRenderer(): Promise { throw new Error(); } async getScreenshot(): Promise { return undefined; } + async overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): Promise { } } export class TestExtensionTipsService extends AbstractNativeExtensionTipsService {