Haiku Screen Saver Add-ons: Lifecycle, Preview, and Configuration
Build a Haiku BScreenSaver add-on around explicit start, draw, stop, preview, and configuration phases, with bounded work and reliable state.
Haiku screen savers are loadable add-ons implementing the BScreenSaver contract. They are not ordinary standalone applications that own the desktop. The screen-saver preferences system loads a module, creates an instance through the exported instantiate_screen_saver() function, supplies a view for drawing or preview, and invokes lifecycle methods as the saver starts, stops, or enters configuration. A reliable add-on respects that host-managed lifecycle and treats each callback as a boundary with specific ownership and responsiveness constraints.
The public header defines the main hooks: constructor with an archived BMessage and image ID, InitCheck(), StartSaver(BView*, bool preview), StopSaver(), Draw(BView*, int32 frame), optional direct-drawing callbacks, configuration callbacks, metadata, state saving, and tick/loop controls. The exact behavior and load environment can vary by Haiku revision; current headers and sample add-ons are the authority for the build target.
Construction and state restoration
The module exports instantiate_screen_saver(BMessage*, image_id) and returns a new BScreenSaver subclass instance. The archive message represents saved configuration state; it may be absent, incomplete, or from an older version. Initialize defaults first, then read supported fields with validation. Do not assume an archived integer is in range or a color tuple has the expected shape.
Use InitCheck() to report whether required resources are available. A saver that depends on optional data should degrade gracefully; a missing decoration should not prevent a plain animation from loading. Keep construction quick. Large image decoding or resource scanning belongs in a controlled preparation phase, and the add-on must not block the preferences application for an unbounded period.
Version your own archived settings. A field added later should have a default when absent; an obsolete field should be ignored safely. Do not serialize pointers, object IDs, or filesystem paths that are valid only for one session. SaveState() should store stable user choices, not runtime buffers or preview-only geometry.
StartSaver and the preview distinction
StartSaver(BView*, bool preview) receives the drawing view and a preview flag. Preview is a distinct environment: the view may be smaller, lifecycle may be shorter, and the user may change settings repeatedly. Do not assume preview means a private window with the same bounds as full-screen mode. Query the supplied view’s current bounds and configure drawing to fit them.
Use StartSaver() to prepare per-run state and establish whether the add-on can operate in that view. Start timers or workers only when necessary, and pair every successful setup with a stop path. If initialization fails partway through, unwind only the resources actually acquired. Avoid creating global application objects or starting an unrelated daemon from a screen-saver module.
Preview must not perform disruptive actions such as changing screen mode, capturing input, or writing large files. The user expects to see the effect in the settings panel without affecting their desktop. Respect the preview value and keep behavior safe even if the host stops the preview immediately after starting it.
Draw work should be bounded
Draw(BView*, int32 frame) is the basic animation callback. Treat it as a render step, not a place to decode assets, perform disk or network I/O, block on a worker, or allocate without bounds. Prepare resources before repeated drawing, keep per-frame allocations out of the hot path, and cap complexity to the view bounds.
The supplied frame value and tick settings are part of the host’s scheduling contract; do not assume a precise frame rate or that frame numbers correspond to wall-clock time. If animation speed should remain stable under load, derive state from elapsed time or a controlled tick policy rather than incrementing position by a fixed amount per callback and assuming every callback arrives on schedule. Keep the result deterministic enough for preview and debugging.
Use the drawing APIs and clipping behavior from Interface Kit correctly. Save and restore view state when changing drawing modes, colors, or transformations. Do not retain a raw BView* beyond the callback or lifecycle guarantee. Direct screen access has additional constraints and should only be used when the current API and hardware path support it; ordinary view drawing is the safer default.
StopSaver must be complete and repeat-safe
StopSaver() is where the module releases per-run resources: stop timers, signal and join workers, release bitmaps, detach views, and restore any state it changed. The module must not race a late worker message against an unloaded image. A stop operation can occur after preview, full-screen use, partial startup, or preference changes. Make cleanup idempotent so a second stop or destructor does not double-free.
If a worker is unavoidable, give it owned state and a cancellation signal. Join it before the add-on can be unloaded. Never let it call virtual methods on an instance after StopSaver() returns unless the host/API explicitly guarantees the instance remains alive and the design coordinates that lifetime.
Do not assume the saver is stopped only after a graceful timeout. User input, display changes, sleep, module selection, or preferences shutdown can end the run. The module should release resources promptly and should not hold a system-wide lock while waiting for a slow worker.
Configuration and module metadata
StartConfig(BView*) and StopConfig() create and destroy the settings UI. Build a small, responsive Interface Kit view; save changes through the supported settings message. Do not assume the preferences application keeps the configuration view alive after StopConfig(). If settings update a running preview, handle the change without leaving stale pointers or applying invalid values.
SupplyInfo() lets the add-on provide metadata such as whether it can be randomized; ModulesChanged() informs it about module information. Implement only the documented metadata contract and keep defaults safe. The user guide’s ScreenSaver preferences panel is the place to install, choose, configure, and test modules; use the current packaging mechanism rather than copying binaries blindly into a system directory.
The current user guide describes user and system add-on locations, but package-managed installation is preferable for normal distribution. A module copied into a non-packaged directory may have different precedence and update behavior from a package. Record the package and module version when diagnosing a saver that fails to load.
Security and robustness boundaries
A screen saver runs inside the host process context and can affect desktop responsiveness. Treat archived settings as untrusted data, cap dimensions and image sizes, and avoid loading arbitrary scripts or executing external files. An animation should not collect input or network data unless the feature explicitly requires it and the user understands that behavior.
A saver can fail because of an add-on ABI mismatch, missing dependency, invalid archive data, unsupported drawing mode, or internal bug. Keep the failure isolated: return an error from InitCheck() or StartSaver() when appropriate, log concise diagnostics, and avoid destabilizing the entire preferences application. Test against the target ABI and package metadata.
Test the host lifecycle, not only the drawing
Test loading from the ScreenSaver preferences panel, preview at several panel sizes, full-screen start, immediate stop, repeated start/stop, configuration open/close, missing optional resources, and invalid archived values. Leave the preview running while changing settings and close preferences while a saver is active. Verify no worker remains, CPU usage stays bounded, and the desktop returns to normal.
Test with and without direct access if the saver implements DirectConnected() or DirectDraw(). Those callbacks exist for special rendering paths; they are not a blanket promise that all graphics hardware supports direct drawing. Keep a fallback path and verify it still renders correctly.
For a crash report, capture Haiku revision, architecture, module package/version, whether the failure happens in preview or full screen, the archived settings if safe to share, and the exact lifecycle point. Distinguish module load failure from a rendering crash and from a preference-panel issue.
The durable design model is host-managed: the preferences service creates the module, passes a view and preview context, calls drawing and configuration hooks, and expects prompt cleanup. Respecting that lifecycle, validating saved state, keeping frame work bounded, and making stop complete allows a screen saver to behave as a well-contained Haiku add-on instead of a fragile desktop-wide process.
Related:
- Haiku BScreen: Display Geometry, Modes, and Safe Screen Access
- Device Drivers and Hardware Support in Haiku
Sources: