Skip to content

Files, LED & Frames Helpers

import {
createFpsLimiter,
encodeCanvasForLed,
toFileUrl,
} from 'wallpaper-engine/helpers';

toFileUrl(path) converts host filesystem values without breaking browser-ready URLs:

Input Output
'' ''
C:/Images/background.png file:///C:/Images/background.png
C%3A/Images/native%20%23%20cover.jpg file:///C:/Images/native%20%23%20cover.jpg
C:\\Images\\cover #1.png file:///C:/Images/cover%20%231.png
//server/share/cover.png or \\\\server\\share\\cover.png file://server/share/cover.png
/assets/background.png Preserved
https://…, http://…, data:…, blob:…, file:… Preserved, case-insensitively by scheme
image.src = toFileUrl(property.value);

Raw filesystem paths normalize backslashes and encode filename characters such as spaces, #, and %. Wallpaper Engine’s encoded-drive form (C%3A/...) is decoded exactly once before conversion; malformed escapes in that form throw URIError. Percent sequences in raw drive paths (C:/...) stay literal, so a filename containing %20 remains distinct from a space. Browser-ready URLs and single-slash asset paths are preserved. The helper performs no filesystem lookup. See Files & Directories for host paths and simulator-owned object URLs.

encodeCanvasForLed(canvas) reads the full 2D canvas and returns the concatenated RGB byte string expected by both hardware surfaces:

const encoded = encodeCanvasForLed(canvas);
window.wpPlugins?.led?.setAllDevicesByImageData(
encoded,
canvas.width,
canvas.height,
);

For every pixel, the output contains three code points in row-major order:

canvas RGBA bytes: R G B A | R G B A | …
encoded string: R G B | R G B | …

Alpha is discarded. The result length is canvas.width * canvas.height * 3. The helper allocates the browser’s ImageData and builds a new string, so use a small canvas such as 100×20 rather than the full wallpaper frame.

Both dimensions must be at least 2 pixels when sending the result to the native LED APIs. The simulator rejects smaller frames with RangeError.

If canvas.getContext('2d') returns null, the helper throws Error('Could not get 2D context from canvas'). Errors from getImageData(), including a tainted canvas, propagate.

Do not call hardware APIs before the host reports their plugin:

window.wallpaperPluginListener = {
onPluginLoaded(name) {
if (name === 'led') {
sendGeneralLedFrame();
}
if (name === 'cue') {
sendAdvancedCueFrame();
}
},
};

Use window.wpPlugins.led.setAllDevicesByImageData() for general RGB hardware. Use window.cue.setLedColorsByImageData() only after the cue plugin loads and direct Corsair iCUE access is required. The helper only encodes bytes; it does not check readiness or send data.

The container and plugin APIs are optional in the type declarations. Guard window.wpPlugins?.led or window.cue when sending; the plugin-name callback alone does not narrow an unrelated global property for TypeScript.

createFpsLimiter(draw) owns one requestAnimationFrame chain and passes elapsed seconds to each allowed draw:

const loop = createFpsLimiter((dt) => {
updateSimulation(dt);
renderFrame();
});
window.wallpaperPropertyListener = {
applyGeneralProperties(properties) {
if (properties.fps !== undefined) {
loop.setLimit(properties.fps);
}
},
setPaused(paused) {
if (paused) {
loop.stop();
}
else {
loop.start();
}
},
};
loop.start();
Method Behavior
start() Cancels an existing scheduled frame, resets timing and the cap accumulator, then schedules a fresh frame
stop() Cancels the scheduled frame and becomes a no-op when already stopped
setLimit(fps) Updates the cap for the next frame; changing the value resets accumulated threshold time

A limit greater than 0 caps drawing; 0 is unlimited. Negative limits throw RangeError, and non-finite limits throw TypeError without changing the active cap.

  • dt is seconds since the last allowed draw, clamped to 0–1.
  • Backward or duplicate timestamps therefore produce 0, not a negative step.
  • Long background-tab stalls produce at most 1 second of simulation progress on the next draw, avoiding a spiral of death.
  • Under a cap, elapsed tick time accumulates until a frame duration is reached; the remainder is retained to reduce drift.
  • Restarting after pause resets both timing baselines, so paused wall-clock time is not delivered as dt.

The limiter controls draw frequency, not browser animation-frame scheduling. It maintains one RAF callback while running and skips draw calls that arrive before the cap threshold.

Use the dedicated Audio guide for clampAudio(), leftChannel(), and rightChannel(). Use the Media guide for getMediaPlaybackStatus(). Those pages include the host callbacks that make the helpers meaningful.

All functions on this page are implemented in src/helpers.ts.

Connect the simulator’s Runtime tab in Development Simulation and check exact signatures in Helpers API.