diff --git a/apps/desktop/src/application-menu.test.ts b/apps/desktop/src/application-menu.test.ts new file mode 100644 index 000000000..723ee23ce --- /dev/null +++ b/apps/desktop/src/application-menu.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { configureApplicationMenu } from './application-menu'; + +describe('desktop application menu', () => { + it('removes Electron\'s stock menu from packaged Linux builds', () => { + const configuredMenus: null[] = []; + + configureApplicationMenu({ + setApplicationMenu(menu) { + configuredMenus.push(menu); + }, + }, true, 'linux'); + + assert.deepEqual(configuredMenus, [null]); + }); + + it('retains the development menu and native menus on other platforms', () => { + for (const scenario of [ + { isPackaged: false, platform: 'linux' as const }, + { isPackaged: true, platform: 'darwin' as const }, + { isPackaged: true, platform: 'win32' as const }, + ]) { + let configured = false; + configureApplicationMenu({ + setApplicationMenu() { + configured = true; + }, + }, scenario.isPackaged, scenario.platform); + assert.equal(configured, false, JSON.stringify(scenario)); + } + }); +}); diff --git a/apps/desktop/src/application-menu.ts b/apps/desktop/src/application-menu.ts new file mode 100644 index 000000000..66da24dd9 --- /dev/null +++ b/apps/desktop/src/application-menu.ts @@ -0,0 +1,13 @@ +type ApplicationMenuController = { + setApplicationMenu: (menu: null) => void; +}; + +export const configureApplicationMenu = ( + menu: ApplicationMenuController, + isPackaged: boolean, + platform: NodeJS.Platform = process.platform, +): void => { + if (isPackaged && platform === 'linux') { + menu.setApplicationMenu(null); + } +}; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 2bdb7f961..4fd7de2b2 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -1,10 +1,11 @@ import { lstatSync } from 'node:fs'; import { isAbsolute, join, relative, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; -import { app, BrowserWindow, dialog, ipcMain, net, protocol, safeStorage, screen, session, shell } from 'electron'; +import { app, BrowserWindow, dialog, ipcMain, Menu, net, protocol, safeStorage, screen, session, shell } from 'electron'; import type { Rectangle } from 'electron'; import { DESKTOP_RENDERER_ORIGIN } from '@propr/shared'; import type { SetupActions } from '@propr/local-setup'; +import { configureApplicationMenu } from './application-menu'; import { DeepLinkDelivery } from './deep-link-delivery'; import { DesktopCredentialService } from './credential-service'; import { createDesktopLocalHost } from './desktop-host'; @@ -427,6 +428,7 @@ if (!hasSingleInstanceLock) { registerProtocolClient(); void app.whenReady().then(async () => { + configureApplicationMenu(Menu, app.isPackaged); logger = createDesktopLogger( join(app.getPath('logs'), 'desktop.jsonl'), () => packagedSmokeEvidence?.write('desktop.log.write_failed'),