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>
This commit is contained in:
2026-07-22 21:52:08 +02:00
co-authored by Claude Opus 4.8
parent 868f31fa9a
commit c1b899ec8b
9 changed files with 294 additions and 23 deletions
+62 -13
View File
@@ -12,6 +12,9 @@ const { app, BrowserWindow, Tray, Menu, ipcMain, screen, nativeImage, shell } =
const { Store, loadConfig } = require('./store');
const { Collector } = require('./collector');
const { CONFIG_FILE, STATE_FILE, USER_DATA_DIR, ensureUserConfig } = require('./paths');
const { getAutostart, setAutostart: applyAutostart } = require('./autostart');
const IS_LINUX = process.platform === 'linux';
const COMPACT_HEIGHT = 352;
const EXPANDED_HEIGHT = 640;
@@ -30,11 +33,59 @@ let collector = null;
let store = null;
let config = {};
/**
* Erzwingt XWayland, sobald das Widget in einer Wayland-Sitzung startet.
*
* Electron wählt seit Version 36 von sich aus Wayland. Dort darf sich ein
* Fenster aber weder selbst positionieren noch dauerhaft in den Vordergrund
* legen — beides ist der Kern dieses Widgets. Über XWayland funktioniert es.
*
* Der Schalter muss auf der Kommandozeile stehen: Ozone wählt die Plattform,
* bevor dieses Skript läuft, deshalb kommen `appendSwitch()` und
* `ELECTRON_OZONE_PLATFORM_HINT` beide zu spät. Bleibt ein Neustart mit
* ergänztem Argument. Das Argument ist zugleich die Abbruchbedingung — im
* zweiten Durchlauf steht es in `argv` und der Zweig greift nicht mehr.
*
* Muss vor `requestSingleInstanceLock()` stehen, damit die verworfene Instanz
* die Sperre gar nicht erst nimmt.
*/
function forceX11IfWayland() {
if (!IS_LINUX || !process.env.WAYLAND_DISPLAY) return false;
if (process.env.ELECTRON_OZONE_PLATFORM_HINT) return false; // ausdrücklich übersteuert
if (process.argv.some((a) => a.startsWith('--ozone-platform'))) return false;
const args = process.argv.slice(1).concat('--ozone-platform=x11');
// Ein AppImage hängt sich beim Start unter /tmp ein und wirft die Einhängung
// beim Beenden wieder weg. `process.execPath` zeigt dorthin und wäre für den
// Nachfolger schon verschwunden — neu gestartet werden muss das Abbild.
if (process.env.APPIMAGE) app.relaunch({ execPath: process.env.APPIMAGE, args });
else app.relaunch({ args });
app.exit(0);
return true;
}
if (!forceX11IfWayland()) {
// Ohne diesen Schalter zeichnet Chromium unter Linux hinter das rahmenlose
// Fenster einen schwarzen Kasten statt echter Transparenz.
if (IS_LINUX) app.commandLine.appendSwitch('enable-transparent-visuals');
bootstrap();
}
/** Verhindert, dass mehrere Instanzen dieselben Zustandsdateien beschreiben. */
if (!app.requestSingleInstanceLock()) {
app.quit();
} else {
function bootstrap() {
if (!app.requestSingleInstanceLock()) {
app.quit();
return;
}
app.on('second-instance', () => showWindow());
main().catch((err) => {
console.error(err);
app.quit();
});
}
/**
@@ -142,13 +193,13 @@ function setClickThrough(enabled) {
}
function setAutostart(enabled) {
app.setLoginItemSettings({ openAtLogin: enabled, args: [] });
applyAutostart(enabled);
buildTrayMenu();
}
function buildTrayMenu() {
if (!tray) return;
const autostart = app.getLoginItemSettings().openAtLogin;
const autostart = getAutostart();
tray.setContextMenu(
Menu.buildFromTemplate([
@@ -171,7 +222,7 @@ function buildTrayMenu() {
click: (item) => setClickThrough(item.checked),
},
{
label: 'Mit Windows starten',
label: IS_LINUX ? 'Mit dem System starten' : 'Mit Windows starten',
type: 'checkbox',
checked: autostart,
click: (item) => setAutostart(item.checked),
@@ -186,7 +237,9 @@ function buildTrayMenu() {
}
function createTray() {
const icon = nativeImage.createFromPath(path.join(ROOT, 'assets', 'tray.png'));
// In der GNOME-Leiste wirkt das 16-px-Symbol ausgefranst — dort das größere.
const file = IS_LINUX ? 'tray@32.png' : 'tray.png';
const icon = nativeImage.createFromPath(path.join(ROOT, 'assets', file));
tray = new Tray(icon);
tray.setToolTip('Claude Live Dashboard');
tray.on('click', toggleWindow);
@@ -297,7 +350,8 @@ function registerIpc() {
config,
meta: {
configPath: CONFIG_FILE,
autostart: app.getLoginItemSettings().openAtLogin,
autostart: getAutostart(),
platform: process.platform,
},
}));
@@ -366,8 +420,3 @@ app.on('window-all-closed', () => {});
app.on('before-quit', () => {
if (collector) collector.stop();
});
main().catch((err) => {
console.error(err);
app.quit();
});