Path: blob/main/src/vs/workbench/electron-browser/parts/titlebar/menubarControl.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 { IAction, Separator } from '../../../../base/common/actions.js';6import { IMenuService, SubmenuItemAction, MenuItemAction } from '../../../../platform/actions/common/actions.js';7import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';8import { IWorkspacesService } from '../../../../platform/workspaces/common/workspaces.js';9import { isMacintosh } from '../../../../base/common/platform.js';10import { INotificationService } from '../../../../platform/notification/common/notification.js';11import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';12import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';13import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';14import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';15import { ILabelService } from '../../../../platform/label/common/label.js';16import { IUpdateService } from '../../../../platform/update/common/update.js';17import { IOpenRecentAction, MenubarControl } from '../../../browser/parts/titlebar/menubarControl.js';18import { IStorageService } from '../../../../platform/storage/common/storage.js';19import { IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from '../../../../platform/menubar/common/menubar.js';20import { IMenubarService } from '../../../../platform/menubar/electron-browser/menubar.js';21import { INativeHostService } from '../../../../platform/native/common/native.js';22import { IHostService } from '../../../services/host/browser/host.js';23import { IPreferencesService } from '../../../services/preferences/common/preferences.js';24import { ICommandService } from '../../../../platform/commands/common/commands.js';25import { OpenRecentAction } from '../../../browser/actions/windowActions.js';26import { isICommandActionToggleInfo } from '../../../../platform/action/common/action.js';27import { getFlatContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';2829export class NativeMenubarControl extends MenubarControl {3031constructor(32@IMenuService menuService: IMenuService,33@IWorkspacesService workspacesService: IWorkspacesService,34@IContextKeyService contextKeyService: IContextKeyService,35@IKeybindingService keybindingService: IKeybindingService,36@IConfigurationService configurationService: IConfigurationService,37@ILabelService labelService: ILabelService,38@IUpdateService updateService: IUpdateService,39@IStorageService storageService: IStorageService,40@INotificationService notificationService: INotificationService,41@IPreferencesService preferencesService: IPreferencesService,42@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,43@IAccessibilityService accessibilityService: IAccessibilityService,44@IMenubarService private readonly menubarService: IMenubarService,45@IHostService hostService: IHostService,46@INativeHostService private readonly nativeHostService: INativeHostService,47@ICommandService commandService: ICommandService,48) {49super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);5051(async () => {52this.recentlyOpened = await this.workspacesService.getRecentlyOpened();5354this.doUpdateMenubar();55})();5657this.registerListeners();58}5960protected override setupMainMenu(): void {61super.setupMainMenu();6263for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {64const menu = this.menus[topLevelMenuName];65if (menu) {66this.mainMenuDisposables.add(menu.onDidChange(() => this.updateMenubar()));67}68}69}7071protected doUpdateMenubar(): void {72// Since the native menubar is shared between windows (main process)73// only allow the focused window to update the menubar74if (!this.hostService.hasFocus) {75return;76}7778// Send menus to main process to be rendered by Electron79const menubarData = { menus: {}, keybindings: {} };80if (this.getMenubarMenus(menubarData)) {81this.menubarService.updateMenubar(this.nativeHostService.windowId, menubarData);82}83}8485private getMenubarMenus(menubarData: IMenubarData): boolean {86if (!menubarData) {87return false;88}8990menubarData.keybindings = this.getAdditionalKeybindings();91for (const topLevelMenuName of Object.keys(this.topLevelTitles)) {92const menu = this.menus[topLevelMenuName];93if (menu) {94const menubarMenu: IMenubarMenu = { items: [] };95const menuActions = getFlatContextMenuActions(menu.getActions({ shouldForwardArgs: true }));96this.populateMenuItems(menuActions, menubarMenu, menubarData.keybindings);97if (menubarMenu.items.length === 0) {98return false; // Menus are incomplete99}100menubarData.menus[topLevelMenuName] = menubarMenu;101}102}103104return true;105}106107private populateMenuItems(menuActions: readonly IAction[], menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) {108for (const menuItem of menuActions) {109if (menuItem instanceof Separator) {110menuToPopulate.items.push({ id: 'vscode.menubar.separator' });111} else if (menuItem instanceof MenuItemAction || menuItem instanceof SubmenuItemAction) {112113// use mnemonicTitle whenever possible114const title = typeof menuItem.item.title === 'string'115? menuItem.item.title116: menuItem.item.title.mnemonicTitle ?? menuItem.item.title.value;117118if (menuItem instanceof SubmenuItemAction) {119const submenu = { items: [] };120121this.populateMenuItems(menuItem.actions, submenu, keybindings);122123if (submenu.items.length > 0) {124const menubarSubmenuItem: IMenubarMenuItemSubmenu = {125id: menuItem.id,126label: title,127submenu128};129130menuToPopulate.items.push(menubarSubmenuItem);131}132} else {133if (menuItem.id === OpenRecentAction.ID) {134const actions = this.getOpenRecentActions().map(this.transformOpenRecentAction);135menuToPopulate.items.push(...actions);136}137138const menubarMenuItem: IMenubarMenuItemAction = {139id: menuItem.id,140label: title141};142143if (isICommandActionToggleInfo(menuItem.item.toggled)) {144menubarMenuItem.label = menuItem.item.toggled.mnemonicTitle ?? menuItem.item.toggled.title ?? title;145}146147if (menuItem.checked) {148menubarMenuItem.checked = true;149}150151if (!menuItem.enabled) {152menubarMenuItem.enabled = false;153}154155keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id);156menuToPopulate.items.push(menubarMenuItem);157}158}159}160}161162private transformOpenRecentAction(action: Separator | IOpenRecentAction): MenubarMenuItem {163if (action instanceof Separator) {164return { id: 'vscode.menubar.separator' };165}166167return {168id: action.id,169uri: action.uri,170remoteAuthority: action.remoteAuthority,171enabled: action.enabled,172label: action.label173};174}175176private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } {177const keybindings: { [id: string]: IMenubarKeybinding } = {};178if (isMacintosh) {179const keybinding = this.getMenubarKeybinding('workbench.action.quit');180if (keybinding) {181keybindings['workbench.action.quit'] = keybinding;182}183}184185return keybindings;186}187188private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined {189const binding = this.keybindingService.lookupKeybinding(id);190if (!binding) {191return undefined;192}193194// first try to resolve a native accelerator195const electronAccelerator = binding.getElectronAccelerator();196if (electronAccelerator) {197return { label: electronAccelerator, userSettingsLabel: binding.getUserSettingsLabel() ?? undefined };198}199200// we need this fallback to support keybindings that cannot show in electron menus (e.g. chords)201const acceleratorLabel = binding.getLabel();202if (acceleratorLabel) {203return { label: acceleratorLabel, isNative: false, userSettingsLabel: binding.getUserSettingsLabel() ?? undefined };204}205206return undefined;207}208}209210211