diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7c7fc636e21..47481c0de3f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -596,7 +596,7 @@ export class CodeApplication extends Disposable { // 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) { + if (titleBarDefaultStyleOverride === TitlebarStyle.CUSTOM) { overrideDefaultTitlebarStyle(titleBarDefaultStyleOverride); } } diff --git a/src/vs/platform/native/common/native.ts b/src/vs/platform/native/common/native.ts index 1f8d0adabe7..13141d89063 100644 --- a/src/vs/platform/native/common/native.ts +++ b/src/vs/platform/native/common/native.ts @@ -114,7 +114,7 @@ export interface ICommonNativeHostService { focusWindow(options?: INativeHostOptions & { force?: boolean }): Promise; // Titlebar default style override - overrideDefaultTitlebarStyle(style: 'native' | 'custom' | undefined): Promise; + overrideDefaultTitlebarStyle(style: 'custom' | undefined): Promise; // Dialogs showMessageBox(options: MessageBoxOptions & INativeHostOptions): Promise; diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 8e5c129ea4f..030b6a27506 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, overrideDefaultTitlebarStyle } from '../../window/common/window.js'; +import { IColorScheme, IOpenedAuxiliaryWindow, IOpenedMainWindow, IOpenEmptyWindowOptions, IOpenWindowOptions, IPoint, IRectangle, IWindowOpenable } 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'; @@ -326,13 +326,12 @@ 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') { + async overrideDefaultTitlebarStyle(windowId: number | undefined, style: 'custom' | undefined): Promise { + if (style === 'custom') { this.stateService.setItem('window.titleBarStyleOverride', style); } else { this.stateService.removeItem('window.titleBarStyleOverride'); } - overrideDefaultTitlebarStyle(style); } //#endregion diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index 02e4152376d..cec025d6632 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -187,18 +187,9 @@ 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 let titlebarStyleDefaultOverride: 'custom' | undefined = undefined; +export function overrideDefaultTitlebarStyle(style: 'custom'): void { + titlebarStyleDefaultOverride = style; } export function hasCustomTitlebar(configurationService: IConfigurationService, titleBarStyle?: TitlebarStyle): boolean { @@ -238,8 +229,8 @@ export function getTitleBarStyle(configurationService: IConfigurationService): T } } - if (titlebarStyleDefaultOverride) { - return titlebarStyleDefaultOverride; + if (titlebarStyleDefaultOverride === 'custom') { + return TitlebarStyle.CUSTOM; } return isLinux && product.quality === 'stable' ? TitlebarStyle.NATIVE : TitlebarStyle.CUSTOM; // default to custom on all OS except Linux stable (for now) @@ -413,6 +404,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native autoDetectHighContrast?: boolean; autoDetectColorScheme?: boolean; isCustomZoomLevel?: boolean; + overrideDefaultTitlebarStyle?: 'custom'; perfMarks: PerformanceMark[]; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 9999cd86776..b47f1a17cb4 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -37,7 +37,7 @@ import product from '../../product/common/product.js'; import { IProtocolMainService } from '../../protocol/electron-main/protocol.js'; import { getRemoteAuthority } from '../../remote/common/remoteHosts.js'; import { IStateService } from '../../state/node/state.js'; -import { IAddRemoveFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings } from '../../window/common/window.js'; +import { IAddRemoveFoldersRequest, INativeOpenFileRequest, INativeWindowConfiguration, IOpenEmptyWindowOptions, IPath, IPathsToWaitFor, isFileToOpen, isFolderToOpen, isWorkspaceToOpen, IWindowOpenable, IWindowSettings, titlebarStyleDefaultOverride } from '../../window/common/window.js'; import { CodeWindow } from './windowImpl.js'; import { IOpenConfiguration, IOpenEmptyConfiguration, IWindowsCountChangedEvent, IWindowsMainService, OpenContext, getLastFocused } from './windows.js'; import { findWindowOnExtensionDevelopmentPath, findWindowOnFile, findWindowOnWorkspaceOrFolder } from './windowsFinder.js'; @@ -1507,6 +1507,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic autoDetectHighContrast: windowConfig?.autoDetectHighContrast ?? true, autoDetectColorScheme: windowConfig?.autoDetectColorScheme ?? false, + overrideDefaultTitlebarStyle: titlebarStyleDefaultOverride, accessibilitySupport: app.accessibilitySupportEnabled, colorScheme: this.themeMainService.getColorScheme(), policiesData: this.policyService.serialize(), diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index da72519dee3..8681fcd2c7e 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -29,6 +29,8 @@ import { ModifierKeyEmitter } from '../../base/browser/dom.js'; import { applicationConfigurationNodeBase, securityConfigurationNodeBase } from '../common/configuration.js'; import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../platform/window/electron-sandbox/window.js'; import product from '../../platform/product/common/product.js'; +import { registerWorkbenchContribution2, WorkbenchPhase } from '../common/contributions.js'; +import { LinuxCustomTitlebarExperiment } from './parts/titlebar/titlebarPart.js'; // Actions (function registerActions(): void { @@ -234,7 +236,6 @@ 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."), }, @@ -424,3 +425,6 @@ import product from '../../platform/product/common/product.js'; jsonRegistry.registerSchema(argvDefinitionFileSchemaId, schema); })(); + +// Workbench Contributions +registerWorkbenchContribution2(LinuxCustomTitlebarExperiment.ID, LinuxCustomTitlebarExperiment, WorkbenchPhase.Eventually); diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 40cfd9c7ec3..10527541426 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -45,7 +45,7 @@ import { WorkspaceTrustEnablementService, WorkspaceTrustManagementService } from import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from '../../platform/workspace/common/workspaceTrust.js'; import { safeStringify } from '../../base/common/objects.js'; import { IUtilityProcessWorkerWorkbenchService, UtilityProcessWorkerWorkbenchService } from '../services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.js'; -import { isBigSurOrNewer, isCI, isMacintosh } from '../../base/common/platform.js'; +import { isBigSurOrNewer, isCI, isLinux, isMacintosh } from '../../base/common/platform.js'; import { Schemas } from '../../base/common/network.js'; import { DiskFileSystemProvider } from '../services/files/electron-sandbox/diskFileSystemProvider.js'; import { FileUserDataProvider } from '../../platform/userData/common/fileUserDataProvider.js'; @@ -61,6 +61,8 @@ import { ElectronRemoteResourceLoader } from '../../platform/remote/electron-san import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { applyZoom } from '../../platform/window/electron-sandbox/window.js'; import { mainWindow } from '../../base/browser/window.js'; +import { Registry } from '../../platform/registry/common/platform.js'; +import { IConfigurationRegistry, Extensions } from '../../platform/configuration/common/configurationRegistry.js'; export class DesktopMain extends Disposable { @@ -79,6 +81,12 @@ export class DesktopMain extends Disposable { // Apply fullscreen early if configured setFullscreen(!!this.configuration.fullscreen, mainWindow); + + // Apply custom title override to defaults if any + if (isLinux && product.quality === 'stable' && this.configuration.overrideDefaultTitlebarStyle === 'custom') { + const configurationRegistry = Registry.as(Extensions.Configuration); + configurationRegistry.registerDefaultConfigurations([{ overrides: { 'window.titleBarStyle': 'custom' } }]); + } } private reviveUris() { diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 0d7a06de173..fa989d363bd 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -28,6 +28,9 @@ 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'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; export class NativeTitlebarPart extends BrowserTitlebarPart { @@ -71,7 +74,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, - @INativeHostService protected readonly nativeHostService: INativeHostService, + @INativeHostService private readonly nativeHostService: INativeHostService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, @@ -288,38 +291,9 @@ export class MainNativeTitlebarPart extends NativeTitlebarPart { @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, @IMenuService menuService: IMenuService, - @IKeybindingService keybindingService: IKeybindingService, - @IProductService productService: IProductService + @IKeybindingService keybindingService: IKeybindingService ) { 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); } } @@ -374,3 +348,50 @@ export class NativeTitleService extends BrowserTitleService { return this.instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart); } } + +export class LinuxCustomTitlebarExperiment extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.linuxCustomTitlebarExperiment'; + + private readonly treatment = this.assignmentService.getTreatment('config.window.experimentalTitleBarStyle'); + + constructor( + @IProductService productService: IProductService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @INativeHostService private readonly nativeHostService: INativeHostService, + @IWorkbenchAssignmentService private readonly assignmentService: IWorkbenchAssignmentService + ) { + super(); + + 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 async updateDefaultTitlebarStyle(): Promise { + const titleBarStyle = this.configurationService.inspect('window.titleBarStyle'); + + let titleBarStyleOverride: 'custom' | undefined; + if (titleBarStyle.applicationValue || titleBarStyle.userValue) { + // configured by user or application: clear override + titleBarStyleOverride = undefined; + } else { + // not configured: set override if experiment is active + const value = await this.treatment; + if (value === 'custom') { + titleBarStyleOverride = 'custom'; + } + } + + await this.nativeHostService.overrideDefaultTitlebarStyle(titleBarStyleOverride); + } +}