Multi-Monitor-Setups sind heute Standard — und Apps müssen damit umgehen können. Das screen-Modul liefert die nötige Info: welche Displays gibt es, welche Auflösung, welcher ist primär, wo soll das Fenster auftauchen. Plus: Events bei Hot-Plug-Wechseln.

Wichtig: screen braucht app.ready

JavaScript
import { app, screen } from 'electron';

// FALSCH — vor app.ready werfen alle screen-Methoden
// const primary = screen.getPrimaryDisplay();

// RICHTIG
app.whenReady().then(() => {
    const primary = screen.getPrimaryDisplay();
    console.log(primary);
});

Ein häufiger Anfänger-Fehler: screen wird im Top-Level genutzt — Crash, weil noch nicht initialisiert.

Display-Info

JavaScript
const primary = screen.getPrimaryDisplay();
console.log(primary);
// {
//   id: 12345,
//   bounds:    { x: 0, y: 0, width: 2560, height: 1440 },
//   workArea:  { x: 0, y: 25, width: 2560, height: 1415 },
//   scaleFactor: 2,         // HiDPI / Retina
//   rotation: 0,
//   internal: true,
//   touchSupport: 'unknown'
// }

const all = screen.getAllDisplays();
console.log(`${all.length} Displays`);

bounds = ganzer Display. workArea = Bereich ohne Menüleiste/Dock/Taskleiste — wichtig für Fenster-Positionierung, sonst landet das Fenster unter der Taskleiste.

Display zum Punkt finden

JavaScript
// Welcher Display am Cursor?
const cursorDisplay = screen.getDisplayNearestPoint(
    screen.getCursorScreenPoint()
);

// Welcher Display zu einem Fenster?
const winDisplay = screen.getDisplayMatching(mainWindow.getBounds());

// Auf einem bestimmten Display zentrieren
const { width, height } = cursorDisplay.workArea;
const winSize = mainWindow.getSize();
mainWindow.setPosition(
    cursorDisplay.workArea.x + Math.floor((width - winSize[0]) / 2),
    cursorDisplay.workArea.y + Math.floor((height - winSize[1]) / 2)
);

Klassischer Use-Case: ein neues Fenster soll auf dem Display erscheinen, wo der Cursor ist — nicht auf dem primären (was bei Multi-Monitor unangenehm ist).

DPI-Scaling

JavaScript
// Scale Factor: 1.0 = klassisch, 2.0 = Retina/HiDPI, 1.5 = mittlere Skalierung
const sf = screen.getPrimaryDisplay().scaleFactor;

// dipToScreenRect / screenToDipRect: Umrechnung CSS-Pixel ↔ Hardware-Pixel
const screenRect = screen.dipToScreenRect(mainWindow, {
    x: 100, y: 100, width: 800, height: 600
});

Auf Retina/HiDPI-Displays ist 1 CSS-Pixel = 2 oder 3 Hardware-Pixel. Die meisten APIs in Electron arbeiten in CSS-Pixeln (DPI-aware) — bounds/getBounds ist die Ausnahme (Hardware-Pixel).

Display-Events

JavaScript
screen.on('display-added', (_event, display) => {
    console.log('Neuer Monitor:', display);
});

screen.on('display-removed', (_event, display) => {
    console.log('Monitor weg:', display);
    // Falls ein Fenster auf dem entfernten Display war:
    // auf primären Display umlenken
    relocateOrphanWindows();
});

screen.on('display-metrics-changed', (_event, display, changedMetrics) => {
    console.log('Display geändert:', changedMetrics);
    // changedMetrics: ['bounds', 'workArea', 'scaleFactor', 'rotation']
});

display-removed ist wichtig: User stöpselt externen Monitor ab, deine Fenster auf diesem Display würden ohne Logik off-screen verschwinden. Mit Listener das Fenster auf den primären zurückholen.

Pattern: gespeicherte Fenster-Position validieren

Beim App-Start prüfen, ob die letzte Fenster-Position noch sichtbar ist (User könnte Monitor abgesteckt haben):

JavaScript
function isPositionVisible(bounds) {
    const displays = screen.getAllDisplays();
    return displays.some(d =>
        bounds.x >= d.bounds.x &&
        bounds.y >= d.bounds.y &&
        bounds.x + bounds.width  <= d.bounds.x + d.bounds.width &&
        bounds.y + bounds.height <= d.bounds.y + d.bounds.height
    );
}

const lastBounds = store.get('window-bounds', null);
const useBounds = (lastBounds && isPositionVisible(lastBounds))
    ? lastBounds
    : { width: 1200, height: 800 };  // Default — wird zentriert

const win = new BrowserWindow(useBounds);

Ohne diese Prüfung wäre das Fenster nach Monitor-Wechsel unsichtbar — User-Frust.

Besonderheiten

screen nicht vor app.ready nutzen.

Häufiger Anfänger-Fehler: screen.getPrimaryDisplay() im Top-Level. Crasht, weil Display-Subsystem noch nicht initialisiert. Immer in app.whenReady() oder später.

workArea statt bounds für Fenster-Positionierung.

bounds enthält Menüleiste/Dock/Taskleiste — Fenster dort plaziert sind teilweise verdeckt. workArea zieht diese Bereiche ab. Für Fenster-Layout fast immer workArea nehmen.

display-removed abfangen — sonst verlorene Fenster.

User stöpselt externen Monitor ab → Fenster auf diesem Display sind off-screen. Listener registrieren, betroffene Fenster auf primären holen. Sonst muss der User raten, wo das Fenster ist.

Gespeicherte Bounds beim Start validieren.

User hat bei letztem Quit Fenster auf externem Monitor — beim App-Start ist der Monitor weg, Fenster wäre off-screen. Mit isPositionVisible-Check vorher prüfen, sonst auf Default zurückfallen.

scaleFactor ist auf gemischten Setups unterschiedlich.

Multi-Monitor mit Retina + Standard-Display: scaleFactor 2.0 vs. 1.0. Wer Bildschirm-Pixel hardcodet (Screenshots, Rendering), muss pro Display rechnen.

Cursor-Position cross-Display funktioniert.

screen.getCursorScreenPoint() liefert Position in der globalen Screen-Koordinate (über alle Displays). Bei Multi-Monitor wechselt die x-Koordinate sprunghaft, wenn der Cursor auf den nächsten Monitor wandert. Zu beachten bei Cursor-Tracking-Apps.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Main-Prozess

Zur Übersicht