diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df67173..d8617ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,6 +137,13 @@ jobs: env: CSC_IDENTITY_AUTO_DISCOVERY: false + # Bash on every OS, Windows included: PowerShell splits an argument like + # `-c.win.signAndEditExecutable=true` at the dot, and electron-builder then reads + # `.win.…` as the path of a config file. + defaults: + run: + shell: bash + steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -149,5 +156,7 @@ jobs: - name: Install dependencies run: npm ci + # With the .exe editing the release build does (app icon + version info), so a + # break in it shows up here rather than on release day. - name: Package (unpacked directory only) - run: npm run package:dir + run: npm run package:dir -- -c.win.signAndEditExecutable=true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 136811a..34cc5ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,6 +90,13 @@ jobs: # Unsigned builds: don't let electron-builder go looking for a signing identity. CSC_IDENTITY_AUTO_DISCOVERY: false + # Bash on every OS, Windows included: PowerShell splits an argument like + # `-c.win.signAndEditExecutable=true` at the dot, and electron-builder then reads + # `.win.…` as the path of a config file. + defaults: + run: + shell: bash + steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 @@ -112,15 +119,17 @@ jobs: - run: npm run build + # signAndEditExecutable: stamps the app icon and version info into the Windows .exe + # (off in electron-builder.yml for local builds, see there). Ignored on other OSes. - name: Package and upload to the draft release if: github.event_name == 'push' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npx electron-builder --publish always + run: npx electron-builder --publish always -c.win.signAndEditExecutable=true - name: Package (dry run) if: github.event_name == 'workflow_dispatch' - run: npx electron-builder --publish never + run: npx electron-builder --publish never -c.win.signAndEditExecutable=true - name: Keep the dry-run installers if: github.event_name == 'workflow_dispatch' diff --git a/electron-builder.yml b/electron-builder.yml index 8a8e159..2911192 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -38,6 +38,9 @@ files: extraResources: - from: packages/ui/build to: ui/build + # The window icon on Linux, where a window has none of its own (main.ts `windowIcon`). + - from: build/icons/256x256.png + to: icon.png asarUnpack: - "**/*.node" @@ -54,10 +57,11 @@ win: target: [nsis, portable, zip] artifactName: ${productName}-${version}-win-${arch}.${ext} icon: build/icon.ico - # electron-builder otherwise fetches the (macOS-targeted) winCodeSign bundle even - # for a plain unsigned Windows build, then fails extracting it here: its symlinks - # need SeCreateSymbolicLinkPrivilege, which a non-admin/non-Developer-Mode Windows - # account doesn't have. Not needed anyway — this build isn't signed. + # Off for local builds: editing the .exe needs electron-builder's winCodeSign bundle, + # whose macOS symlinks a non-admin, non-Developer-Mode Windows account can't extract. + # But editing is also what stamps the app icon and version info into the .exe — without + # it Windows shows Electron's default icon — so the CI and Release workflows turn it on + # (`-c.win.signAndEditExecutable=true`); their runners have the privilege. signAndEditExecutable: false linux: diff --git a/packages/electron/src/main.ts b/packages/electron/src/main.ts index d9474d9..4a250cc 100644 --- a/packages/electron/src/main.ts +++ b/packages/electron/src/main.ts @@ -30,6 +30,17 @@ import { loadWindowGeometry, trackWindowGeometry } from './windowState.js'; // `--bg` token (packages/ui/src/app.css). const BACKGROUND_COLOR = '#171717'; +/** The window's icon. Packaged on Windows and macOS the window takes the icon of its + * .exe / .app bundle; this covers Linux, where a window has none of its own, and + * `npm run dev:electron`, which runs the plain electron binary and would otherwise + * show Electron's default icon. */ +function windowIcon(): string | undefined { + if (process.platform === 'darwin') return undefined; + return app.isPackaged + ? join(process.resourcesPath, 'icon.png') + : join(__dirname, '..', '..', '..', 'build', 'icons', '256x256.png'); +} + /** How long the hidden window may wait for the page before it is revealed anyway. */ const REVEAL_FALLBACK_MS = 3000; @@ -44,6 +55,7 @@ function createWindow(): BrowserWindow { const win = new BrowserWindow({ title: 'BetterSshClient', + icon: windowIcon(), width: geometry.width, height: geometry.height, x: geometry.x,