Back to Cherry Studio

Power

src/main/core/power/README.md

2.0.03.5 KB
Original Source

Power

The system power hub. A single lifecycle-managed service (PowerService) that owns every Electron powerMonitor / powerSaveBlocker concern so the rest of the app never touches those APIs directly.

Responsibilities

AreaWhat it provides
Notification eventsTyped EmitterEvent for onSuspend / onResume / onLockScreen / onUnlockScreen / onPowerSourceChange. suspend/resume + power-source are de-duplicated against internal state (macOS double-fires — electron/electron#24803); lock/unlock are pass-through.
Shutdown barrierregisterShutdownHandler(fn)Disposable. On OS shutdown, handlers run serially and are bounded by a hard timeout, then the app quits. Cross-platform: powerMonitor shutdown + preventDefault on macOS/Linux, @paymoapp/electron-shutdown-handler on Windows.
Sleep preventionpreventSleep(reason?)Disposable. Ref-counted holds; the OS blocker (prevent-app-suspension) is active only while a hold is held AND the user opted in via app.power.prevent_sleep_when_busy. isPreventingSleep() reports the effective state.
QueriesgetPowerPhase() / getPowerSource() / isOnBatteryPower() / getSystemIdleTime() / getSystemIdleState(thresholdSec) — level-triggered, so a late caller reconciles current state without having seen the edge.

Quick Start

ts
import { application } from '@application'

const power = application.get('PowerService')

// Keep the machine awake for the duration of some work (effective only if the user
// enabled `app.power.prevent_sleep_when_busy`):
const hold = power.preventSleep('job:export')
try {
  await doWork()
} finally {
  hold.dispose() // idempotent
}

// React to the machine suspending/resuming:
this.registerDisposable(power.onSuspend(() => pauseLongPoll()))
this.registerDisposable(power.onResume(() => resumeLongPoll()))

// Run cleanup before the OS shuts the machine down:
this.registerDisposable(power.registerShutdownHandler(() => flushCriticalState()))

Notes

  • WhenReady phase. The app is already ready, so powerSaveBlocker / BrowserWindow are used directly — no app.whenReady() gymnastics. The preference gate is self-read, mirroring TrayService / ThemeService / ProxyService.
  • Sleep prevention is a generic registry. Any worker that needs the machine awake registers a hold; the gate (the user preference) is orthogonal and owned here. The Job system is the first registrant; streaming and other workers self-register later through the same API.
  • preventSleep() is best-effort and never throws. It always returns a usable Disposable; any powerSaveBlocker failure is logged and swallowed inside the service. Consumers therefore need no defensive try/catch around acquisition — the graceful degradation lives in the provider, not at every call site.
  • OS shutdown is routed through the app's normal quit flow. On macOS/Linux the barrier calls event.preventDefault() then application.quit() (Windows: blockShutdown → handlers → releaseShutdownapplication.quit()). Because the quit goes through before-quit, an active Application.preventQuit hold (e.g. a data migration) will gate an OS-initiated shutdown just like a user quit — bounded by the hard shutdown-handler timeout, since the OS cannot be blocked indefinitely.
  • The user-facing toggle lives in Settings → General; the preference is defined in the data-classify target-key-definitions.json (no v1 source).