diff --git a/src/main/index.ts b/src/main/index.ts index 59eeccbba..212f9e636 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -87,6 +87,7 @@ import { registerAppMenu, rebuildAppMenu } from './menu/register-app-menu' +import { createGpuAccelerationAboutPanelOptions } from './menu/gpu-acceleration-about-panel' import { checkForRemoteServerUpdate, checkForUpdatesFromMenu, @@ -359,10 +360,30 @@ const gpuCrashFallbackTracker = new GpuCrashFallbackTracker({ threshold: DEFAULT_GPU_CRASH_FALLBACK_THRESHOLD }) let gpuFallbackActiveThisLaunch = false +let gpuFeatureStatus: Electron.GPUFeatureStatus | null = null let localPtyStartupReady: Promise = Promise.resolve() let localPtyProviderStartupReady: Promise = Promise.resolve() const AGENT_STATE_CRASH_BREADCRUMB_MIN_INTERVAL_MS = 30_000 const isServeMode = process.argv.includes('--serve') + +function updateGpuAccelerationAboutPanel(): void { + app.setAboutPanelOptions( + createGpuAccelerationAboutPanelOptions({ + appName: app.name, + appVersion: app.getVersion(), + platform: process.platform, + gpuFallbackActive: gpuFallbackActiveThisLaunch, + gpuFeatureStatus + }) + ) +} + +app.on('gpu-info-update', () => { + gpuFeatureStatus = app.getGPUFeatureStatus() + if (app.isReady()) { + updateGpuAccelerationAboutPanel() + } +}) if (isServeMode) { reserveServeStdoutForReadiness() } @@ -1968,6 +1989,7 @@ void app.whenReady().then(async () => { electronApp.setAppUserModelId(devInstanceIdentity.appUserModelId) // Why: setName drives the macOS safeStorage Keychain item name; use the stable appName (not per-branch `name`) so dev branches share one key and don't re-prompt. app.setName(devInstanceIdentity.appName) + updateGpuAccelerationAboutPanel() // Why: managed WSL launchers live outside the Windows app bundle, so keep their launcher/bridge contract synced across app updates. managedWslCliReconciliationStatus = 'pending' diff --git a/src/main/menu/gpu-acceleration-about-panel.test.ts b/src/main/menu/gpu-acceleration-about-panel.test.ts new file mode 100644 index 000000000..2102460fe --- /dev/null +++ b/src/main/menu/gpu-acceleration-about-panel.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest' +import { + createGpuAccelerationAboutPanelOptions, + describeGpuAcceleration +} from './gpu-acceleration-about-panel' + +describe('GPU acceleration About panel', () => { + it.each([ + ['enabled', 'Enabled'], + ['disabled_off', 'Disabled'], + ['disabled_software', 'Software rendering'], + ['unavailable_software', 'Software rendering'], + ['unavailable', 'Unavailable'], + ['undefined', 'Status unavailable'] + ])('describes Chromium compositing status %s', (gpu_compositing, expected) => { + expect(describeGpuAcceleration({ gpu_compositing }, false)).toBe(expected) + }) + + it('gives Safe Graphics Mode precedence over Chromium status', () => { + expect(describeGpuAcceleration({ gpu_compositing: 'enabled' }, true)).toBe( + 'Disabled (Safe Graphics Mode)' + ) + }) + + it('reports status as unavailable until Chromium publishes GPU information', () => { + expect(describeGpuAcceleration(null, false)).toBe('Status unavailable') + }) + + it('uses the Linux-visible copyright field', () => { + expect( + createGpuAccelerationAboutPanelOptions({ + appName: 'Orca', + appVersion: '1.2.3', + platform: 'linux', + gpuFallbackActive: false, + gpuFeatureStatus: { gpu_compositing: 'enabled' } + }) + ).toEqual({ + applicationName: 'Orca', + applicationVersion: '1.2.3', + copyright: 'GPU acceleration: Enabled' + }) + }) + + it.each(['darwin', 'win32'] satisfies NodeJS.Platform[])('uses credits on %s', (platform) => { + expect( + createGpuAccelerationAboutPanelOptions({ + appName: 'Orca', + appVersion: '1.2.3', + platform, + gpuFallbackActive: true, + gpuFeatureStatus: null + }) + ).toEqual({ + applicationName: 'Orca', + applicationVersion: '1.2.3', + credits: 'GPU acceleration: Disabled (Safe Graphics Mode)' + }) + }) +}) diff --git a/src/main/menu/gpu-acceleration-about-panel.ts b/src/main/menu/gpu-acceleration-about-panel.ts new file mode 100644 index 000000000..340897ce2 --- /dev/null +++ b/src/main/menu/gpu-acceleration-about-panel.ts @@ -0,0 +1,49 @@ +export type GpuAccelerationAboutPanelOptions = { + appName: string + appVersion: string + platform: NodeJS.Platform + gpuFallbackActive: boolean + gpuFeatureStatus: Pick | null +} + +export function describeGpuAcceleration( + gpuFeatureStatus: Pick | null, + gpuFallbackActive: boolean +): string { + if (gpuFallbackActive) { + return 'Disabled (Safe Graphics Mode)' + } + + const compositing = gpuFeatureStatus?.gpu_compositing.trim().toLowerCase() + if (!compositing || compositing === 'undefined') { + return 'Status unavailable' + } + if (compositing === 'enabled') { + return 'Enabled' + } + if (compositing.includes('software')) { + return 'Software rendering' + } + if (compositing.startsWith('disabled')) { + return 'Disabled' + } + if (compositing.startsWith('unavailable')) { + return 'Unavailable' + } + return `Unknown (${compositing})` +} + +export function createGpuAccelerationAboutPanelOptions({ + appName, + appVersion, + platform, + gpuFallbackActive, + gpuFeatureStatus +}: GpuAccelerationAboutPanelOptions): Electron.AboutPanelOptionsOptions { + const status = `GPU acceleration: ${describeGpuAcceleration(gpuFeatureStatus, gpuFallbackActive)}` + return { + applicationName: appName, + applicationVersion: appVersion, + ...(platform === 'linux' ? { copyright: status } : { credits: status }) + } +}