Skip to content

Commit 2e0f88d

Browse files
authored
fix(desktop): restore linux launcher identity (anomalyco#31709)
1 parent e1073e5 commit 2e0f88d

3 files changed

Lines changed: 95 additions & 8 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { expect, test } from "bun:test"
2+
import type { Configuration } from "electron-builder"
3+
4+
const legacyDesktopEntry = "resources/linux/opencode-desktop.desktop"
5+
6+
const channels = [
7+
{ channel: "dev", appId: "ai.opencode.desktop.dev" },
8+
{ channel: "beta", appId: "ai.opencode.desktop.beta" },
9+
{ channel: "prod", appId: "ai.opencode.desktop" },
10+
] as const
11+
12+
for (const channel of channels) {
13+
test(`uses one Linux desktop identity for ${channel.channel}`, async () => {
14+
const previous = process.env.OPENCODE_CHANNEL
15+
process.env.OPENCODE_CHANNEL = channel.channel
16+
17+
const module = await import(`./electron-builder.config.ts?channel=${channel.channel}`)
18+
const config = module.default as Configuration
19+
20+
if (previous === undefined) delete process.env.OPENCODE_CHANNEL
21+
else process.env.OPENCODE_CHANNEL = previous
22+
23+
expect(config.appId).toBe(channel.appId)
24+
expect(config.extraMetadata?.desktopName).toBe(`${channel.appId}.desktop`)
25+
expect(config.linux?.executableName).toBe(channel.appId)
26+
expect(config.linux?.desktop?.entry?.StartupWMClass).toBe(channel.appId)
27+
})
28+
}
29+
30+
test("keeps a hidden prod launcher for old Linux pins", async () => {
31+
const previous = process.env.OPENCODE_CHANNEL
32+
process.env.OPENCODE_CHANNEL = "prod"
33+
34+
const module = await import("./electron-builder.config.ts?compat=prod")
35+
const config = module.default as Configuration
36+
37+
if (previous === undefined) delete process.env.OPENCODE_CHANNEL
38+
else process.env.OPENCODE_CHANNEL = previous
39+
40+
expect(config.deb?.fpm?.[0]).toEndWith(`${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop`)
41+
expect(config.rpm?.fpm?.[0]).toEndWith(`${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop`)
42+
43+
const desktop = await Bun.file(legacyDesktopEntry).text()
44+
expect(desktop).toContain("Exec=/opt/OpenCode/ai.opencode.desktop %U")
45+
expect(desktop).toContain("Icon=ai.opencode.desktop")
46+
expect(desktop).toContain("StartupWMClass=ai.opencode.desktop")
47+
expect(desktop).toContain("NoDisplay=true")
48+
})

packages/desktop/electron-builder.config.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ import { promisify } from "node:util"
66
import type { Configuration } from "electron-builder"
77

88
const execFileAsync = promisify(execFile)
9-
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")
9+
const packageDir = path.dirname(fileURLToPath(import.meta.url))
10+
const rootDir = path.resolve(packageDir, "../..")
1011
const signScript = path.join(rootDir, "script", "sign-windows.ps1")
12+
// The Electron 42 packaging update briefly installed Linux launchers/icons under
13+
// "opencode-desktop". Keep that hidden desktop entry around so existing GNOME/KDE
14+
// pins still resolve after the canonical app id changes back to ai.opencode.desktop.
15+
const legacyDesktopEntry = path.join(packageDir, "resources", "linux", "opencode-desktop.desktop")
16+
const legacyDesktopEntryFpm = `${legacyDesktopEntry}=/usr/share/applications/opencode-desktop.desktop`
1117

1218
async function signWindows(configuration: { path: string }) {
1319
if (process.platform !== "win32") return
@@ -26,12 +32,26 @@ const channel = (() => {
2632
return "dev"
2733
})()
2834

29-
const getBase = (): Configuration => ({
35+
const APP_IDS = {
36+
dev: "ai.opencode.desktop.dev",
37+
beta: "ai.opencode.desktop.beta",
38+
prod: "ai.opencode.desktop",
39+
} as const
40+
41+
const getBase = (appId: string): Configuration => ({
3042
artifactName: "opencode-desktop-${os}-${arch}.${ext}",
3143
directories: {
3244
output: "dist",
3345
buildResources: "resources",
3446
},
47+
// Linux launchers are .desktop files, so this is the desktop file name,
48+
// not just the app id. For prod, app id "ai.opencode.desktop" becomes
49+
// "ai.opencode.desktop.desktop".
50+
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
51+
// https://www.electron.build/docs/linux/
52+
extraMetadata: {
53+
desktopName: `${appId}.desktop`,
54+
},
3555
files: ["out/**/*", "resources/**/*"],
3656
extraResources: [
3757
{
@@ -74,27 +94,35 @@ const getBase = (): Configuration => ({
7494
linux: {
7595
icon: `resources/icons`,
7696
category: "Development",
77-
executableName: "opencode-desktop",
97+
executableName: appId,
98+
desktop: {
99+
entry: {
100+
// Match the installed .desktop file and hicolor icon basename so
101+
// Linux shells can associate the running Electron window with its launcher.
102+
StartupWMClass: appId,
103+
},
104+
},
78105
target: ["AppImage", "deb", "rpm"],
79106
},
80107
})
81108

82109
function getConfig() {
83-
const base = getBase()
110+
const appId = APP_IDS[channel]
111+
const base = getBase(appId)
84112

85113
switch (channel) {
86114
case "dev": {
87115
return {
88116
...base,
89-
appId: "ai.opencode.desktop.dev",
117+
appId,
90118
productName: "OpenCode Dev",
91119
rpm: { packageName: "opencode-dev" },
92120
}
93121
}
94122
case "beta": {
95123
return {
96124
...base,
97-
appId: "ai.opencode.desktop.beta",
125+
appId,
98126
productName: "OpenCode Beta",
99127
protocols: { name: "OpenCode Beta", schemes: ["opencode"] },
100128
publish: { provider: "github", owner: "anomalyco", repo: "opencode-beta", channel: "latest" },
@@ -104,11 +132,12 @@ function getConfig() {
104132
case "prod": {
105133
return {
106134
...base,
107-
appId: "ai.opencode.desktop",
135+
appId,
108136
productName: "OpenCode",
109137
protocols: { name: "OpenCode", schemes: ["opencode"] },
110138
publish: { provider: "github", owner: "anomalyco", repo: "opencode", channel: "latest" },
111-
rpm: { packageName: "opencode" },
139+
deb: { fpm: [legacyDesktopEntryFpm] },
140+
rpm: { packageName: "opencode", fpm: [legacyDesktopEntryFpm] },
112141
}
113142
}
114143
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[Desktop Entry]
2+
Name=OpenCode
3+
Exec=/opt/OpenCode/ai.opencode.desktop %U
4+
Terminal=false
5+
Type=Application
6+
Icon=ai.opencode.desktop
7+
StartupWMClass=ai.opencode.desktop
8+
NoDisplay=true
9+
Comment=Open source AI coding agent
10+
Categories=Development;

0 commit comments

Comments
 (0)