Version: 0.1.0 | Last updated: 2026-07-27
- createWeDevKit() — Factory Function
- DevKitInstance — Top-Level Instance
- MediaController — Media Integration Control
- RgbController — RGB Data Access
- LifecycleController — Lifecycle Control
- PropertiesController — Property Configuration
- Mp3PlayerController — MP3 Spectrum Player
- Type Reference
- Build-Time Injection API
- Agent Usage Examples
Creates a WE Dev Kit instance, injecting all WE runtime simulation APIs in one call.
function createWeDevKit(options?: DevKitConfig): DevKitInstance| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Master switch |
autoDetect |
boolean |
true |
Auto-detect real WE environment and skip |
audio |
boolean | AudioConfig |
true |
Audio simulation config |
media |
boolean | MediaConfig |
true |
Media integration simulation config |
properties |
boolean |
true |
Property listener polyfill |
rgb |
boolean |
true |
RGB LED simulation |
lifecycle |
boolean |
true |
Lifecycle events |
panel |
boolean | PanelConfig |
true |
Control panel. Set to false to disable entirely |
| Field | Type | Default | Description |
|---|---|---|---|
amplitude |
number |
0.6 |
Amplitude 0–1 |
bassBoost |
number |
1.2 |
Bass gain |
variationSpeed |
number |
1.0 |
Variation speed |
frameRate |
number |
30 |
Frame rate |
| Field | Type | Default | Description |
|---|---|---|---|
tracks |
MockTrack[] |
[] |
Custom track library (empty = use 5 built-in tracks) |
autoCycle |
boolean |
true |
Auto-rotate tracks |
cycleIntervalMs |
number |
8000 |
Rotation interval (ms) |
| Field | Type | Default | Description |
|---|---|---|---|
position |
{ x: number; y: number } |
{ x: 0, y: 0 } |
Panel initial position |
collapsed |
boolean |
false |
Start collapsed |
theme |
'light' | 'dark' |
'dark' |
Theme |
// Basic usage (all features enabled)
const kit = createWeDevKit();
// Disable panel entirely (API only)
const kit = createWeDevKit({ panel: false });
// Enable panel and RGB only
const kit = createWeDevKit({ audio: false, media: false });
// Fine-grained config
const kit = createWeDevKit({
panel: { position: { x: 100, y: 50 }, theme: 'dark' },
audio: { amplitude: 0.8, bassBoost: 1.5, frameRate: 60 },
media: { autoCycle: true, cycleIntervalMs: 5000 },
});
// HTML script tag mode (IIFE)
// <script src="./dist/index.global.js"></script>
// <script>const kit = WeDevKit.createWeDevKit({ panel: true });</script>The instance object returned by createWeDevKit().
| Method | Signature | Description |
|---|---|---|
destroy |
(): void |
Destroy all mocks, restore original state (JS timers, CSS, window globals) |
togglePanel |
(): void |
Toggle control panel visibility |
getConfig |
(): Readonly<DevKitConfig> |
Get current config (read-only snapshot) |
pushProperties |
(props: Record<string, unknown>): void |
Manually trigger a property push (calls applyUserProperties) |
pushAudioFrame |
(): void |
Manually trigger audio data push |
nextTrack |
(): void |
Manually skip to next track |
setAudioEnabled |
(enabled: boolean): void |
Set audio data input on/off (auto zero-fade on disable) |
interface DevKitState {
isLoaded: boolean; // Whether loaded
isPanelVisible: boolean; // Whether panel is visible
currentTrackIndex: number; // Current track index
playbackState: PlaybackState; // Playback state
isRgbPluginLoaded: boolean; // Whether RGB plugin is loaded (dynamic getter)
isAudioEnabled: boolean; // Whether audio input is enabled
}All
statefields use lazy getters and always reflect the latest value.
| Field | Type | Description |
|---|---|---|
media |
MediaController |
Media integration control |
rgb |
RgbController |
RGB data access |
lifecycle |
LifecycleController |
Lifecycle control |
properties |
PropertiesController |
Property configuration |
Fully simulates all 5 WE Media Integration listeners + constants. Built-in library of 5 tracks (Jay Chou, Daft Punk, JJ Lin, Ludovico Einaudi, G.E.M.), each with a gradient SVG cover.
| Method | Description | WE Behavior |
|---|---|---|
play() |
Play/resume | Pushes wallpaperRegisterMediaPlaybackListener state change |
pause() |
Pause | PLAYING → PAUSED |
stop() |
Stop | Any state → STOPPED, resets position |
Behavior details:
- Calling
play()fromstopped: pushesSTOPPEDthenPLAYING, also pushes metadata and thumbnail - Calling
play()frompaused: only pushesPLAYING, no metadata re-push - Calling
pause()fromplaying: only pushesPAUSED
| Method | Description |
|---|---|
nextTrack() |
Next track (cycles) |
prevTrack() |
Previous track (cycles) |
setTrack(index) |
Jump to track at index |
Behavior details:
- Track changes do not send
STOPPED(avoids UI flicker) - Only updates metadata and thumbnail events
- Playback state unchanged
| Method | Description |
|---|---|
setCustomTrack({ title, artist, ... }) |
Override current track metadata without changing track |
setCustomThumbnail(dataUri) |
Set custom cover art, auto-extracts dominant colors |
| Method | Description |
|---|---|
seek(pct) |
Seek to percentage 0–100 |
getPosition() |
Get current position (seconds) |
| Property | Type | Description |
|---|---|---|
currentIndex |
number |
Current track index |
playbackState |
'playing' | 'paused' | 'stopped' |
Current playback state |
tracks |
MockTrack[] |
Full track list |
interface MockTrack {
title: string; // Title
artist: string; // Artist
album?: string; // Album name
genre?: string; // Genre
duration?: number; // Duration in seconds (default 240)
thumbnail?: string; // Base64 data URI cover
primaryColor?: string; // Primary color
secondaryColor?: string; // Secondary color
tertiaryColor?: string; // Tertiary color
textColor?: string; // Text color
highContrastColor?: string; // High-contrast color
}| Controller Method | WE Listener Triggered | Event Type |
|---|---|---|
play() |
wallpaperRegisterMediaPlaybackListener |
MediaPlaybackEvent |
pause() |
wallpaperRegisterMediaPlaybackListener |
MediaPlaybackEvent |
stop() |
wallpaperRegisterMediaPlaybackListener |
MediaPlaybackEvent |
setTrack() / nextTrack() / prevTrack() |
wallpaperRegisterMediaPropertiesListener + wallpaperRegisterMediaThumbnailListener + wallpaperRegisterMediaTimelineListener |
MediaPropertiesEvent + MediaThumbnailEvent + MediaTimelineEvent |
seek() |
wallpaperRegisterMediaTimelineListener |
MediaTimelineEvent |
| Progress update (every 100ms) | wallpaperRegisterMediaTimelineListener |
MediaTimelineEvent |
| Initial push | wallpaperRegisterMediaStatusListener |
MediaStatusEvent |
window.wallpaperMediaIntegration contains:
{
PLAYBACK_PLAYING: 0,
PLAYBACK_PAUSED: 1,
PLAYBACK_STOPPED: 2,
}Intercepts window.wpPlugins.led.setAllDevicesByImageData calls (WE LED plugin interface), providing decoded data access and manual simulation.
| Method | Return | Description |
|---|---|---|
getLastFrame() |
RgbFrameData | null |
Get last frame raw data |
getDecodedImageData() |
ImageData | null |
Decode to canvas ImageData (usable with ctx.putImageData()) |
getPalette() |
{ color: string; ratio: number }[] |
Get last frame palette (up to 8 colors) |
onFrame(callback) |
() => void |
Register frame callback, returns unsubscription function |
simulateFrame(width?, height?, pixelData?) |
void |
Manually simulate an RGB frame |
interface RgbFrameData {
width: number; // Pixel width
height: number; // Pixel height
pixels: number[]; // RGB pixel array [r0,g0,b0, r1,g1,b1, ...]
palette: { // Palette (descending by ratio)
color: string; // Hex color like "#4A90D9"
ratio: number; // Ratio 0–1
}[];
}- Frame capture: When a project sends LED data via
window.wpPlugins.led.setAllDevicesByImageData(imageData, width, height), it is automatically decoded and stored - ImageData decoding: RGB pixel array → canvas
ImageData(with alpha channel), ready forctx.putImageData() - Palette extraction: Divides image into 10×10 grid cells, averages color per cell, quantizes to 16 levels, aggregates top 8 colors
- Manual simulation: Use
simulateFrame()to generate test data without a real plugin
// Get and draw last frame
const frame = kit.rgb.getLastFrame();
if (frame) {
const imgData = kit.rgb.getDecodedImageData();
canvas.getContext('2d')!.putImageData(imgData!, 0, 0);
console.log('Palette:', kit.rgb.getPalette());
}
// Listen to frames
const unsub = kit.rgb.onFrame(({ width, height, pixels, palette }) => {
console.log(`RGB frame: ${width}x${height}, ${palette.length} colors`);
});
// Unsubscribe
unsub();
// Manually simulate a frame
kit.rgb.simulateFrame(100, 20);Simulates WE pause/resume/FPS change lifecycle operations. All hijacked native JS timer functions are automatically restored on destroy().
| Method | Description | WE Behavior |
|---|---|---|
pause() |
Simulate WE wallpaper pause | Calls wallpaperPropertyListener.setPaused(true) |
resume() |
Simulate WE wallpaper resume | Calls wallpaperPropertyListener.setPaused(false) |
setFps(fps) |
Simulate FPS limit change | Calls wallpaperPropertyListener.applyGeneralProperties({ fps }) |
| Property | Type | Description |
|---|---|---|
isPaused |
boolean |
Whether currently paused |
kit.lifecycle.pause() synchronously performs:
- Call project callback: Invokes any registered
wallpaperPropertyListener.setPaused(true) - CSS animation pause: Adds class
wpxPausePseudoAnimationAllto<html>and injects a<style>rule:animation-play-state: paused !important - JS timer interception: Hijacks
setTimeout/setInterval/requestAnimationFrame— new calls during pause are queued
kit.lifecycle.resume() does the reverse: removes CSS pause, replays queued timers/RAF calls.
kit.destroy() automatically restores all original timer functions onto window.
applyGeneralProperties({ fps }):
- Calls any registered
applyGeneralPropertiescallback - If none registered, logs only
Reads general.properties definitions from project.json, providing property visibility queries (with condition evaluation), missing translation detection, and add/edit/delete operations.
Data source: PropertiesController bridges to the panel's internal property cache. When the panel is enabled, data comes from live
loadProjectProperties()results. When the panel is disabled (panel: false), methods return empty collections.
| Method | Return | Description |
|---|---|---|
getProperty(key) |
ProjectPropertyDef | undefined |
Get a single property definition |
getAllProperties() |
ProjectPropertyDef[] |
Get all property definitions |
getVisibility(key) |
PropertyVisibility |
Query a property's visibility (with live condition evaluation) |
getAllVisibility() |
PropertyVisibility[] |
Query all properties' visibility |
checkTranslation(key) |
PropertyTranslationStatus |
Check if a property's i18n key is missing |
getMissingTranslations() |
PropertyTranslationStatus[] |
Get all properties with missing translations |
getVisibleProperties() |
ProjectPropertyDef[] |
Get currently visible properties (condition-filtered) |
getCurrentValues() |
Record<string, unknown> |
Get all property current values ({ key: value }) |
reloadProperties() |
Promise<void> |
Reload property definitions from project.json |
addProperty(def) |
ProjectPropertyDef |
Add a new property definition |
updateProperty(key, def) |
ProjectPropertyDef | undefined |
Update an existing property |
removeProperty(key) |
boolean |
Remove a property definition |
interface ProjectPropertyDef {
key: string; // Property key, e.g. "rgb_show"
type: 'bool' | 'slider' | 'combo' | 'color' | 'text' | 'textinput' | 'file' | 'directory' | 'group';
value: unknown; // Current value
text?: string; // i18n key, e.g. "ui_rgb_show"
displayName?: string; // Human-readable name from localization
missingTranslation?: boolean; // Whether translation is missing
min?: number; // Slider minimum
max?: number; // Slider maximum
step?: number; // Slider step
precision?: number; // Slider decimal precision
fraction?: boolean; // Slider allows fractions
fileType?: string; // File/directory type filter (e.g. "video")
mode?: string; // Directory load mode ("ondemand")
options?: { value: unknown; label: string }[]; // Combo options
condition?: string; // Visibility condition expression
order?: number; // Sort order
index?: number; // WE project.json index field
}interface PropertyVisibility {
key: string; // Property key
visible: boolean; // Whether currently visible
condition: string | null; // Original condition expression
blockedBy?: string; // Property name causing invisibility
blockedValue?: unknown; // The blocking property's current value
}interface PropertyTranslationStatus {
key: string; // Property key
i18nKey: string; // i18n translation key
missing: boolean; // Whether translation is missing
displayName: string; // Currently displayed name
}interface PropertyDefInput {
key: string;
type: PropertyType;
value?: unknown;
text?: string; // i18n key
displayName?: string;
min?: number; max?: number; step?: number;
precision?: number; fraction?: boolean;
fileType?: string; mode?: string;
options?: { value: unknown; label: string }[];
condition?: string;
order?: number;
index?: number;
}Supports condition expression syntax from project.json (full lexer + parser):
- Comparison:
.value == X,.value != X - Booleans:
true,false - Numbers: integer and float
- Strings:
'single-quoted'or"double-quoted" - Composition:
&&(AND),||(OR) - Grouping:
(expr)
Example conditions:
showDate.value == true && DateX.value > 0
visual_audio_model.value == 1 && ColorMode.value == 2
projectJsonReader auto-matches translations by browser language:
- Exact match (e.g.
"zh-CN"→"zh-cn") - Language prefix match (e.g.
"zh-CN"→"zh") - Fallback to
"en-us" - First available language
// Get property definition
const prop = kit.properties.getProperty('rgb_show');
console.log(prop?.displayName, prop?.value);
// Check visibility
const vis = kit.properties.getVisibility('rgb_show');
if (!vis.visible) {
console.log(`Hidden by ${vis.blockedBy} = ${vis.blockedValue}`);
}
// Check missing translations
const missing = kit.properties.getMissingTranslations();
console.log(`${missing.length} missing translations:`, missing.map(m => m.i18nKey));
// Get visible properties
const visible = kit.properties.getVisibleProperties();
console.log(`${visible.length} visible properties`);
// Add new property
kit.properties.addProperty({
key: 'my_setting',
type: 'bool',
value: true,
text: 'ui_my_setting',
order: 1,
});Real MP3 playback and spectrum extraction via Web Audio API. Features log-band RMS merging, Gaussian smoothing, time weighting, peak holding, and other DSP processing — extracting 64-band real spectrum data (replacing simulated audio).
This controller exposes UI controls through the panel's Audio section, with simulated/real spectrum toggled via AudioBridge.
| Method | Signature | Description |
|---|---|---|
loadFile |
(file: File): Promise<void> |
Load an MP3 file (auto-decodes to AudioBuffer) |
play |
(): void |
Start playback |
pause |
(): void |
Pause (preserves position) |
stop |
(): void |
Stop (resets to beginning) |
seek |
(percent: number): void |
Seek to percentage 0–100 |
setVolume |
(v: number): void |
Set volume 0–1 |
setSensitivity |
(v: number): void |
Spectrum sensitivity 0.1–1 (lower = smoother), default 0.5 |
setCeiling |
(v: number): void |
Output ceiling 0.1–1 (caps max amplitude), default 1.0 |
setLoop |
(enabled: boolean): void |
Enable/disable looping, default true |
setActive |
(active: boolean): void |
Toggle real spectrum vs simulated data |
destroy |
(): void |
Clean up AudioContext and all resources |
| Property | Type | Description |
|---|---|---|
isPlaying |
boolean |
Whether currently playing |
isLoaded |
boolean |
Whether a file is loaded |
isActive |
boolean |
Whether real spectrum is active |
currentTime |
number |
Current playback position (seconds) |
duration |
number |
Total duration (seconds) |
fileName |
string |
File name |
AnalyserNode.getByteFrequencyData (2048 FFT)
→ Gaussian smoothing (radius=2, sigma=1.0, removes spikes)
→ Bin-level EMA time weighting (history=4, reduces flicker)
→ Log-band RMS merging (64 bands, geometric frequency width)
→ Inter-band horizontal smoothing (3-point center-weighted)
→ Band-level EMA (controlled by sensitivity²)
→ Peak-hold normalization
→ Output Float32Array[128] (left 0–63 / right 64–127)
type PlaybackState = 'playing' | 'paused' | 'stopped';type AudioMode = 'beats' | 'melody' | 'mixed';type AudioSourceType = 'simulated' | 'mp3';dev-kit injects the following global APIs onto window:
| Global API | Purpose |
|---|---|
window.wallpaperPropertyListener |
Property listener (applyUserProperties, setPaused, applyGeneralProperties, etc.) |
window.wallpaperPluginListener |
Plugin load listener |
window.wpPlugins.led |
LED plugin setAllDevicesByImageData |
window.wallpaperMediaIntegration |
Media integration constants |
window.wallpaperRegisterAudioListener |
Audio listener registration |
window.wallpaperRegisterMediaStatusListener |
Media status listener registration |
window.wallpaperRegisterMediaPropertiesListener |
Media properties listener registration |
window.wallpaperRegisterMediaThumbnailListener |
Media thumbnail listener registration |
window.wallpaperRegisterMediaPlaybackListener |
Media playback state listener registration |
window.wallpaperRegisterMediaTimelineListener |
Media timeline listener registration |
The wallpaper-engine-web-dev-kit/inject sub-module provides the ability to inject dev-kit into existing wallpaper project build outputs, without modifying source code.
Inject the dev-kit script into an HTML string. Pure string transformation, works in both browser and Node.js.
function injectIntoHtml(html: string, options?: InjectOptions): string| Field | Type | Default | Description |
|---|---|---|---|
config |
Record<string, unknown> |
All enabled | DevKit config object (passed to createWeDevKit) |
scriptSrc |
string |
'./we-dev-kit/index.global.js' |
Script tag src path (relative to HTML) |
autoCreate |
boolean |
true |
Whether to auto-call WeDevKit.createWeDevKit(config) |
insertAt |
'before-body-end' | 'after-body-start' | 'before-head-end' |
'before-body-end' |
Injection position |
import fs from 'node:fs';
import { injectIntoHtml } from 'wallpaper-engine-web-dev-kit/inject';
const html = fs.readFileSync('dist/index.html', 'utf8');
const modified = injectIntoHtml(html, { config: { panel: true } });
fs.writeFileSync('dev/index.html', modified);One-shot dev build preparation: copies project build output + injects dev-kit scripts + copies dev-kit JS files.
function prepareDevBuild(options: DevBuildOptions): void| Field | Type | Default | Description |
|---|---|---|---|
inputDir |
string |
— (required) | Project build output directory (e.g. dist/) |
outputDir |
string |
'dev' |
Development output directory |
config |
Record<string, unknown> |
All enabled | DevKit config object |
scriptSrc |
string |
Auto | Script src path |
kitDistPath |
string |
Auto (node_modules) | Path to dev-kit dist directory |
targetDirName |
string |
'we-dev-kit' |
Sub-directory name for dev-kit files |
import { prepareDevBuild } from 'wallpaper-engine-web-dev-kit/inject';
prepareDevBuild({
inputDir: 'dist',
outputDir: 'dev',
config: { panel: true, audio: true, media: true, rgb: true, lifecycle: true },
});Flow:
inputDir→ copied to →outputDir→ dev-kit injected → openoutputDir/index.htmlin browser. Your original build output is never modified.
Typical usage patterns suitable for AI agents (e.g. GitHub Copilot, Claude):
const kit = createWeDevKit({ panel: false, audio: false });
// Start playing
kit.media.play();
// Wait a few seconds then skip
await new Promise(r => setTimeout(r, 3000));
kit.media.nextTrack();
// Check current track
const track = kit.media.getCurrentTrack();
console.log(`Now playing: ${track.title} - ${track.artist}`);
// Pause
kit.media.pause();const kit = createWeDevKit();
// Wait for project.json to load
await new Promise(r => setTimeout(r, 1000));
// Find missing translations
const bad = kit.properties.getMissingTranslations();
if (bad.length > 0) {
console.log('Missing translations:', bad.map(b => b.i18nKey));
}
// Check a specific property's visibility (with live condition evaluation)
const vis = kit.properties.getVisibility('audio_visual_model');
if (!vis.visible) {
console.log(`Reason: ${vis.blockedBy} = ${vis.blockedValue}`);
}
// Get all visible properties
const visProps = kit.properties.getVisibleProperties();
console.log(`Visible: ${visProps.map(p => p.key).join(', ')}`);
// Add a new property
kit.properties.addProperty({
key: 'my_new_prop',
type: 'bool',
value: true,
text: 'ui_my_new_prop',
});const kit = createWeDevKit({ panel: false });
// Register RGB callback
kit.rgb.onFrame((frame) => {
const ctx = document.getElementById('preview')!.getContext('2d')!;
const imgData = new ImageData(
new Uint8ClampedArray(frame.pixels.flatMap(p => [p, 255])),
frame.width
);
ctx.putImageData(imgData, 0, 0);
});
// Manually simulate a frame
kit.rgb.simulateFrame(100, 20);const kit = createWeDevKit({ panel: false });
console.log('Paused:', kit.lifecycle.isPaused); // false
// Pause (CSS animations + JS timers paused together)
kit.lifecycle.pause();
console.log('Paused:', kit.lifecycle.isPaused); // true
// Resume (replays queued timers)
kit.lifecycle.resume();
// Simulate FPS limit
kit.lifecycle.setFps(30);const kit = createWeDevKit({ panel: false });
console.log('Audio enabled:', kit.state.isAudioEnabled); // true
// Disable audio (zero-fade + simulator fadeout)
kit.setAudioEnabled(false);
// Re-enable (simulator fadein)
kit.setAudioEnabled(true);const kit = createWeDevKit({ panel: false });
// Pause — pauses CSS animations + intercepts JS timers
kit.lifecycle.pause();
console.log('Paused:', kit.lifecycle.isPaused); // true
console.log(document.documentElement.classList.contains('wpxPausePseudoAnimationAll')); // true
// Resume — removes CSS pause + replays queued timers
kit.lifecycle.resume();
console.log('Resumed:', !kit.lifecycle.isPaused); // trueconst kit = createWeDevKit({
audio: { amplitude: 0.5, bassBoost: 1.0 },
media: { autoCycle: true },
rgb: true,
lifecycle: true,
properties: true,
});
// ... use kit ...
// Destroy: restores all original window APIs, CSS styles, JS timers
kit.destroy();