/** * Energiesparmodus des Rechners. * * **Wozu.** Die Prüfungssimulation misst ihre Restzeit an der Wanduhr: * `endeMs = startMs + Minuten`, und `verbrauchtMs = Date.now() − startMs`. * Klappt jemand mitten in einem 120-Minuten-Bogen den Laptop zu, zählt die * gesamte Schlafzeit als Bearbeitungszeit – nach einer Mittagspause ist der * Bogen beim Aufwachen abgelaufen, und der Herzschlag der Simulation schreibt * die aufgeblähte Dauer sogar in die Datenbank. Dieses Modul misst, wie lange * der Rechner wirklich geschlafen hat, und meldet es der Oberfläche; die * schreibt ihren Zeitursprung um denselben Betrag vor. Die Uhr steht damit * während des Schlafs still – dieselbe Zusage, die der Schließen-Dialog in * {@link ./fenster} für das Beenden schon macht. * * **Warum das nicht im Renderer gemessen wird.** Naheliegend wäre ein * Sekundentakt, der prüft, ob zwischen zwei Takten mehr als eine Sekunde * vergangen ist. Chromium drosselt Zeitgeber in verdeckten Fenstern jedoch auf * einen Takt pro Minute: Ein bloß **minimiertes** Fenster sähe damit aus wie * ein schlafender Rechner, und die Prüfungsuhr ließe sich durch Minimieren * anhalten. Das wäre ein Schummelweg, kein Nachteilsausgleich. `powerMonitor` * meldet ausschließlich echten Energiesparmodus und ist von der Drosselung * unberührt. * * **Was hier bewusst nicht behandelt wird.** Ein Sprung der Systemuhr (NTP, * manuelles Stellen) verfälscht dieselbe Rechnung, wird hier aber nicht * abgefangen: Ihn vom Energiesparmodus zu unterscheiden verlangt eine zweite, * monotone Zeitquelle, deren Verhalten im Schlaf je nach Plattform abweicht – * und beide Quellen zugleich zu korrigieren führte zur Doppelgutschrift. Der * Bearbeitungsdauer ist damit weiterhin nur bis zur Grenze der * Kappung zu trauen, wenn jemand während eines Laufs die Uhr stellt. * * **Die Bildschirmsperre zählt mit.** `lock-screen` wird nicht abgehört: Ein * gesperrter, laufender Rechner ist etwas anderes als ein schlafender. Wer den * Bildschirm sperrt und weggeht, bearbeitet den Bogen nicht – aber er könnte * ihn auf einem zweiten Gerät nachschlagen. Die Uhr läuft dort weiter. */ import { BrowserWindow, powerMonitor } from 'electron'; import { KANAL_ENERGIESPAREN_ENDE } from '../shared/ipc'; /** * Kürzeste Schlafdauer, die gemeldet wird. * * Zwischen `suspend` und `resume` liegt selbst bei einem sofort abgebrochenen * Standby ein Rest an Zeit. Eine Gutschrift von Bruchteilen einer Sekunde * verschöbe den Zeitursprung ohne erkennbaren Anlass und ließe die Ansage * grundlos erscheinen. */ const MINDESTDAUER_MS = 2000; /** Zeitpunkt des Einschlafens; `null`, solange der Rechner wach ist. */ let eingeschlafenUm: number | null = null; /** Ob die Zuhörer hängen – ein zweites Anmelden meldete doppelt. */ let angemeldet = false; function beiSuspend(): void { eingeschlafenUm = Date.now(); } function beiResume(): void { const beginn = eingeschlafenUm; eingeschlafenUm = null; if (beginn === null) { return; } const schlafMs = Date.now() - beginn; /* Auch gegen einen Uhrsprung während des Schlafs abgesichert: Eine negative oder winzige Dauer wird nicht gemeldet, statt den Ursprung zu verschieben. */ if (schlafMs < MINDESTDAUER_MS) { return; } for (const fenster of BrowserWindow.getAllWindows()) { if (!fenster.isDestroyed()) { fenster.webContents.send(KANAL_ENERGIESPAREN_ENDE, schlafMs); } } } /** * Beginnt, den Energiesparmodus zu beobachten. * * Mehrfaches Aufrufen ist folgenlos. */ export function energieUeberwachen(): void { if (angemeldet) { return; } powerMonitor.on('suspend', beiSuspend); powerMonitor.on('resume', beiResume); angemeldet = true; } /** Gegenstück für Tests und sauberes Herunterfahren. */ export function energieVergessen(): void { if (!angemeldet) { return; } powerMonitor.off('suspend', beiSuspend); powerMonitor.off('resume', beiResume); angemeldet = false; eingeschlafenUm = null; }