Developer Documentation
Official developer reference for Wallcraft. Build isolated, high-performance desktop widgets using standard HTML5/CSS3/JavaScript, and create interactive 3D live wallpapers using WebGL 2.0 GLSL shaders.
Architecture & Sandbox Security
Wallcraft executes all community widgets inside strict HTML5 <iframe sandbox="allow-scripts"> containers with an opaque (null) origin. External widgets have zero direct access to Node.js, Electron preload bridges, or local file systems.
All interaction between your widget and the desktop environment takes place via an asynchronous, strictly validated postMessage RPC channel exposed through the window.WallCraft SDK.
┌─────────────────────────────────────────────────────────────────────────┐
│ WALLCRAFT RUNTIME │
├─────────────────────────────────────────────────────────────────────────┤
│ Community Widgets (user-widgets) │
│ - Isolated HTML5 Iframe Sandbox (sandbox="allow-scripts") │
│ - Opaque Origin (null) — No direct OS/Node.js access; permission-gated host bridge │
│ - window.WallCraft SDK (PostMessage RPC Bridge) │
│ - Self-contained package (.zip with manifest.json, html, css, js) │
└─────────────────────────────────────────────────────────────────────────┘
window.WallCraft & window.wallcraft)
window.WallCraft in community widgets. The lowercase sandbox alias exposes only a subset and names the audio object audioMixer; it does not include config, language or lifecycle hooks. In the host window, window.wallcraft is a different, privileged preload API.
Community Sandbox vs. Privileged Preload Layer
Wallcraft maintains a strict security perimeter between third-party community widgets and the native operating system. Community widgets execute inside an isolated <iframe sandbox="allow-scripts"> and interact exclusively through the injected window.WallCraft SDK. Direct Electron host preload APIs (also named window.wallcraft in the host context) are restricted to privileged internal components and settings panels.
| Feature / Subsystem | Sandbox SDK (window.WallCraft) |
Host Preload Bridge (window.wallcraft) |
Execution Context & Permission |
|---|---|---|---|
| WASAPI Audio Mixer | window.WallCraft.audio.* |
window.wallcraft.audioMixer.* |
PostMessage RPC bridge (Requires "audio" permission) |
| Declarative Modals | window.WallCraft.showModal(options) |
WallCraftWallpaperModals.showModal() |
PostMessage RPC bridge (Native acrylic UI dialog) |
| Dynamic Configuration | window.WallCraft.config, onUpdate(cb) |
Electron Store IPC | Delivered on init & merged on user settings change |
| Language (i18n) | window.WallCraft.language, onLanguageChange |
window.wallcraft.changeLanguage |
Active locale delivered via postMessage ("en", "tr") |
| Lifecycle Hooks | onPause, onResume, onDestroy |
Window lifecycle events | Sent during gaming mode, occlusion, or deletion |
| System Telemetry (CPU/RAM/GPU) | window.WallCraft.getSystemStats() |
window.wallcraft.getSystemStats() |
PostMessage RPC bridge (Requires "system-stats" permission) |
| Media Controls (SMTC) | window.WallCraft.onMediaInfo, mediaControl |
window.wallcraft.onMediaInfo, mediaControl |
PostMessage RPC bridge (Requires "media" permission) |
| Discord Voice RPC | window.WallCraft.getDiscordVoiceState(), etc. |
window.wallcraft.getDiscordVoiceState(), etc. |
PostMessage RPC bridge (Requires "discord" permission) |
| App Launcher (Shortcuts) | window.WallCraft.launchShortcut() |
window.wallcraft.launchShortcut() |
PostMessage RPC bridge (Requires "launcher" permission) |
| App & File Icons | window.WallCraft.getFileIcon() |
window.wallcraft.getFileIcon() |
PostMessage RPC bridge (Automatic shell icon extraction) |
| Native File Pickers | selectImage(), selectVideo(), etc. |
selectImage(), selectVideo(), etc. |
PostMessage RPC bridge (Win32 Common Dialogs) |
| Clipboard Access | Synthetic shortcuts (Ctrl+C, Ctrl+V) |
window.wallcraft.writeClipboardText(text) |
Synthetic keyboard bridge vs Programmatic host write |
| Diagnostics & Feedback | Unavailable in sandbox | window.wallcraft.getSystemDiagnostics() |
Host Settings UI Only (Troubleshooting) |
| Widget Management | Unavailable in sandbox | window.wallcraft.getAvailableWidgets(), etc. |
Host Settings UI Only (Package Manager) |
Compatibility: SDK Preview, checked against application working sources labelled 0.0.1-beta on 13 October 2026. This is a source compatibility reference, not confirmation of a published binary. Host-only methods are grouped in Advanced Host Reference; they are unavailable to community widgets.
| Interface | Scope |
|---|---|
window.WallCraft | Public sandbox SDK: config, language, lifecycle hooks, audio and permission-gated services. |
window.wallcraft (sandbox) | Compatibility subset; audio is audioMixer. Prefer the public SDK. |
window.wallcraft (host) | Privileged application internals; not a community SDK. |
Quick Start: Build Your First Widget in 5 Minutes
Every Wallcraft widget is simply a local folder containing 4 core code files, plus optional dedicated media assets:
my-clock-widget/
├── manifest.json # Metadata, grid span (cols/rows), and config schema
├── widget.html # Markup & layout elements
├── widget.css # Theme styling with CSS design tokens
├── widget.js # Lifecycle logic (init, update, destroy)
├── icon.png # (Optional) Dedicated tile icon in Settings (icon.png/webp/svg)
└── preview.webp # (Optional) Fullscreen catalog/hub preview (preview.webp/png)
Here is a complete, minimal "Live Clock" widget:
{
"id": "minimal-clock",
"name": "Minimal Digital Clock",
"version": "1.0.0",
"author": "Your Name",
"description": "A sleek, theme-adaptive desktop clock.",
"entry": {
"html": "widget.html",
"script": "widget.js",
"css": "widget.css"
},
"gridSpan": {
"cols": 4,
"rows": 2
},
"wrapper": "default",
"icon": "icon.png",
"previewImage": "preview.webp",
"configSchema": [
{
"key": "showSeconds",
"label": "Show Seconds",
"type": "boolean",
"default": true
}
]
}
<div class="clock-container">
<div class="clock-header">
<span class="clock-label">SYSTEM CLOCK</span>
<span class="clock-badge">LIVE</span>
</div>
<div id="clock-display" class="clock-digits">--:--:--</div>
</div>
/* Container adapted to wrapper theme tokens */
.clock-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 1.25rem;
font-family: var(--wc-font-primary, 'Inter', sans-serif);
}
.clock-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 0.5rem;
}
.clock-label {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
color: var(--wc-theme-text-sub, #94a3b8);
}
.clock-badge {
font-size: 0.65rem;
font-weight: 700;
padding: 2px 6px;
border-radius: var(--wc-theme-radius-btn, 6px);
background: var(--wc-theme-primary, #FF5A36);
color: #000000;
}
.clock-digits {
font-size: 2.25rem;
font-weight: 700;
font-family: var(--wc-font-mono, 'JetBrains Mono', monospace);
color: var(--wc-theme-text-main, #ffffff);
letter-spacing: -0.02em;
}
(function () {
'use strict';
window.WallCraftWidget = {
container: null,
timerId: null,
showSeconds: true,
init: function (container, config) {
this.container = container;
this.update(config);
this.tick();
this.timerId = setInterval(() => this.tick(), 1000);
},
update: function (config) {
if (!config) return;
if (typeof config.showSeconds === 'boolean') {
this.showSeconds = config.showSeconds;
this.tick();
}
},
tick: function () {
if (!this.container) return;
const clockEl = this.container.querySelector('#clock-display');
if (!clockEl) return;
const d = new Date();
const h = String(d.getHours()).padStart(2, '0');
const m = String(d.getMinutes()).padStart(2, '0');
const s = String(d.getSeconds()).padStart(2, '0');
clockEl.textContent = this.showSeconds ? `${h}:${m}:${s}` : `${h}:${m}`;
},
destroy: function () {
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = null;
}
this.container = null;
}
};
})();
Live Interactive Widget Preview
Download the clock example. Extract it and open preview.html for a browser-only layout/lifecycle preview. Import the extracted folder or ZIP into WallCraft to test the actual injected SDK. The snippets above illustrate the files; widget.html alone is markup and does not load CSS, JavaScript or call init.
Testing & Debugging Workflow
Because Wallcraft runs as a native background desktop canvas behind Windows desktop icons (WorkerW / Progman layer), standard global browser shortcuts like F12 are not intercepted by the desktop wallpaper window. Use the following proven workflow to develop and debug:
- 1. Local Layout Preview: Open the downloaded clock’s
preview.htmlin a browser. For your own widget, provide a preview harness that loads CSS/JS and calls init; it cannot emulate native SDK services. PressF12to open DevTools, inspect DOM/CSS layout, and debug your JavaScript logic with breakpoints and live network tabs. - 2. In-App Terminal Logging: When testing your imported widget inside Wallcraft, launch the application from the command line (
npm startor Developer build). Allconsole.log,console.warn, andconsole.errormessages inside the sandboxed iframe are forwarded viawc:sandbox-logand output to the host terminal prefixed with[Widget:<widget-id>]. - 3. Live Reload via Settings: Import your widget folder via Settings → Modules & Widgets → "Import Folder". After editing your local files, toggle the widget off and back on in Settings to instantly reload the iframe without restarting Wallcraft.
PostMessage RPC Protocol & Error Guarantees
Sandboxed community widgets communicate with the WallCraft host through asynchronous JSON messages over the HTML5 postMessage bridge.
| Message Type | Direction | Description |
|---|---|---|
wc:init |
Host → Widget | Delivers initial config and language. Triggers widget init(). |
wc:update |
Host → Widget | Delivers complete merged config object on user settings modification. |
wc:pause |
Host → Widget | Sent when games run or desktop is occluded. Widget must pause rAF loops and intervals. |
wc:resume |
Host → Widget | Sent when desktop is revealed. Widget restarts timers and animations. |
wc:language-change |
Host → Widget | Broadcasts updated language code ("en", "tr"). |
wc:theme-update |
Host → Widget | Delivers updated CSS theme variables ({ variables }) for live instant styling. |
wc:audio-state-update |
Host → Widget | Broadcasts live WASAPI master volume, peak levels, and process sessions. |
wc:audio-request |
Widget → Host | Sends WASAPI volume or mute change requests ({ reqId, action, ...args }). |
wc:audio-response |
Host → Widget | Resolves or rejects pending audio SDK promises ({ reqId, result, error }). |
wc:system-stats-request |
Widget → Host | Requests real-time CPU, RAM, GPU, and uptime telemetry. |
wc:system-stats-response |
Host → Widget | Returns system telemetry payload ({ reqId, result, error }). |
wc:request-media-info |
Widget → Host | Requests immediate refresh of Windows SMTC Now Playing metadata. |
wc:media-info |
Host → Widget | Broadcasts updated Now Playing track title, artist, album, thumbnail, and timeline. |
wc:media-control |
Widget → Host | Sends playback command ('play-pause', 'next', 'previous', etc.). |
wc:discord-request |
Widget → Host | Sends Discord Voice commands ('getState', 'toggleMute', 'toggleDeaf', etc.). |
wc:discord-response |
Host → Widget | Returns Discord RPC voice state response ({ reqId, result, error }). |
wc:discord-voice-state-update |
Host → Widget | Broadcasts Discord voice room connection changes and member rosters. |
wc:discord-voice-speaking-update |
Host → Widget | Broadcasts user speaking indicator state ({ userId, speaking, isSpeaking }). |
wc:file-request |
Widget → Host | Requests file picker dialogs, shell shortcut launch, or icon extraction. |
wc:file-response |
Host → Widget | Returns chosen file paths or execution status ({ reqId, result, error }). |
wc:request-modal |
Widget → Host | Opens native acrylic declarative modal dialog ({ reqId, widgetId, options }). |
wc:modal-result |
Host → Widget | Delivers user modal confirmation values or cancellation. |
wc:synthetic-pointer |
Host → Widget | Dispatches native desktop mouse clicks into the sandboxed iframe. |
wc:synthetic-keyboard |
Host → Widget | Forwards keystrokes from global Win32 hook when an input element is focused. |
wc:blur-active-input |
Host → Widget | Blurs active input when user clicks away on the desktop wallpaper. |
wc:destroy |
Host → Widget | Final signal before iframe unmounting. Triggers widget destroy(). |
wc:destroy: pending audio promises reject with Error('Widget destroyed'), while pending modal promises cleanly resolve to null to prevent unhandled rejection crashes.
Standardized RPC Error Catalog
| Error Message | Trigger Condition | Recommended Widget Handling |
|---|---|---|
"Widget destroyed" |
Widget iframe was unmounted while an asynchronous audio or modal request was in flight. | Expected teardown behavior; ignore in widget catch blocks or cancel pending operations in destroy(). |
"Audio Mixer service unavailable" |
Host Windows WASAPI Core Audio daemon is offline or IPC connection disconnected. | Show fallback volume controls or display an offline audio state in UI. |
"Unauthorized audio request origin" |
Request sender does not match a verified, active sandboxed community widget instance. | Security check failed; ensure widget runs inside official Wallcraft runtime environment. |
"Security violation: spoofed widget identity" |
Sender iframe attempted to request a modal using a mismatched or forged widgetId. |
Ensure your options payload matches the id specified in manifest.json. |
"Options object is required for WallCraft.showModal" |
WallCraft.showModal() was invoked with undefined, null, or a non-object. |
Pass a valid options configuration object containing at least title. |
"Modal service not available" |
Wallpaper modal overlay renderer is not initialized or is restarting. | Retry with exponential backoff or inform user to reopen the modal. |
"Volume must be a valid number" |
setMasterVolume or setAppVolume received NaN or a non-numeric argument. |
Clamp volume values to an integer between 0 and 100. |
"Invalid PID" |
setAppVolume or setAppMute was provided with an invalid process ID. |
Verify PID matches a positive integer returned by WallCraft.audio.getState(). |
"Unsupported audio action: [action]" |
An unrecognized action payload was posted across the audio RPC channel. | Use only supported methods: getState, setMasterVolume, setMasterMute, setAppVolume, setAppMute. |
This is a private JSON-message postMessage protocol, not JSON-RPC 2.0. Prefer SDK methods to sending raw messages. Promise rejection reports bridge/permission errors; a resolved result may still represent cancellation or failure. There is no automatic timeout. Pending modal requests resolve to null on destroy; pending audio/generic requests reject. Fire-and-forget media commands do not report completion.
// Illustrative SDK envelope; do not bypass permission checks.
const request = {type: 'wc:audio-request', reqId: 'unique-id', action: 'getState'};
// Response: {type: 'wc:audio-response', reqId: 'unique-id', result: state}
// Error: {type: 'wc:audio-response', reqId: 'unique-id', error: '...'}| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
onUpdate(cb), onLanguageChange(cb), onPause(cb), onResume(cb), onDestroy(cb) | void; callbacks are attached to the widget instance lifetime. | No capability permission; do not register repeatedly during update. |
config, language | Initial configuration and active language; updates arrive through callbacks. | No permission. |
showModal(options) | Promise<object|null>; cancel/destroy → null; invalid options reject. | No capability permission; handle null before using fields. |
manifest.json Package Reference
The manifest.json file defines your widget identity, layout requirements, dynamic settings form, and theme wrapper.
| Field | Type | Status | Description |
|---|---|---|---|
id |
string |
Required | 1–128 characters: letters, digits, underscore, hyphen and dot; the first character cannot be a dot. No .., trailing dot, reserved Windows names (CON/PRN/AUX/NUL/COM1–9/LPT1–9, including extensions), or __proto__, constructor, prototype (case-insensitive). Valid: sys-monitor; invalid: .hidden, x..y, CON. |
name |
string |
Recommended | Display title shown in the Wallcraft widget catalog and settings. Different items may share this name; identity is determined by ID. |
instanceName |
string |
Optional | Exact window property containing the lifecycle object, e.g. "MyCustomWidget" for window.MyCustomWidget. Allowed characters: letters, digits, underscore, dollar sign, dot and hyphen. Read as a literal property, not a nested path. If omitted, the name is derived from the last dot-separated ID segment, with hyphenated words capitalized and Widget appended (e.g. my-custom → MyCustomWidget). The runtime also accepts window.WallCraftWidget. Creator Studio preserves the original instance name when assigning a publication ID; retain this field in exported packages. |
version |
string |
Recommended | SemVer format (e.g., "1.0.0"). |
entry |
object |
Recommended | Object containing relative paths: { "html": "...", "script": "...", "css": "..." }. |
gridSpan |
object |
Recommended | Slots occupied on 24×24 grid: { "cols": 1..24, "rows": 1..24 }. |
wrapper |
string |
Optional | Frame style: "default" follows the active desktop theme; "squircle-acrylic" explicitly selects WallCraft Default. Other keys include "flat-dark", "flat-light" (Liquid Glass), "cyber-obsidian", and "none" for frameless mode. See the complete list under Design Tokens. Defaults to "default". |
pinMode |
string |
Optional | "default" (desktop layer), "top-clickable" (always-on-top interactive), "top-non-clickable" (HUD ghost). |
icon |
string |
Optional | Dedicated widget tile icon path (e.g. "icon.png", "icon.webp", "icon.svg") or URL. Automatically detected from root directory candidate files (icon.png, icon.webp, icon.svg, icon.jpg, icon.jpeg) if omitted. |
previewImage |
string |
Optional | Fullscreen preview image path (e.g. "preview.webp", "preview.png") or URL. Automatically detected from root directory candidate files (preview.webp, preview.png, preview.jpg, preview.jpeg, preview.svg) if omitted. |
permissions |
string[] |
Optional | Declared capability scopes: ["system-stats", "audio", "media", "discord", "launcher", "network"]. |
configSchema |
array |
Optional | Array of config definitions rendered as an interactive modal in Wallcraft. |
Widget Media Detection: Dedicated Icons & Fullscreen Previews
Wallcraft features an intelligent dual-media detection engine (detectWidgetMedia in widget-manager.js) for both built-in and community extensions. Developers can supply a dedicated tile icon for the Modules & Widgets catalog as well as high-definition preview images for the marketplace, completely independently:
-
Dedicated Tile Icon (
manifest.icon/icon.png): Rendered on the widget card/tile inside Wallcraft Settings. The runtime checksmanifest.iconormanifest.iconImagefirst; if omitted, it automatically scans the widget folder foricon.png,icon.webp,icon.svg,icon.jpg, oricon.jpeg. If no icon file is found, Wallcraft gracefully falls back to the preview image, or displays an elegant two-letter uppercase monogram over an algorithmic 135-degree procedural gradient. -
Fullscreen Preview (
manifest.previewImage/preview.webp): Used for full-resolution showcase previews, marketplace detail modals, and catalog cards. The runtime checksmanifest.previewImageormanifest.previewUrl; if omitted, it automatically scans the widget folder forpreview.webp,preview.png,preview.jpg,preview.jpeg, orpreview.svg. -
Zero-Config Auto-Discovery:
Declaring
iconorpreviewImageinmanifest.jsonis entirely optional. Simply droppingicon.pngandpreview.webpinto your widget root folder enables instant auto-detection upon folder or .zip import.
Declared Permissions Reference
| Permission Scope | Target API Bridge | Description |
|---|---|---|
"system-stats" |
window.WallCraft.getSystemStats() |
Grants real-time hardware telemetry: CPU load/temp, RAM used/total, GPU load/temp/VRAM, and Disk I/O. |
"audio" |
window.WallCraft.audio |
Grants Windows WASAPI Core Audio volume adjustments, peak metering, and per-process audio sessions. |
"media" |
window.WallCraft.onMediaInfo / mediaControl |
Grants Windows SMTC transport controls (Spotify, Chrome, Apple Music metadata and playback). |
"discord" |
window.WallCraft.getDiscordVoiceState |
Grants Discord Voice RPC channel state, speaking indicators, and mute/deafen actions. |
"launcher" |
window.WallCraft.launchShortcut(path) |
Grants executable launching capability for desktop shortcuts and .exe application binaries. |
"network" |
fetch() (HTTPS) |
Approved HTTPS connections only. CSP currently uses connect-src https:; WebSocket is not a supported contract. Servers must permit the sandbox’s opaque origin through CORS. |
The importer enforces a safe id. Supply name, version, entry and gridSpan for a predictable package; they are recommendations rather than a complete JSON Schema validation. Missing version falls back to 1.0.0 in package metadata. Entry files are checked/read later by the import and loader paths; a manifest accepted by the ID check alone is not necessarily a runnable widget.
Declaring a permission does not grant it. WallCraft asks for user approval and stores the decision against the package fingerprint. Denial leaves the capability unavailable; changed package contents require a new decision. Handle denied calls and explain why each scope is needed. The launcher also checks the selected target and may ask for an additional security confirmation.
async function loadJson(url, signal) {
const response = await fetch(url, {signal});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
// Declare "network" and obtain user approval first.
// Call with your HTTPS endpoint that permits opaque-origin CORS.
// Abort via AbortController on pause/destroy; catch network/permission errors.
24×24 Magnetic Grid & Viewport Scaling
Wallcraft divides the user desktop into a 24 horizontal columns × 24 vertical rows grid. When widgets are dragged, they magnetically snap to grid intersections and center axes.
| Grid Slots (cols×rows) | 1080p (1920×1080) | 1440p (2560×1440) | 4K UHD (3840×2160) | Recommended Use Case |
|---|---|---|---|---|
2 × 2 (Compact Mini) |
160px × 90px | 213px × 120px | 320px × 180px | Mini icon, battery badge, counter |
4 × 2 (Standard Badge) |
320px × 90px | 426px × 120px | 640px × 180px | Digital clock, mini hardware meter |
6 × 3 (Horizontal Card) |
480px × 135px | 640px × 180px | 960px × 270px | Weather report, compact music player |
6 × 4 (Panel Card) |
480px × 180px | 640px × 240px | 960px × 360px | Full audio mixer, notes list, calendar |
8 × 5 (Dashboard Module) |
640px × 225px | 853px × 300px | 1280px × 450px | Multi-band audio visualizer, GPU monitor |
Display Scaling (Ultrawide & Portrait)
| Aspect Ratio | Resolution | 1 Grid Slot (Width × Height) | 6 × 4 Panel Card Dimensions |
|---|---|---|---|
| Standard 16:9 | 1920 × 1080 (FHD) | 80px × 45px | 480px × 180px |
| Standard 16:9 | 2560 × 1440 (2K QHD) | 106.6px × 60px | 640px × 240px |
| Standard 16:9 | 3840 × 2160 (4K UHD) | 160px × 90px | 960px × 360px |
| Ultrawide 21:9 | 3440 × 1440 (UWQHD) | 143.3px × 60px | 860px × 240px |
| Super Ultrawide 32:9 | 5120 × 1440 (DQHD) | 213.3px × 60px | 1280px × 240px |
| Portrait 9:16 | 1080 × 1920 (Vertical) | 45px × 80px | 270px × 320px |
width: 100%; height: 100%) so panels scale proportionally on both ultrawide gaming monitors and vertical programmer displays.
JavaScript Lifecycle (`init`, `update`, `destroy`)
Every widget script exports an object containing the core lifecycle hooks. Wallcraft triggers these methods during initialization, settings change, and application shutdown:
window.WallCraftWidget = {
container: null,
// 1. Called on initial mount
init: function (container, config) {
this.container = container;
this.update(config);
},
// 2. Called whenever user edits settings
update: function (config) {
// Update DOM styles or intervals
},
// 3. Called when desktop is obscured / gaming mode
pause: function () {
// Suspend custom animations
},
// 4. Called when desktop returns to view
resume: function () {
// Resume animations
},
// 5. Called before widget is deleted
destroy: function () {
this.container = null;
}
};
See Resource Cleanup for a restartable requestAnimationFrame example. Do not start duplicate loops in update or resume; stop and restart one owned loop. Cancel fetches with AbortController and call unsubscribe functions returned by streaming APIs. The runtime’s automatic pause handling does not replace these responsibilities.
Dynamic Config Schema (`configSchema`)
When users click the gear icon on your widget, Wallcraft automatically renders an acrylic modal form based on your configSchema array. When saved, the updated configuration object is passed to your update(config) method.
| Field Type | Description | Key Parameters | Output Value |
|---|---|---|---|
"text" |
Single-line input. If key contains image, photo, url or path (case-insensitive), Settings adds an image Browse button wired to selectImage() with its default image filter. This is not a generic file/application or folder picker; the schema exposes no picker-kind option. |
default |
string |
"number" |
Numeric stepper input. | default, min, max |
number |
"boolean" / "toggle" |
Modern toggle switch. | default |
boolean |
"range" / "slider" |
Smooth range slider with live feedback. | default, min, max, step, unit |
number |
"color" |
Hex color picker palette. | default: "#D4FF00" |
string |
"select" |
Dropdown select list. Accepts flat array or { value, label } objects. |
options, default |
string | number |
"font-family" |
Central Typography Picker. Automatically exposes Wallcraft's 13 font options without defining options! | default: "default" |
string (e.g. "JetBrains Mono", "Inter") |
"reorder" / "sortable" |
Sortable list allowing users to reorder items with live up/down buttons and reset action. | items: [{ id, label }], default: ["id1", "id2"] |
string[] (sorted array of IDs) |
Reorder Schema Specification
The reorder type renders sortable cards with up/down arrows. When reordered by the user, the update(config) lifecycle hook receives a sorted array of string IDs:
// 1. In manifest.json configSchema:
{
"key": "metricOrder",
"label": "Cards Display Order",
"type": "reorder",
"items": [
{ "id": "cpu", "label": "Processor (CPU)" },
{ "id": "ram", "label": "Memory (RAM)" },
{ "id": "gpu", "label": "Graphics (GPU)" }
],
"default": ["cpu", "ram", "gpu"]
}
// 2. Delivered to update(config):
update: function (config) {
// config is ALWAYS the complete merged settings object
console.log(config.metricOrder); // ["gpu", "cpu", "ram"]
// Update DOM order using CSS flexbox or child nodes:
config.metricOrder.forEach((id, idx) => {
const el = this.container.querySelector('#' + id);
if (el) el.style.order = idx;
});
}
labelKey (i18n) vs. label
label for static strings (e.g. "Custom Title"). Use labelKey to reference Wallcraft's built-in localization dictionary (e.g. "showSeconds", "accentColor") so titles translate dynamically.
Do not use the automatic image Browse button for executables, shortcuts or folders. For a manually pasted application target, choose a neutral text key such as appTarget, for example {"key":"appTarget","type":"text","label":"Application target","default":""}. The user must save the Settings form to persist the value. Alternatively, call window.WallCraft.selectAppOrFolder() from a widget user action for a session-only selection; that call does not save configuration.
Design Tokens & 11 Frame Options
Wallcraft provides a centralized Design Token Layer. By using semantic CSS variables instead of hardcoded hex values, your widget automatically adapts to the selected frame option: 10 built-in themes (including Liquid Glass, Flat Dark, and Caldera) or Zero-Wrapper.
/* Card styled with dynamic theme tokens */
.my-card {
background: var(--wc-theme-surface);
border: 1px solid var(--wc-theme-surface-border);
border-radius: var(--wc-theme-radius-card);
color: var(--wc-theme-text-main);
font-family: var(--wc-font-primary);
}
/* Action button inheriting theme active accents */
.my-action-btn {
background: var(--wc-theme-btn-bg);
border: 1px solid var(--wc-theme-btn-border);
border-radius: var(--wc-theme-radius-btn);
color: var(--wc-theme-text-sub);
transition: all var(--wc-trans-fast);
}
.my-action-btn:hover {
background: var(--wc-theme-btn-hover);
color: var(--wc-theme-text-main);
}
/* Neon primary accent */
.my-primary-badge {
background: var(--wc-theme-primary);
box-shadow: var(--wc-glow-accent);
color: #000000;
font-weight: 700;
}
@import url(...) in widget CSS. The picker has 13 options: 11 Google Fonts families, system-ui and the theme’s default. The sandbox loads Google Fonts CSS/font resources online; offline or blocked requests use the configured fallback stacks. Local availability and zero layout shift are not guaranteed. Reference font-family: var(--wc-font-mono); or var(--wc-font-primary);.
10 Built-in Themes, Frameless Mode & CSS Variables
WallCraft includes 10 frame themes and a frameless mode. The table lists their default accents and card/button radii from the theme token definitions. Flat Dark uses an opaque surface without blur or shadows. Liquid Glass uses a translucent, separately filtered glass layer; Acrylic Blur also uses backdrop blur. Users can customize fonts, colors and radii in Settings:
Theme / wrapper key | Primary accent | Secondary accent | Card / button radius | Visual character |
|---|---|---|---|---|
WallCraft Default"squircle-acrylic" |
#FF5A36 |
#FFA959 |
14px / 14px | Smoked dark glass with layered highlights and rounded corners. |
Flat Dark"flat-dark" |
#C4B5FD |
#B8B2C4 |
24px / 10px | Opaque charcoal surface with lavender accents; no blur or elevation. |
Liquid Glass (GPU Intensive)"flat-light" |
#365A7B |
#55758B |
34px / 24px | Translucent blue-tinted glass, double rim highlights and a separate 24px blur layer. The stored theme key remains flat-light for compatibility; resource use depends on hardware. |
Cyber Obsidian"cyber-obsidian" |
#00F0FF |
#FF0055 |
8px / 6px | Titanium-style dark HUD with cyan and pink accents and compact corners. |
Glacial Pearl"glacial-pearl" |
#0284C7 |
#0EA5E9 |
24px / 12px | Light pearl surface with icy blue accents and soft rounded corners. |
Aurora Prism"aurora-prism" |
#A855F7 |
#22D3EE |
22px / 12px | Violet and cyan gradients with spectral edge highlights. |
Acrylic Blur (GPU Intensive)"acrylic-blur" |
#BAE6FD |
#38BDF8 |
36px / 14px | Translucent acrylic with 24px backdrop blur; resource use depends on hardware. |
Caldera"caldera-ember" |
#FC5000 |
#524AE9 |
40px / 14px | Warm limestone surface with orange and violet accents and broad corners. |
Slash Vault"slash-vault" |
#CC9166 |
#B85E3A |
10px / 14px | Deep onyx surface with copper accents and fine ledger-like lines. |
Linear Precision"linear-precision" |
#E4F222 |
#27A644 |
12px / 6px | Dark carbon surface with lime and green accents and precise inset borders. |
Zero-Wrapper"none" |
#D4FF00 |
#00F0FF |
0px / 8px | Transparent canvas without a wrapper border, background or card radius. |
Dynamic Theme Customization
Users can tailor any wrapper theme directly in Wallcraft Settings → Theme Tokens with immediate live preview:
- Primary & Secondary Typography: Independently select from 13 font options.
- 2-Tier Accent Color Hierarchy: Override
--theme-accent(primary) and--theme-accent-secondary(glow/badges). - Corner Curvature: Sliders for Card Border Radius (0px to 64px) and Button / Pill Radius (0px to 32px).
- Live RPC Synchronization: Changes are immediately broadcasted to all active widget iframes via
wc:theme-update.
The 13 Font Options
| Font Key | Typography Style | Characteristics |
|---|---|---|
default |
Theme-adaptive | Dynamically resolves to the native font of the active wrapper theme. |
Inter |
Neo-grotesque Sans | Clean, modern, optimized for computer screens and small UI text. |
Roboto |
Utilitarian Sans | Balanced, neutral geometry with friendly geometric curves. |
Outfit |
Geometric Display | Contemporary geometric design with high visual presence. |
JetBrains Mono |
Developer Monospace | Optimized for reading code, metrics, and digital clocks. |
Geist Mono |
Futuristic Monospace | Minimalist monospace inspired by architectural precision. |
Geist |
Neutral Sans | Engineered for high readability across dense telemetry stations. |
Bebas Neue |
Condensed Display | All-caps condensed display font perfect for bold digital clocks. |
Anton |
Poster Display | Reworked traditional grotesque with ultra-bold weight. |
DM Sans |
Low-contrast Sans | Geometric sans-serif with subtle elegance for widget bodies. |
Playfair Display |
Editorial Serif | High-contrast serif typography for classic and luxury cards. |
Fraunces |
Soft-serif Display | Expressive vintage serif inspired by early 20th century typefaces. |
system-ui |
OS Native | Native Windows Segoe UI font for seamless OS integration. |
Community Widget Localization (i18n)
Community widgets are standalone .zip archives. WallCraft passes the selected interface language to your widget through window.WallCraft.language and window.WallCraft.onLanguageChange. Each widget supplies its own translations.
const I18N = {
en: { title: "Network Monitor", download: "Download" },
tr: { title: "Ağ Monitörü", download: "İndirme" }
};
function t(key) {
const lang = window.WallCraft?.language || 'en';
return I18N[lang]?.[key] || I18N['en']?.[key] || key;
}
// Listen to live language changes
window.WallCraft?.onLanguageChange?.((newLang) => {
updateTranslations();
});
System Stats & Telemetry API (`getSystemStats`)
Widgets with the "system-stats" permission can query real-time hardware telemetry including CPU utilization, core temperature, RAM usage, GPU load, GPU temperature (via NVIDIA NVML / AMD ADL), and system uptime.
// Reading real-time hardware telemetry
const stats = await window.WallCraft.getSystemStats();
console.log('CPU Usage (%):', stats.cpu.usage);
console.log('CPU Temp (°C):', stats.cpu.temperature ?? stats.cpu.temp ?? '--'); // null if no sensor monitor active
console.log('CPU Model:', stats.cpu.model);
console.log('CPU Cores:', stats.cpu.cores);
console.log('RAM Usage (%):', stats.ram.usage);
console.log('RAM Total (GB):', stats.ram.total);
console.log('RAM Used (GB):', stats.ram.used);
// Raw byte telemetry is also available under stats.memory:
// stats.memory.used, stats.memory.total, stats.memory.free
console.log('GPU Usage (%):', stats.gpu.usage);
console.log('GPU Temp (°C):', stats.gpu.temperature ?? '--'); // Queried natively via NVML (NVIDIA) or ADL (AMD)
console.log('GPU Model:', stats.gpu.name);
console.log('System Uptime (sec):', stats.uptime);
Telemetry Object Schema
| Property | Type | Description |
|---|---|---|
stats.cpu.usage |
number |
Overall CPU utilization percentage (0–100). |
stats.cpu.temperature |
number | null |
CPU core package temperature in °C (null if sensor unavailable). |
stats.cpu.cores |
number |
Logical core count. |
stats.cpu.model |
string |
Processor model brand string. |
stats.gpu.usage |
number |
3D graphics engine load percentage (0–100). |
stats.gpu.temperature |
number | null |
Dedicated GPU temperature in °C via NVIDIA NVML or AMD ADL. |
stats.gpu.name |
string |
Graphics adapter model name. |
stats.ram.usage |
number |
System RAM utilization percentage (0–100). |
stats.ram.total / used |
number |
Total and used physical memory in gigabytes. |
stats.uptime |
number |
System running time in seconds since last boot. |
"permissions": ["system-stats"] in your widget's manifest.json to access this API.
pause() and destroy() lifecycle hooks.
| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
getSystemStats() | Promise<object|null>; permission/bridge errors reject; unavailable sensors may lack readings. | system-stats; stop your polling timers in pause/destroy. |
Core Audio SDK (`window.WallCraft.audio`)
Wallcraft exposes Windows WASAPI Core Audio controls directly to sandboxed widgets through the asynchronous window.WallCraft.audio bridge.
// 1. Read master volume and active process audio sessions
const state = await window.WallCraft.audio.getState();
console.log('Master Volume:', state.master.volume); // 0 - 100
console.log('Is Muted:', state.master.isMuted); // boolean
console.log('Active Sessions:', state.sessions); // Spotify, Chrome, etc.
// 2. Set master system volume (0 - 100)
await window.WallCraft.audio.setMasterVolume(80);
// 3. Toggle master mute
await window.WallCraft.audio.setMasterMute(true);
// 4. Control individual app volume by Process ID (PID)
await window.WallCraft.audio.setAppVolume(spotifyPid, 50);
await window.WallCraft.audio.setAppMute(spotifyPid, true);
// 5. Real-time audio change listener & peak metering
const unsubscribe = window.WallCraft.audio.onStateChange((state) => {
// Schedule rendering; requestAnimationFrame is not a rate limiter.
requestAnimationFrame(() => {
updateVolumeSlider(state.master.volume); // 0 - 100
updatePeakMeter(state.master.peak); // 0 - 100 (real-time peak)
});
});
// Remember to call unsubscribe() inside destroy()!
"permissions": ["audio"] in your widget's manifest.json to access the Windows WASAPI Core Audio mixer and session volume controls.
audioMixer.onStateUpdate(callback, {peak: false}) exposes the peak subscription option; public audio.onStateChange(callback) does not.
| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
audio.getState() | Promise<state>; bridge/permission failures reject. | audio |
audio.setMasterVolume(volume), setAppVolume(pid, volume) | Promise<{success:true, ...}> acknowledges dispatch, not verified native completion. Volume is rounded/clamped to 0–100; invalid input rejects. | audio |
audio.setMasterMute(mute), setAppMute(pid, mute) | Promise<{success:true, ...}> dispatch acknowledgement. pid must be positive; mute is converted to boolean. | audio |
audio.onStateChange(callback) | Returns an unsubscribe function; no peak option. | audio; call unsubscribe in destroy. |
setAppMute needs a build containing the 13 October 2026 preload bridge fix. Older builds may reject the request; handle errors and do not display a native success guarantee based only on the dispatch acknowledgement.
Windows SMTC Media Control API
Widgets with the "media" permission can interface with Windows System Media Transport Controls (SMTC) to read metadata from Spotify, Chrome, Edge, and Apple Music, as well as trigger playback controls.
// 1. Listen for Now Playing track metadata
window.WallCraft.onMediaInfo((mediaInfo) => {
console.log('Track Title:', mediaInfo.title);
console.log('Artist:', mediaInfo.artist);
console.log('Album:', mediaInfo.album);
console.log('Thumbnail:', mediaInfo.thumbnail); // base64 / URL
console.log('Playback Status:', mediaInfo.status); // "playing", "paused", "stopped"
console.log('Timeline:', mediaInfo.position, '/', mediaInfo.duration);
});
// 2. Request immediate metadata refresh
window.WallCraft.requestMediaInfo();
// 3. Media playback controls (Supported actions: 'play-pause', 'next', 'previous', 'mute', 'volume-up', 'volume-down')
window.WallCraft.mediaControl('play-pause');
window.WallCraft.mediaControl('next');
window.WallCraft.mediaControl('previous');
"permissions": ["media"] in your widget's manifest.json to subscribe to track metadata and dispatch media playback commands.
| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
onMediaInfo(callback) | Unsubscribe function; callback receives metadata updates. | media; unsubscribe in destroy. |
requestMediaInfo(options), mediaControl(action) | void; fire-and-forget, no completion/permission rejection Promise. | media; denied calls are not executed. |
Discord Voice RPC Integration API
Widgets with the "discord" permission connect directly to the Discord Gateway RPC client on Windows, allowing you to create live voice channel overlays and voice activity indicators on the desktop.
// 1. Read voice channel state
const discordState = await window.WallCraft.getDiscordVoiceState();
// Channel name: discordState.channelName or discordState.currentChannel?.name
console.log('Connected Channel:', discordState.channelName || discordState.currentChannel?.name);
// Members list: discordState.members or discordState.participants
console.log('Users in Room:', discordState.members || discordState.participants);
// 2. Speaking indicator hook ({ userId, speaking, isSpeaking })
window.WallCraft.onDiscordSpeaking(({ userId, speaking, isSpeaking }) => {
highlightUserAvatar(userId, isSpeaking ?? speaking);
});
// 3. Room state changes
window.WallCraft.onDiscordVoiceState((newState) => {
renderVoiceRoom(newState);
});
// 4. Mute / Deafen controls
window.WallCraft.toggleDiscordMute();
window.WallCraft.toggleDiscordDeaf();
window.WallCraft.disconnectDiscordVoice();
"permissions": ["discord"] in your widget's manifest.json to connect to Discord Voice RPC, inspect active channels, and toggle mute/deafen states.
| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
getDiscordVoiceState() | Promise<state|null>; handle disconnected state and rejection. | discord |
toggleDiscordMute(), toggleDiscordDeaf(), disconnectDiscordVoice() | Promise<null> after bridge dispatch; errors reject. | discord |
onDiscordVoiceState(cb), onDiscordSpeaking(cb) | Return unsubscribe functions. | discord; unsubscribe in destroy. |
App Launcher & Icons API
Launch installed Windows software and extract native high-resolution icons directly from executable (.exe) and shortcut (.lnk) files:
// 1. Launch a desktop shortcut or executable
await window.WallCraft.launchShortcut('C:\\Program Files\\Spotify\\Spotify.exe');
// 2. Extract base64 PNG icon directly from Windows binary
const iconBase64 = await window.WallCraft.getFileIcon('C:\\Windows\\notepad.exe');
iconImageElement.src = iconBase64;
// 3. Prompt user to select an app or folder
const targetPath = await window.WallCraft.selectAppOrFolder({
title: 'Select Application for Quick Launch'
});
launchShortcut) requires system privileges. In community widgets, launcher actions are mediated via the host bridge and require explicit user configuration.
| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
selectAppOrFolder(options) | Promise<{path,target,name}|null>; cancel → null. Folder mode: type:"folder", folder or openDirectory. | Host picker; selection is not launch authorization. |
getFileIcon(path) | Promise<string|null>; icon data URL or no icon. | Host-resolved icon only. |
launchShortcut(path) | Promise<{success:boolean,error?:string}|null>; cancellation/unsafe target may resolve with success:false; bridge errors reject. | launcher plus per-target security confirmation; UNC paths, command interpreters, dangerous scripts/shortcut destinations and .url shortcuts are restricted. |
async function chooseAndLaunch(sdk = window.WallCraft) {
const selection = await sdk.selectAppOrFolder({});
if (!selection) return {cancelled: true};
const result = await sdk.launchShortcut(selection.path);
if (!result?.success) throw new Error(result?.error || 'Launch failed');
return result;
}
// From a user action: chooseAndLaunch().catch(showError);
// Requires launcher approval and a separate target confirmation.Selection is not persistence. selectAppOrFolder() returns a selection only; it does not write widget configuration. The public sandbox SDK currently has no config-write method. Assigning the selected path to a widget variable or WallCraft.config lasts only for the current instance and can be replaced by a Settings update or reload. To retain a target, expose a neutral appTarget text field through configSchema and have the user paste the target and save the Settings form. The in-memory storage shim is not a persistence mechanism.
Native File Pickers & Wallpaper Path API
Access Windows native file picker dialogs, retrieve current system wallpaper path. These calls are mediated by the host; they do not grant direct filesystem access:
// 1. Select image or wallpaper file
const imagePath = await window.WallCraft.selectImage({ title: 'Select Background Image' });
// 2. Select video file
const videoPath = await window.WallCraft.selectVideo({ title: 'Select Video File' });
// 3. Get currently active Windows desktop wallpaper path
const wallpaperPath = await window.WallCraft.getWindowsWallpaperPath();
| Method / parameters | Return / cancellation | Permission / cleanup |
|---|---|---|
selectImage(options), selectVideo(options) | Promise<string|string[]|null>; single cancel → null, multi cancel → []. Image directory mode returns image paths or []. | No filesystem grant; host dialog only. Use multiple/multiSelections; image folder mode uses folder/directory/openDirectory. |
getWindowsWallpaperPath() | Promise<string|null>; null when not available. | No capability permission. This is a host-resolved path, not file-read access. |
Clipboard Access API (writeClipboardText)
Write plain text programmatically to the Windows system clipboard. While community widgets use synthetic keyboard shortcuts (Ctrl+C, Ctrl+V) and postMessage clipboard signals transparently, privileged host components and settings windows can trigger clipboard writes directly:
// Programmatically write text to Windows clipboard
window.wallcraft.writeClipboardText('https://getwallcraft.com');
console.log('Copied text to Windows clipboard successfully!');
<input>, <textarea>, or editable element and presses Ctrl+C or Ctrl+X, the runtime dispatches a wc:clipboard-copy postMessage event to the host, automatically populating the Windows clipboard.
Diagnostics & Feedback API
Used primarily by Wallcraft settings and troubleshooting panels to inspect GPU capabilities, memory limits, and submit in-app developer reports:
// 1. Retrieve full system and GPU diagnostics
const diag = await window.wallcraft.getSystemDiagnostics();
console.log('GPU Renderer:', diag.gpu.renderer);
console.log('VRAM Dedicated (MB):', diag.gpu.dedicatedMemory);
console.log('Electron / Chrome Version:', diag.versions.electron, diag.versions.chrome);
console.log('OS Build:', diag.os.release);
// 2. Submit user feedback or crash log
await window.wallcraft.submitFeedback({
type: 'bug', // 'bug', 'feature', 'general'
subject: 'Shader compile failed on Intel Iris',
message: 'Detailed description of the issue...',
includeDiagnostics: true
});
// 3. Listen for requests to open the feedback modal dialog
window.wallcraft.onOpenFeedbackModal(() => {
openFeedbackUI();
});
Widget Management & Package API
Powers the Wallcraft Settings UI (Modules & Widgets tab) to catalog installed widgets, safely read source files, open the community widgets directory, import packages, and delete widgets:
// 1. Retrieve all installed built-in and community widgets
const widgets = await window.wallcraft.getAvailableWidgets();
console.log('Available Widgets:', widgets);
// 2. Safely read a widget source file (e.g. manifest.json, widget.js)
const manifestContent = await window.wallcraft.readWidgetFile('my-clock-widget', 'manifest.json');
// 3. Open user-widgets directory in Windows File Explorer
await window.wallcraft.openWidgetsFolder();
// 4. Import a widget (.zip or folder mode)
const importResult = await window.wallcraft.importWidget('zip'); // 'zip' or 'folder'
console.log('Imported Widget ID:', importResult.widgetId);
// 5. Delete a community widget from disk
await window.wallcraft.deleteWidget('my-old-widget');
// 6. Listen for changes when widgets are imported or removed
window.wallcraft.onWidgetsUpdated((updatedList) => {
refreshWidgetCatalog(updatedList);
});
getAvailableWidgets() is automatically augmented with detected icon, iconImage, and previewImage absolute file:/// URLs (or remote HTTPS URLs for marketplace items), fully processed through detectWidgetMedia. The Settings UI consumes these resolved paths directly to render widget tile icons and fallbacks without manual path resolution.
Declarative Modals RPC (`window.WallCraft.showModal`)
Due to iframe sandbox policies, widgets cannot spawn unconstrained popups. Instead, Wallcraft provides a sleek, acrylic desktop modal dialog triggered via a single asynchronous promise call:
const result = await window.WallCraft.showModal({
title: "Create Reminder",
description: "Enter reminder details for your desktop.",
confirmText: "Save Note",
cancelText: "Cancel",
width: 420,
fields: [
{
id: "title",
type: "text",
label: "Note Title",
placeholder: "Meeting with team..."
},
{
id: "priority",
type: "select",
label: "Priority",
options: [
{ value: "low", label: "Low" },
{ value: "high", label: "High" }
],
default: "high"
},
{
id: "pinToTop",
type: "toggle",
label: "Pin to Top",
default: true
}
]
});
if (result && result.confirmed) {
console.log("Form Values:", result.values);
// result.values = { title: "...", priority: "high", pinToTop: true }
}
Desktop Input & Synthetic Keyboard Bridge
Wallcraft routes native Windows mouse and keyboard events directly into the sandboxed iframe:
- Pointer Simulation: Desktop clicks are translated to
PointerEventinside the sandbox. - Keyboard Hook: Focusing an
<input>or<textarea>activates a transparent Win32 keyboard hook for typing, Backspace, Enter, and Arrow keys. - Clipboard Sync:
Ctrl+C,Ctrl+V, andCtrl+Xsync seamlessly with the Windows clipboard over postMessage.
Storage & State Management (In-Memory Shim)
HTML5 sandboxed iframes without allow-same-origin cannot use normal origin-backed browser storage. WallCraft installs an In-Memory Storage Shim for localStorage and sessionStorage. Both use the same Map within the current iframe instance; values are lost when the widget reloads or its iframe is recreated. This is temporary storage, not disk-backed persistence.
For persistent preferences, declare fields with configSchema in manifest.json and have the user save them through the Settings form. Declaring the schema or calling selectAppOrFolder() alone does not save values. The public sandbox SDK has no config-write method; changing a local variable, WallCraft.config or the storage shim does not update saved Settings.
Packaging & Distribution (.zip)
To prepare a widget ZIP for local sharing or import:
- Package Structure: Ensure
manifest.jsonis located at the root of the archive (not nested in an extra subfolder). - Media Assets: Include a dedicated tile icon (
icon.pngoricon.webp, 1:1 square ratio, recommended 128×128 or 256×256 px) and a high-resolution preview image (preview.webporpreview.png, 16:9 ratio, recommended 1280×720 or 1920×1080 px) at the archive root for instant visual recognition in the Wallcraft catalog. - Flat Compression: Compress the folder contents into a standard
.zipfile (e.g.my-widget.zip). - Local Validation: Test extraction by importing the
.zipvia Wallcraft Settings -> Modules & Widgets -> "Import Package".
| Limit / check | Value |
|---|---|
| Individual file | 5 MiB |
| Total extracted content | 64 MiB |
| ZIP archive | 32 MiB |
| Archive entries | 1024 |
| Per-file compression ratio | ≤ 200 |
| Rejected paths | Unsafe/reserved names, traversal, absolute paths, case-insensitive duplicates and symlinks. |
For import errors, check the root manifest, ID/path safety and every size limit first. Do not ship caches, node_modules or build tooling in the widget ZIP. Test the exact archive you intend to share.
Publishing to Community Hub
- Publication identity: Open the in-app Community Hub submission modal and select your own saved shader, widget or preset. Display names may repeat across all types; catalog IDs must be unique across types, ignoring case. Creator Studio generates a publication ID independently of the title. For updates, select the same saved local source to retain that ID, even when renaming the item. Save an unsaved creation locally before its first submission if you plan to update it; an unsaved draft ID is retained only while the modal stays open. Hub downloads cannot be re-published.
- Export formats: Use Export Package in the submission modal after filling in metadata. Widgets export as
.zip, shaders as.json, and presets as.wallcraft-preset.json. Only widget ZIPs use a rootmanifest.json. Keep the generated ID; do not derive it from the display name or export the original local source instead. - Submit: Use Submit PR to Hub with a classic GitHub token with
public_reposcope, or use the modal’s Git commands inside a local clone of your fork after installing Git/GitHub CLI and runninggh auth login. For manual submission, extract the widget ZIP contents intowidgets/<publication-id>/; save shader JSON asshaders/<publication-id>.jsonand preset JSON aspresets/<publication-id>.wallcraft-preset.json. Preservemanifest.id,instanceNameand asset paths. A JSON payload containingwidgetFilesis not a widget manifest; use the exported ZIP. - Validation and dependencies: Widget manifest IDs and shader/preset payload IDs must match their catalog IDs. Widget pack child IDs must also be unique and must not collide with catalog items or built-in widget IDs. Publish custom widgets and shaders used by a preset first, then export the preset from the submission modal so known publication IDs are used in its references. Presets do not embed those widget packages. Maintainers regenerate
catalog-v2.jsonwith commit pins and SHA-256 hashes after reviewing source changes; contributors do not hand-edit hashes.
Multi-Monitor & Per-Monitor Profiles
Wallcraft provides a resilient multi-monitor hardware identity engine (monitor-identity.js) that decouples transient OS display indexes from physical monitors.
Core Architectural Features
- Persistent Hardware Identity: Resolves displays by EDID descriptors, PnP manufacturer IDs, physical resolution, and scale factor. Disconnecting or reconnecting a monitor preserves saved positions and layouts without corruption.
- Independent Per-Monitor Customization: Each physical monitor can run its own independent live wallpaper (different GLSL shaders, videos, image slideshows, or solid colors), its own independent set of widgets on separate 24×24 magnetic grids, and individual theme token overrides.
- Display Identify Overlays: The
showDisplayIdentifyOverlaysservice flashes high-contrast monitor numbers directly onto each physical screen, allowing users to immediately identify which display is being configured in Settings. - Multi-Profile Management: Create and switch between up to 10 persistent user profiles with inline rename, duplicate, and delete actions. Profiles can be exported and shared as
.jsonpreset files.
Always-On-Top Pinned Widgets (Overlay Mode)
In addition to standard desktop-level widgets behind application windows, Wallcraft supports Always-on-Top Pinned Overlays that float above games, full-screen apps, and browser windows.
How Pinned Overlays Operate
WS_EX_TOPMOSTManagement: Pinned widgets run inside specialized transparent overlay windows (pinnedOverlayWindows).- Foreground WinEvent Hook: A native
EVENT_SYSTEM_FOREGROUNDhook automatically re-asserts pinned widgets at the head of theHWND_TOPMOSTband whenever games, browsers, or external windows gain focus. - Transparent Click-Through Hit Testing: When the mouse is outside a pinned widget's boundary, the window applies
setIgnoreMouseEvents(true): clicks pass completely through to the game or application below. When the cursor enters widget bounds or an interactive modal opens,setIgnoreMouseEvents(false)is dynamically engaged for instant interactivity.
3D Shader Engine: WebGL 2.0 Pipeline
Wallcraft runs on a high-performance WebGL 2.0 (with WebGL 1.0 fallback) fullscreen quad. Your custom GLSL fragment shader paints directly to the GPU at 60/144+ FPS.
// Wallcraft automatically injects precision, u_resolution, u_time, and v_uv.
void main() {
vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution.xy) / min(u_resolution.x, u_resolution.y);
float d = length(uv) - 0.5;
vec3 col = vec3(sin(d * 10.0 + u_time), cos(d * 8.0 - u_time), 1.0);
gl_FragColor = vec4(col, 1.0);
}
Complete Uniforms Reference
| Uniform Name | GLSL Type | Range / Unit | Description |
|---|---|---|---|
u_resolution |
vec2 |
(width, height) |
Current screen viewport resolution in physical pixels. |
u_time |
float |
0.0 -> ∞ (sec) |
Continuous monotonic running time since shader start. |
u_deltaTime |
float |
~0.001 -> 0.1 (sec) |
Elapsed delta time between consecutive frames (for physics & motion). |
u_frame |
int |
0, 1, 2, ... |
Monotonic total rendered frame count. |
v_uv |
varying vec2 |
[0.0, 1.0] |
Normalized UV coordinates (Bottom-left: 0,0, Top-right: 1,1). |
u_mouse |
vec2 |
[0.0, 1.0] |
Current normalized mouse coordinates across the desktop. |
u_mouse_click |
vec2 |
[0.0, 1.0] |
Coordinates of the most recent mouse click event. |
u_audio_vol |
float |
[0.0, 1.0] |
Standalone capture: average normalized FFT magnitude across 32 bins, not RMS or the OS volume setting. |
u_audio_bass |
float |
[0.0, 1.0] |
Standalone: average of bins 0–3; not a fixed 20–150 Hz band. |
u_audio_mid |
float |
[0.0, 1.0] |
Standalone: average of bins 4–15; frequency boundaries depend on sample rate. |
u_audio_treble |
float |
[0.0, 1.0] |
Standalone: average of bins 16–31; visualizer mappings may differ. |
u_audio_spectrum[32] |
float[32] |
[0.0, 1.0] |
32 normalized spectrum values. Standalone capture uses linear FFT bins; the visualizer feed can use a different mapping. |
u_color1, u_color2, u_accent |
vec3 |
rgb [0.0, 1.0] |
Configured primary, secondary, and accent colors from Settings. |
u_speed |
float |
0.1 -> 5.0 |
Shader animation speed multiplier from Settings. |
u_intensity |
float |
0.1 -> 5.0 |
Visual intensity or bloom multiplier from Settings. |
u_texture / u_channel0 |
sampler2D |
Texture Unit 0 | User-selected custom wallpaper texture or video frame. |
u_noise / u_channel1 |
sampler2D |
Texture Unit 1 | Built-in 256×256 seamless smooth value noise texture. |
u_prev_frame / u_channel2 |
sampler2D |
Texture Unit 2 | Previous frame FBO texture for feedback and fluid simulations. |
u_channel3 |
sampler2D |
Texture Unit 3 | Secondary texture or multi-pass buffer channel. |
u_has_texture |
int |
0 or 1 |
1 if custom user texture is loaded; 0 otherwise. |
32-Value Audio Reactivity
Standalone shader capture uses desktop getUserMedia, an AudioContext and AnalyserNode with fftSize = 64. A requestAnimationFrame loop reads 32 FFT bins and divides byte magnitudes by 255. Capture availability and latency depend on the device and runtime; this path does not promise a worker or zero latency. A running Audio Visualizer can feed the shader through a different spectrum mapping.
Access individual frequency bands in your shader with u_audio_spectrum[i] (where i is 0 to 31), or use convenient grouped bands: u_audio_bass, u_audio_mid, and u_audio_treble.
Auto-Detected Uniform Annotations
Wallcraft automatically parses custom uniforms in your GLSL code and produces interactive sliders, color pickers, and toggles directly in the Settings UI:
// 1. Float Slider with custom ranges
uniform float u_waveSpeed = 2.5; // min: 0.1, max: 10.0, step: 0.1, label: Wave Speed
uniform int u_particles = 100; // min: 10, max: 500, label: Particle Count
// 2. Vector Parameters (2D / 4D Sliders)
uniform vec2 u_scale; // default: [1.0, 1.0], min: 0.1, max: 5.0, label: Zoom
// 3. Color Picker
uniform vec3 u_glowColor; // default: #00ffcc, label: Glow Color
// 4. Boolean Switch
uniform bool u_enableBloom; // default: true, label: Enable Bloom
Universal GLSL Compatibility
Wallcraft features an intelligent compilation layer. You can use standard GLSL 2.0 fragment shaders with zero manual rewriting:
void mainImage(out vec4 fragColor, in vec2 fragCoord)is automatically wrapped into standardvoid main().iResolution,iTime,iTimeDelta,iFrame, andiMouseare natively supported.iChannel0throughiChannel3map directly to Wallcraft texture channels.
Multi-Pass Pipeline, Ping-Pong FBO & Procedural Textures
When your shader references the u_prev_frame uniform, Wallcraft switches into ping-pong Framebuffer Object (FBO) mode. Furthermore, Wallcraft features full multi-pass Shadertoy pipeline execution:
- Buffer Passes: Full native support for
Common,Buffer A,Buffer B,Buffer C,Buffer D, and the finalImagerendering passes. - Flexible Channel Routing: Route any buffer's output to another buffer's
iChannel0..3input to create complex fluid dynamics, Navier-Stokes fluid advection, smoke dissipation, reaction-diffusion patterns, or optical feedback loops. - Built-in Procedural Textures: Bind procedural texture generators directly into channels without external image files: Smooth Value Noise, RG Noise, Rock / Stone Surface, Lichen / Moss Pattern, Wood Grain, Deep Space Stars, and Pebbles Mosaic.
Performance & Resource Cleanup
Because Wallcraft runs 24/7 on the user desktop, community widgets must never leak memory or spin CPU cycles while idle:
- Clean Timers & Listeners: Always clear
setInterval,requestAnimationFrame, andwindow.WallCraft.audio.onStateChangecallbacks inside yourdestroy()method. - Pause/resume ownership: When runtime pause is active, timer/rAF callbacks are filtered and tracked audio contexts are suspended. An interval is not removed just because its callback is skipped. Recursive rAF/timeout chains need an explicit restart in
resume(). Stop your loops inpause()and clean subscriptions/resources indestroy(); automatic handling is not a zero-CPU or leak-free guarantee. - Avoid DOM Thrashing: Do not set
textContentorstyleif values have not changed from the prior frame.
window.WallCraftWidget = new class {
init(container) { this.container = container; this.resume(); }
frame = (time) => {
this.container.style.opacity = String(0.75 + 0.25 * Math.sin(time / 1000));
this.raf = requestAnimationFrame(this.frame);
};
pause() { cancelAnimationFrame(this.raf); this.raf = null; }
resume() { this.pause(); this.raf = requestAnimationFrame(this.frame); }
destroy() { this.pause(); this.container.style.opacity = ''; }
};