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();
|
login.cancel();
|
||||||
for (const s of subscribers) s.end();
|
for (const s of subscribers) s.end();
|
||||||
await daemon.stop();
|
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 pithos;
|
||||||
let win;
|
let win;
|
||||||
|
|
||||||
|
// Taskbar pins and notifications group under Pithos, not Electron.
|
||||||
|
if (process.platform === 'win32') app.setAppUserModelId('st.silentmode.pithos');
|
||||||
|
|
||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
app.quit();
|
app.quit();
|
||||||
} else {
|
} 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": {
|
"scripts": {
|
||||||
"web": "node bin/pithos-web.js",
|
"web": "node bin/pithos-web.js",
|
||||||
"desktop": "electron desktop/main.cjs",
|
"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": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
|
|
@ -23,6 +27,66 @@
|
||||||
"@lydell/node-pty": "1.2.0-beta.15"
|
"@lydell/node-pty": "1.2.0-beta.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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