Files
swinksandClaude Opus 4.8 c1b899ec8b Linux-Fassung (Ubuntu) ergaenzen
Die Fachlogik war bereits plattformneutral — paths.js loeste ~/.config
schon auf, jsonl.js liest ueber os.homedir(). Zu tun waren Autostart,
Build-Targets und drei Linux-Eigenheiten der Oberflaeche.

XWayland erzwingen: Ab Electron 36 waehlt Chromium in einer
Wayland-Sitzung von sich aus Wayland. Dort darf sich ein Fenster weder
selbst positionieren noch dauerhaft in den Vordergrund legen — beides
ist der Kern dieses Widgets. Der Schalter muss auf der Kommandozeile
stehen, weil Ozone die Plattform vor dem Main-Skript festlegt;
appendSwitch() und ELECTRON_OZONE_PLATFORM_HINT kommen beide zu spaet.
Bleibt ein einmaliger Neustart mit --ozone-platform=x11, der sich am
Argument selbst als Abbruchbedingung erkennt. Im AppImage muss dabei
execPath auf APPIMAGE zeigen, sonst laeuft der Nachfolger in den
/tmp-Einhaengepunkt, den der Vorgaenger beim Beenden wegraeumt.

Autostart: app.setLoginItemSettings() gibt es unter Linux nicht. Das
neue Modul autostart.js kapselt die Weiche und schreibt bzw. loescht
dort ~/.config/autostart/claude-live-dashboard.desktop. Der Startbefehl
kommt aus APPIMAGE, falls gesetzt.

Ausserdem: enable-transparent-visuals fuer echte Transparenz, das
32-px-Tray-Symbol in der GNOME-Leiste, plattformabhaengige
Beschriftungen in Tray-Menue und Einstellungsdialog, AppImage- und
deb-Targets, icon.png auf 512 px (electron-builder rechnet daraus die
Linux-Groessen herunter) und start.sh als Gegenstueck zu start.cmd.

Getestet unter GNOME/Wayland: Fenster auf X11 an erwarteter Position,
gemerkte Position wird wiederhergestellt, Tray registriert sich auf
DBus, dump liest die Transkripte, beide Pakete bauen und das AppImage
startet ohne Schalter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 21:52:08 +02:00

127 lines
3.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* Erzeugt die Tray-Symbole als PNG — ohne Bildbibliothek, nur mit zlib aus der
* Standardbibliothek. Einmalig aufzurufen:
*
* node tools/make-icon.js
*
* Motiv: ein Ring mit einem Sektor, der den Füllstand andeutet.
*/
const fs = require('node:fs');
const path = require('node:path');
const zlib = require('node:zlib');
const OUT_DIR = path.join(__dirname, '..', 'assets');
/** CRC32 nach PNG-Spezifikation. */
const CRC_TABLE = (() => {
const table = new Int32Array(256);
for (let n = 0; n < 256; n++) {
let c = n;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
table[n] = c;
}
return table;
})();
function crc32(buf) {
let c = -1;
for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
return (c ^ -1) >>> 0;
}
function chunk(type, data) {
const out = Buffer.alloc(data.length + 12);
out.writeUInt32BE(data.length, 0);
out.write(type, 4, 'ascii');
data.copy(out, 8);
const crcInput = Buffer.concat([Buffer.from(type, 'ascii'), data]);
out.writeUInt32BE(crc32(crcInput), data.length + 8);
return out;
}
/**
* @param {number} size Kantenlänge
* @param {(x: number, y: number) => [number, number, number, number]} shade RGBA je Pixel
*/
function makePng(size, shade) {
// Scanlines mit Filterbyte 0 je Zeile
const raw = Buffer.alloc(size * (size * 4 + 1));
let p = 0;
for (let y = 0; y < size; y++) {
raw[p++] = 0;
for (let x = 0; x < size; x++) {
const [r, g, b, a] = shade(x, y);
raw[p++] = r;
raw[p++] = g;
raw[p++] = b;
raw[p++] = a;
}
}
const ihdr = Buffer.alloc(13);
ihdr.writeUInt32BE(size, 0);
ihdr.writeUInt32BE(size, 4);
ihdr[8] = 8; // Bittiefe
ihdr[9] = 6; // Farbtyp RGBA
ihdr[10] = 0;
ihdr[11] = 0;
ihdr[12] = 0;
return Buffer.concat([
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
chunk('IHDR', ihdr),
chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),
chunk('IEND', Buffer.alloc(0)),
]);
}
/** Ring mit Füllsektor; kantengeglättet über 4×4-Supersampling. */
function ringShader(size, color, fillFraction = 0.68) {
const c = (size - 1) / 2;
const rOuter = size * 0.46;
const rInner = size * 0.28;
const SS = 4;
return (x, y) => {
let hits = 0;
for (let sy = 0; sy < SS; sy++) {
for (let sx = 0; sx < SS; sx++) {
const px = x + (sx + 0.5) / SS - 0.5;
const py = y + (sy + 0.5) / SS - 0.5;
const dx = px - c;
const dy = py - c;
const dist = Math.hypot(dx, dy);
if (dist > rOuter || dist < rInner) continue;
// Winkel ab 12 Uhr im Uhrzeigersinn
let ang = Math.atan2(dx, -dy);
if (ang < 0) ang += Math.PI * 2;
if (ang <= fillFraction * Math.PI * 2) hits++;
}
}
const a = Math.round((hits / (SS * SS)) * 255);
return [color[0], color[1], color[2], a];
};
}
function main() {
fs.mkdirSync(OUT_DIR, { recursive: true });
const orange = [217, 89, 38]; // Slot 2 der Palette
for (const size of [16, 32]) {
const file = path.join(OUT_DIR, `tray${size === 16 ? '' : `@${size}`}.png`);
fs.writeFileSync(file, makePng(size, ringShader(size, orange)));
console.log(`geschrieben: ${file}`);
}
// Fenster- und Anwendungssymbol. 512 px, weil electron-builder daraus die
// Linux-Symbolgrößen herunterrechnet und mindestens 256 px verlangt.
const file = path.join(OUT_DIR, 'icon.png');
fs.writeFileSync(file, makePng(512, ringShader(512, orange)));
console.log(`geschrieben: ${file}`);
}
main();