Pithos desktop: Windows installer and portable build
Until now the desktop app only ran from a source checkout, so Pithos was really usable only inside Theseus. electron-builder (the version Theseus uses) now makes an NSIS installer and a portable exe; s3d is still fetched on first run by the checksum-checked installer, so the app stays small and never ships an s3d it did not verify. Icons are rendered from ui/icon.svg. Quitting also no longer waits on idle keep-alive sockets before exiting.
This commit is contained in:
parent
3dd1583ad9
commit
7741ed2ee9
7 changed files with 4475 additions and 6 deletions
|
|
@ -883,7 +883,10 @@ export async function createPithos(opts = {}) {
|
|||
login.cancel();
|
||||
for (const s of subscribers) s.end();
|
||||
await daemon.stop();
|
||||
await new Promise((r) => server.close(r));
|
||||
// Idle keep-alive sockets would hold close() for seconds; nobody is left to answer.
|
||||
const closed = new Promise((r) => server.close(r));
|
||||
server.closeAllConnections?.();
|
||||
await closed;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
BIN
desktop/icons/icon.ico
Normal file
BIN
desktop/icons/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
BIN
desktop/icons/icon.png
Normal file
BIN
desktop/icons/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
|
|
@ -9,6 +9,9 @@ const { pathToFileURL } = require('node:url');
|
|||
let pithos;
|
||||
let win;
|
||||
|
||||
// Taskbar pins and notifications group under Pithos, not Electron.
|
||||
if (process.platform === 'win32') app.setAppUserModelId('st.silentmode.pithos');
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit();
|
||||
} else {
|
||||
|
|
|
|||
4353
package-lock.json
generated
4353
package-lock.json
generated
File diff suppressed because it is too large
Load diff
68
package.json
68
package.json
|
|
@ -11,7 +11,11 @@
|
|||
"scripts": {
|
||||
"web": "node bin/pithos-web.js",
|
||||
"desktop": "electron desktop/main.cjs",
|
||||
"test": "node --test test/*.test.js"
|
||||
"test": "node --test test/*.test.js",
|
||||
"icons": "electron scripts/make-icons.cjs",
|
||||
"dist:win": "electron-builder --win nsis portable",
|
||||
"dist:linux": "electron-builder --linux AppImage tar.gz",
|
||||
"dist:mac": "electron-builder --mac dmg zip"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
|
@ -23,6 +27,66 @@
|
|||
"@lydell/node-pty": "1.2.0-beta.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^44.5.1"
|
||||
"electron": "^44.5.1",
|
||||
"electron-builder": "^25.1.8"
|
||||
},
|
||||
"author": "Silent Mode",
|
||||
"homepage": "https://silentmode.st/pithos/",
|
||||
"build": {
|
||||
"appId": "st.silentmode.pithos",
|
||||
"productName": "Pithos",
|
||||
"artifactName": "Pithos-${version}-${arch}.${ext}",
|
||||
"extraMetadata": {
|
||||
"main": "desktop/main.cjs"
|
||||
},
|
||||
"directories": {
|
||||
"output": "dist/desktop",
|
||||
"buildResources": "desktop/icons"
|
||||
},
|
||||
"asar": true,
|
||||
"asarUnpack": [
|
||||
"node_modules/@lydell/**"
|
||||
],
|
||||
"npmRebuild": false,
|
||||
"files": [
|
||||
"core/**/*",
|
||||
"ui/**/*",
|
||||
"desktop/**/*",
|
||||
"LICENSE",
|
||||
"package.json",
|
||||
"node_modules/**/*",
|
||||
"!**/*.md",
|
||||
"!**/*.map",
|
||||
"!**/*.d.ts"
|
||||
],
|
||||
"win": {
|
||||
"icon": "desktop/icons/icon.ico",
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
]
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerIcon": "desktop/icons/icon.ico",
|
||||
"uninstallerIcon": "desktop/icons/icon.ico",
|
||||
"installerHeaderIcon": "desktop/icons/icon.ico",
|
||||
"shortcutName": "Pithos",
|
||||
"artifactName": "Pithos-Setup-${version}.${ext}"
|
||||
},
|
||||
"portable": {
|
||||
"artifactName": "Pithos-${version}-portable.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"icon": "desktop/icons/icon.png",
|
||||
"category": "Utility",
|
||||
"synopsis": "Decentralized cloud storage you own, on Sia"
|
||||
},
|
||||
"mac": {
|
||||
"icon": "desktop/icons/icon.png",
|
||||
"category": "public.app-category.utilities"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
52
scripts/make-icons.cjs
Normal file
52
scripts/make-icons.cjs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Renders ui/icon.svg into the desktop app's icons: desktop/icons/icon.png (512 px,
|
||||
// for Linux and the window) and desktop/icons/icon.ico (16-256 px, PNG-compressed
|
||||
// entries, for Windows). Run with Electron, which brings its own renderer:
|
||||
// npx electron scripts/make-icons.cjs
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const root = path.join(__dirname, '..');
|
||||
const svg = fs.readFileSync(path.join(root, 'ui', 'icon.svg'), 'utf8');
|
||||
const out = path.join(root, 'desktop', 'icons');
|
||||
|
||||
app.disableHardwareAcceleration();
|
||||
app.on('window-all-closed', () => {});
|
||||
app.whenReady().then(async () => {
|
||||
// The page draws the SVG on a canvas at each size and hands back a PNG.
|
||||
const win = new BrowserWindow({ show: false, webPreferences: { offscreen: true } });
|
||||
await win.loadURL('data:text/html,<html><body></body></html>');
|
||||
const src = `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
|
||||
const render = async (size) => {
|
||||
const url = await win.webContents.executeJavaScript(`new Promise((ok, fail) => {
|
||||
const img = new Image(); img.onerror = () => fail(new Error('svg did not load'));
|
||||
img.onload = () => { const c = document.createElement('canvas'); c.width = c.height = ${size};
|
||||
const g = c.getContext('2d'); g.imageSmoothingQuality = 'high'; g.drawImage(img, 0, 0, ${size}, ${size}); ok(c.toDataURL('image/png')); };
|
||||
img.src = ${JSON.stringify(src)};
|
||||
})`);
|
||||
return Buffer.from(url.split(',')[1], 'base64');
|
||||
};
|
||||
|
||||
fs.mkdirSync(out, { recursive: true });
|
||||
fs.writeFileSync(path.join(out, 'icon.png'), await render(512));
|
||||
|
||||
const sizes = [16, 24, 32, 48, 64, 128, 256];
|
||||
const pngs = [];
|
||||
for (const s of sizes) pngs.push(await render(s));
|
||||
const header = Buffer.alloc(6);
|
||||
header.writeUInt16LE(0, 0); header.writeUInt16LE(1, 2); header.writeUInt16LE(sizes.length, 4);
|
||||
const dir = Buffer.alloc(16 * sizes.length);
|
||||
let offset = 6 + dir.length;
|
||||
sizes.forEach((s, i) => {
|
||||
const e = i * 16;
|
||||
dir.writeUInt8(s >= 256 ? 0 : s, e); dir.writeUInt8(s >= 256 ? 0 : s, e + 1);
|
||||
dir.writeUInt8(0, e + 2); dir.writeUInt8(0, e + 3);
|
||||
dir.writeUInt16LE(1, e + 4); dir.writeUInt16LE(32, e + 6);
|
||||
dir.writeUInt32LE(pngs[i].length, e + 8); dir.writeUInt32LE(offset, e + 12);
|
||||
offset += pngs[i].length;
|
||||
});
|
||||
fs.writeFileSync(path.join(out, 'icon.ico'), Buffer.concat([header, dir, ...pngs]));
|
||||
win.destroy();
|
||||
console.log('icons written to', out);
|
||||
app.quit();
|
||||
}).catch((e) => { console.error(e); app.exit(1); });
|
||||
Loading…
Add table
Reference in a new issue