// Fenster anlegen (KONZEPT § 4.2). Das Fenster zeigt an, sonst nichts — // kein Node-Zugriff, contextIsolation an, alles läuft über die Brücke // (src/bruecke) und den MessagePort zum Kern. // // 5.5.0: Die Größe hängt am Bildschirm (fensterlage.ts) — 80 % des // Arbeitsbereichs beim ersten Start, danach die zuletzt gemerkte Lage, // sofern sie noch auf einem Bildschirm liegt. import { BrowserWindow, screen } from 'electron' import { join } from 'node:path' import { fensterrandEntfernen } from './fensterrand' import { anfangsLage, lageLesen, lageSchreiben, MINDEST_BREITE, MINDEST_HOEHE, type Lage } from './fensterlage' export function fensterErstellen(profilOrdner: string): BrowserWindow { const lageDatei = join(profilOrdner, 'fenster-lage.json') const lage = anfangsLage( lageLesen(lageDatei), screen.getPrimaryDisplay().workArea, screen.getAllDisplays().map((d) => d.workArea), ) const fenster = new BrowserWindow({ x: lage.x, y: lage.y, width: lage.width, height: lage.height, minWidth: Math.min(MINDEST_BREITE, 860), minHeight: Math.min(MINDEST_HOEHE, 560), show: true, autoHideMenuBar: true, // Eigene Titelleiste (Commander-Wunsch 01.09.2026): Der Windows-Rahmen // ist weg, Minimieren/Maximieren/Schließen sitzen im UI und gezogen // wird an der Kopfzeile (.ziehbar in fenster/stil.css). // // Wichtig: GRÖSSE ÄNDERN bleibt möglich. Electron setzt für rahmenlose // Fenster unter Windows weiterhin WS_THICKFRAME (Option `thickFrame`, // Standard true) — die Kanten lassen sich also weiter ziehen, und // Doppelklick auf die Ziehfläche maximiert wie gewohnt. frame: false, // Dunkler Grund SOFORT — sonst blitzt beim Start ein weißes Rechteck, // bevor React lädt. Das warme Bühnen-Schwarz des Kino-Mixes // (oklch(0.13 0.012 60), siehe fenster/stil.css). backgroundColor: '#171412', title: 'Rippy', webPreferences: { preload: join(__dirname, '../bruecke/index.js'), contextIsolation: true, nodeIntegration: false, sandbox: true, }, }) if (lage.maximiert) fenster.maximize() // Lage merken — verzögert, damit ein Ziehen nicht hundertmal schreibt. let zeiger: NodeJS.Timeout | null = null const merken = (): void => { if (zeiger !== null) clearTimeout(zeiger) zeiger = setTimeout(() => { if (fenster.isDestroyed()) return const maximiert = fenster.isMaximized() // Im maximierten Zustand die NORMALE Lage behalten (getNormalBounds), // sonst würde beim nächsten Start ein riesiges Fenster entmaximiert. const b = maximiert ? fenster.getNormalBounds() : fenster.getBounds() const neu: Lage = { x: b.x, y: b.y, width: b.width, height: b.height, maximiert } lageSchreiben(lageDatei, neu) }, 400) } fenster.on('resize', merken) fenster.on('move', merken) fenster.on('maximize', merken) fenster.on('unmaximize', merken) // Der DWM-Rand von Windows 11 (die feine helle Linie ums Fenster) — er // gehört nicht zum Fensterinhalt, mit CSS ist er nicht zu erreichen. // Ein Fehlschlag ist kein Ausfall: Unter Windows 10 gibt es ihn nicht. const rand = fensterrandEntfernen(fenster.getNativeWindowHandle()) if (!rand.ok) console.warn(`[haupt] Fensterrand bleibt: ${rand.fehler}`) // electron-vite: Im dev-Modus bedient ein Vite-Server das Fenster // (ELECTRON_RENDERER_URL), gebaut wird aus out/fenster geladen. // RIPPY_START_BEREICH öffnet einen bestimmten Bereich (#einstellungen / // #einrichtung) — der Smoke-Beweis fotografiert damit auch diese Seiten. const devUrl = process.env['ELECTRON_RENDERER_URL'] const startBereich = process.env['RIPPY_START_BEREICH'] ?? '' if (devUrl !== undefined && devUrl.length > 0) { void fenster.loadURL(startBereich.length > 0 ? `${devUrl}#${startBereich}` : devUrl) } else { void fenster.loadFile( join(__dirname, '../fenster/index.html'), startBereich.length > 0 ? { hash: startBereich } : undefined, ) } return fenster }