diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 70dc697..95805d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,6 +49,10 @@ jobs: - name: Install setuptools (node-gyp distutils fix) run: python -m pip install setuptools + - name: Install pacman build tooling (Linux) + if: matrix.platform == 'linux' + run: sudo apt-get update && sudo apt-get install -y libarchive-tools + - name: Install dependencies run: npm ci @@ -78,6 +82,7 @@ jobs: dist/*.exe.blockmap dist/*.AppImage dist/*.deb + dist/*.pacman dist/latest*.yml if-no-files-found: ignore diff --git a/README.md b/README.md index 3d23a6d..4333df3 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Grab the latest release for your platform: - **macOS**: `.dmg` (Apple Silicon & Intel) - **Windows**: `.exe` installer -- **Linux**: `.AppImage` or `.deb` +- **Linux**: `.AppImage`, `.deb`, or `.pacman` (Arch/Manjaro) ## Prerequisites @@ -105,11 +105,30 @@ npm run build # Platform-specific npm run build:mac # DMG + zip (arm64 + x64) npm run build:win # NSIS installer (x64 + arm64) -npm run build:linux # AppImage + deb (x64 + arm64) +npm run build:linux # AppImage + deb + pacman (x64 + arm64) ``` Output goes to `dist/`. +### Building on Arch / Manjaro + +The `deb` and `pacman` targets are built via the `fpm` binary bundled by +electron-builder, which links against `libcrypt.so.1`. Arch ships `libxcrypt` +without that legacy ABI, so install the compat shim once: + +```bash +sudo pacman -S libxcrypt-compat +``` + +`AppImage` builds without it. + +The pacman package is published as **`switchboard-doctly`** rather than +`switchboard` because the Arch `extra` repo already ships a package named +`switchboard` (elementary OS's Pantheon Control Center). Renaming avoids the +file-conflict that would block installation alongside it. The app itself is +still called Switchboard everywhere users see it — only the package identity +changes. Uninstall later with `sudo pacman -R switchboard-doctly`. + ## Releasing Releases are driven by git tags: diff --git a/build/icons/128x128.png b/build/icons/128x128.png new file mode 100644 index 0000000..3d65898 Binary files /dev/null and b/build/icons/128x128.png differ diff --git a/build/icons/16x16.png b/build/icons/16x16.png new file mode 100644 index 0000000..e6d1fc6 Binary files /dev/null and b/build/icons/16x16.png differ diff --git a/build/icons/256x256.png b/build/icons/256x256.png new file mode 100644 index 0000000..65ca532 Binary files /dev/null and b/build/icons/256x256.png differ diff --git a/build/icons/32x32.png b/build/icons/32x32.png new file mode 100644 index 0000000..b06d750 Binary files /dev/null and b/build/icons/32x32.png differ diff --git a/build/icons/48x48.png b/build/icons/48x48.png new file mode 100644 index 0000000..624c74a Binary files /dev/null and b/build/icons/48x48.png differ diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 0000000..99227e9 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/build/icons/64x64.png b/build/icons/64x64.png new file mode 100644 index 0000000..bfad910 Binary files /dev/null and b/build/icons/64x64.png differ diff --git a/package.json b/package.json index 927c7a4..bfe68a9 100644 --- a/package.json +++ b/package.json @@ -106,10 +106,25 @@ "linux": { "target": [ "AppImage", - "deb" + "deb", + "pacman" ], + "icon": "build/icons", "category": "Development" }, + "pacman": { + "packageName": "switchboard-doctly", + "artifactName": "switchboard-doctly-${version}.${ext}", + "depends": [ + "gtk3", + "nss", + "libnotify", + "libxtst", + "libxss", + "xdg-utils", + "libsecret" + ] + }, "dmg": { "background": "build/dmg-background.png", "iconSize": 100, diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js index a4deef3..1a1f9f6 100644 --- a/scripts/generate-icons.js +++ b/scripts/generate-icons.js @@ -2,6 +2,8 @@ const { execSync } = require('child_process'); const fs = require('fs'); const path = require('path'); +const png2icons = require('png2icons'); +const { loadImage, createCanvas } = require('@napi-rs/canvas'); const OUTPUT_DIR = path.join(__dirname, '..', 'build'); const pngPath = path.join(OUTPUT_DIR, 'icon.png'); @@ -10,7 +12,6 @@ fs.mkdirSync(OUTPUT_DIR, { recursive: true }); // If icon.png already exists, use it; otherwise generate a placeholder if (!fs.existsSync(pngPath)) { - const { createCanvas } = require('@napi-rs/canvas'); const SIZE = 1024; const canvas = createCanvas(SIZE, SIZE); const ctx = canvas.getContext('2d'); @@ -94,7 +95,6 @@ bg.save('${paddedPath}') console.log(`Created ${icnsPath} (with macOS padding)`); } else { // Non-macOS fallback: use png2icons - const png2icons = require('png2icons'); const pngBuffer = fs.readFileSync(pngPath); const icnsBuffer = png2icons.createICNS(pngBuffer, png2icons.BICUBIC2, 0); if (icnsBuffer) { @@ -104,7 +104,6 @@ bg.save('${paddedPath}') } // ICO (Windows) — png2icons on all platforms -const png2icons = require('png2icons'); const pngBuffer = fs.readFileSync(pngPath); const icoBuffer = png2icons.createICO(pngBuffer, png2icons.BICUBIC2, 0, true); if (icoBuffer) { @@ -113,4 +112,20 @@ if (icoBuffer) { console.log(`Created ${icoPath} (${icoBuffer.length} bytes)`); } -console.log('Icon generation complete.'); +// Linux: hicolor-sized PNGs for /usr/share/icons/hicolor/x/apps/ +// electron-builder picks these up when linux.icon points at the directory. +(async () => { + const linuxDir = path.join(OUTPUT_DIR, 'icons'); + fs.mkdirSync(linuxDir, { recursive: true }); + const img = await loadImage(pngPath); + for (const size of [16, 32, 48, 64, 128, 256, 512]) { + const c = createCanvas(size, size); + const ctx = c.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + ctx.drawImage(img, 0, 0, size, size); + fs.writeFileSync(path.join(linuxDir, `${size}x${size}.png`), c.toBuffer('image/png')); + } + console.log(`Created ${linuxDir} (Linux hicolor sizes)`); + console.log('Icon generation complete.'); +})();