wallpaper-engine 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,325 @@
1
+ # wallpaper-engine
2
+
3
+ TypeScript type definitions, a Vite plugin, and runtime helpers for building [Wallpaper Engine](https://www.wallpaperengine.io/) web wallpapers.
4
+
5
+ - **Full type coverage** for the entire Wallpaper Engine Web API — property listeners, media integration, audio, iCUE/LED plugins, and `window` augmentation
6
+ - **Vite plugin** that auto-generates `project.json` at build time with full IntelliSense on your property definitions
7
+ - **Strong inference** — define your properties once and TypeScript automatically types every key in `applyUserProperties`
8
+ - **Tree-shakeable helpers** for color conversion, audio processing, file URLs, LED encoding, and FPS-limited animation loops
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ bun add wallpaper-engine
16
+ # or
17
+ npm install wallpaper-engine
18
+ # or
19
+ pnpm add wallpaper-engine
20
+ ```
21
+
22
+ Vite is an optional peer dependency, required only if you use `wallpaper-engine/plugin`:
23
+
24
+ ```bash
25
+ bun add -d vite
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Package Exports
31
+
32
+ | Import path | Contents |
33
+ |---|---|
34
+ | `wallpaper-engine` | All TypeScript types + `window` augmentation |
35
+ | `wallpaper-engine/plugin` | Vite plugin, property builders, `WallpaperUserPropertiesOf<T>` |
36
+ | `wallpaper-engine/helpers` | Runtime utility functions |
37
+
38
+ ---
39
+
40
+ ## Vite Plugin
41
+
42
+ The plugin emits a `project.json` asset alongside your build so Wallpaper Engine can load the wallpaper without any manual file maintenance.
43
+
44
+ ### Basic setup
45
+
46
+ ```ts
47
+ // vite.config.ts
48
+ import { defineConfig } from 'vite';
49
+ import { wallpaperEnginePlugin, colorProperty, sliderProperty, boolProperty } from 'wallpaper-engine/plugin';
50
+
51
+ export default defineConfig({
52
+ plugins: [
53
+ wallpaperEnginePlugin({
54
+ title: 'My Wallpaper',
55
+ supportsAudioProcessing: true,
56
+ properties: {
57
+ bgcolor: colorProperty({ text: 'Background Color', value: '1 1 1' }),
58
+ speed: sliderProperty({ text: 'Speed', value: 1, min: 0, max: 10 }),
59
+ showClock: boolProperty({ text: 'Show Clock', value: true }),
60
+ },
61
+ }),
62
+ ],
63
+ });
64
+ ```
65
+
66
+ This outputs a `project.json` alongside your build:
67
+
68
+ ```json
69
+ {
70
+ "file": "index.html",
71
+ "title": "My Wallpaper",
72
+ "type": "web",
73
+ "supportsaudioprocessing": true,
74
+ "general": {
75
+ "properties": {
76
+ "bgcolor": { "type": "color", "text": "Background Color", "value": "1 1 1", "index": 0, "order": 0 },
77
+ "speed": { "type": "slider", "text": "Speed", "value": 1, "index": 1, "order": 1, "min": 0, "max": 10 },
78
+ "showClock": { "type": "bool", "text": "Show Clock", "value": true, "index": 2, "order": 2 }
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Property builder reference
85
+
86
+ | Builder | Property type | Runtime value |
87
+ |---|---|---|
88
+ | `colorProperty` | Color picker | `WallpaperColorValue` — `value: "R G B"` (0–1 per channel) |
89
+ | `sliderProperty` | Numeric slider | `WallpaperSliderValue` — `value: number` |
90
+ | `boolProperty` | Checkbox | `WallpaperBoolValue` — `value: boolean` |
91
+ | `comboProperty` | Dropdown | `WallpaperComboValue` — `value: string` (hidden key), `text: string` (label) |
92
+ | `textInputProperty` | Text input | `WallpaperTextValue` — `value: string` |
93
+ | `fileProperty` | File picker | `WallpaperFileValue` — `value: string` (path, prefix with `file:///`) |
94
+ | `directoryProperty` | Directory picker | `WallpaperDirectoryValue` — `value: string` (path) |
95
+
96
+ ### Localization
97
+
98
+ Property labels starting with `ui_` are resolved against the localization map:
99
+
100
+ ```ts
101
+ wallpaperEnginePlugin({
102
+ title: 'My Wallpaper',
103
+ properties: {
104
+ bgcolor: colorProperty({ text: 'ui_bgcolor', value: '0 0 0' }),
105
+ },
106
+ localization: {
107
+ 'en-us': { 'ui_bgcolor': 'Background Color' },
108
+ 'de-de': { 'ui_bgcolor': 'Hintergrundfarbe' },
109
+ },
110
+ });
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Strong Property Typing
116
+
117
+ Define your properties in a dedicated file, then import it in both `vite.config.ts` and your wallpaper source. `WallpaperUserPropertiesOf<T>` maps each definition to its exact runtime value type automatically.
118
+
119
+ ```ts
120
+ // src/properties.ts
121
+ import { colorProperty, sliderProperty, boolProperty } from 'wallpaper-engine/plugin';
122
+
123
+ export const myProperties = {
124
+ bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),
125
+ speed: sliderProperty({ text: 'Speed', value: 1, min: 0, max: 5 }),
126
+ showClock: boolProperty({ text: 'Show Clock', value: true }),
127
+ };
128
+ ```
129
+
130
+ ```ts
131
+ // vite.config.ts
132
+ import { wallpaperEnginePlugin } from 'wallpaper-engine/plugin';
133
+ import { myProperties } from './src/properties';
134
+
135
+ export default defineConfig({
136
+ plugins: [wallpaperEnginePlugin({ title: 'My Wallpaper', properties: myProperties })],
137
+ });
138
+ ```
139
+
140
+ ```ts
141
+ // src/wallpaper.ts
142
+ import type { WallpaperUserPropertiesOf } from 'wallpaper-engine/plugin';
143
+ import type { myProperties } from './properties';
144
+ import { wallpaperColorToRgb } from 'wallpaper-engine/helpers';
145
+
146
+ type MyProps = WallpaperUserPropertiesOf<typeof myProperties>;
147
+ // → { bgcolor: WallpaperColorValue; speed: WallpaperSliderValue; showClock: WallpaperBoolValue }
148
+
149
+ window.wallpaperPropertyListener = {
150
+ applyUserProperties(props: Partial<MyProps>) {
151
+ if (props.bgcolor) document.body.style.background = wallpaperColorToRgb(props.bgcolor.value);
152
+ if (props.speed) setSpeed(props.speed.value); // inferred as number ✓
153
+ if (props.showClock) toggle(props.showClock.value); // inferred as boolean ✓
154
+ },
155
+ };
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Helpers
161
+
162
+ All helpers are side-effect-free and individually tree-shakeable.
163
+
164
+ ```ts
165
+ import {
166
+ parseWallpaperColor,
167
+ wallpaperColorToRgb,
168
+ wallpaperColorToHex,
169
+ toFileUrl,
170
+ clampAudio,
171
+ leftChannel,
172
+ rightChannel,
173
+ encodeCanvasForLed,
174
+ createFpsLimiter,
175
+ } from 'wallpaper-engine/helpers';
176
+ ```
177
+
178
+ ### Color
179
+
180
+ ```ts
181
+ // "R G B" string (0–1 per channel) → { r, g, b } (0–255)
182
+ const { r, g, b } = parseWallpaperColor(props.bgcolor.value);
183
+
184
+ // → CSS "rgb(255,128,0)"
185
+ el.style.color = wallpaperColorToRgb(props.bgcolor.value);
186
+
187
+ // → CSS "#ff8000"
188
+ el.style.color = wallpaperColorToHex(props.bgcolor.value);
189
+ ```
190
+
191
+ ### Files
192
+
193
+ ```ts
194
+ // Prefix a WE path with file:/// before using it as an <img> or <video> src
195
+ img.src = toFileUrl(props.myimage.value);
196
+ ```
197
+
198
+ ### Audio
199
+
200
+ ```ts
201
+ window.wallpaperRegisterAudioListener((raw) => {
202
+ const audio = clampAudio(raw); // clamp all 128 values to 0–1
203
+ const left = leftChannel(audio); // indices 0–63 (bass → treble)
204
+ const right = rightChannel(audio); // indices 64–127 (bass → treble)
205
+ renderBars(left, right);
206
+ });
207
+ ```
208
+
209
+ > **Important — always use `window.wallpaperRegisterAudioListener`, not `globalThis.`.**
210
+ > Wallpaper Engine scans your compiled JS for this exact call to detect that the wallpaper uses audio and automatically sets `"supportsaudioprocessing": true` in its internal `project.json`. Without that flag, WE will **not** send audio data in live desktop mode (the editor preview always sends audio regardless, which can mask the problem).
211
+ >
212
+ > If audio works in the WE editor but not as a live wallpaper, open the wallpaper in the WE editor and click **Edit → Save** to force WE to write the updated `project.json`. After saving, re-apply the wallpaper as your desktop background.
213
+
214
+ ### LED / RGB
215
+
216
+ ```ts
217
+ // Encode a canvas as the RGB byte string expected by setAllDevicesByImageData
218
+ const canvas = document.getElementById('RGBCanvas') as HTMLCanvasElement;
219
+ const encoded = encodeCanvasForLed(canvas);
220
+ window.wpPlugins.led.setAllDevicesByImageData(encoded, canvas.width, canvas.height);
221
+ ```
222
+
223
+ ### FPS-limited animation loop
224
+
225
+ Mirrors the FPS cap delivered by `applyGeneralProperties`. Pass `0` for unlimited.
226
+
227
+ ```ts
228
+ const loop = createFpsLimiter((dt) => renderFrame(dt));
229
+
230
+ window.wallpaperPropertyListener = {
231
+ applyGeneralProperties(props) {
232
+ if (props.fps !== undefined) loop.setLimit(props.fps);
233
+ },
234
+ };
235
+
236
+ window.onload = () => loop.start();
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Window Augmentation
242
+
243
+ If you're not using Vite or don't need the plugin, the main `wallpaper-engine` entry is all you need. A single side-effect import augments the global `Window` interface so every WE API is fully typed — no manual `declare` blocks, no runtime cost.
244
+
245
+ ```ts
246
+ import 'wallpaper-engine';
247
+
248
+ // All of these are now fully typed:
249
+ window.wallpaperPropertyListener = { ... };
250
+ window.wallpaperRegisterAudioListener((audio) => { ... });
251
+ window.wallpaperRequestRandomFileForProperty('mydir');
252
+ window.wallpaperPluginListener = { onPluginLoaded(name, version) { ... } };
253
+ window.wpPlugins.led.setAllDevicesByImageData(encoded, w, h);
254
+ window.cue.setLedsColorsAsync(deviceIndex, leds);
255
+
256
+ // Media integration
257
+ window.wallpaperRegisterMediaPropertiesListener((e) => { /* e.title, e.artist, ... */ });
258
+ window.wallpaperRegisterMediaPlaybackListener((e) => { /* e.state */ });
259
+ window.wallpaperRegisterMediaThumbnailListener((e) => { /* e.thumbnail (base64 PNG) */ });
260
+ ```
261
+
262
+ The import is erased at compile time — nothing is added to your bundle.
263
+
264
+ Two alternatives that also work without an `import` in your source:
265
+
266
+ **`tsconfig.json`** — applies the augmentation project-wide, no import needed anywhere:
267
+ ```json
268
+ {
269
+ "compilerOptions": {
270
+ "types": ["wallpaper-engine"]
271
+ }
272
+ }
273
+ ```
274
+
275
+ **Triple-slash reference** — per-file, useful if you only want types in specific files:
276
+ ```ts
277
+ /// <reference types="wallpaper-engine" />
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Full Type Reference
283
+
284
+ All types are exported from `wallpaper-engine` (main entry).
285
+
286
+ ### Property definition types (`project.json`)
287
+
288
+ `WallpaperColorProperty` · `WallpaperSliderProperty` · `WallpaperBoolProperty` · `WallpaperComboProperty` · `WallpaperTextInputProperty` · `WallpaperFileProperty` · `WallpaperDirectoryProperty` · `WallpaperPropertyDefinition` · `WallpaperProject` · `WallpaperProjectGeneral` · `WallpaperLocalization`
289
+
290
+ ### Runtime value types (`applyUserProperties`)
291
+
292
+ `WallpaperColorValue` · `WallpaperSliderValue` · `WallpaperBoolValue` · `WallpaperComboValue` · `WallpaperTextValue` · `WallpaperFileValue` · `WallpaperDirectoryValue` · `WallpaperUserProperties` · `WallpaperGeneralProperties`
293
+
294
+ ### Listener interfaces
295
+
296
+ `WallpaperPropertyListener` · `WallpaperPluginListener`
297
+
298
+ ### Media integration
299
+
300
+ `WallpaperMediaStatusEvent` · `WallpaperMediaPropertiesEvent` · `WallpaperMediaThumbnailEvent` · `WallpaperMediaPlaybackEvent` · `WallpaperMediaPlaybackState` · `WallpaperMediaTimelineEvent`
301
+
302
+ ### iCUE / LED
303
+
304
+ `WallpaperCuePlugin` · `WallpaperLedPlugin` · `CueDeviceInfo` · `CueLedColor` · `CueLedPosition` · `CueProtocolDetails`
305
+
306
+ ---
307
+
308
+ ## Building
309
+
310
+ ```bash
311
+ bun run build # production build (ESM + CJS + .d.ts)
312
+ bun run dev # watch mode
313
+ bun run typecheck # type-check without emitting
314
+ ```
315
+
316
+ Output goes to `dist/` with the following structure:
317
+
318
+ ```
319
+ dist/
320
+ index.js / index.cjs / index.d.ts
321
+ helpers.js / helpers.cjs / helpers.d.ts
322
+ plugin/
323
+ index.js / index.cjs / index.d.ts
324
+ ```
325
+
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helpers.ts"],"sourcesContent":["/**\r\n * Common utility helpers for Wallpaper Engine web wallpapers.\r\n *\r\n * Every export is side-effect-free and tree-shakeable — import only what you need.\r\n *\r\n * @example\r\n * import { wallpaperColorToRgb, toFileUrl, clampAudio, createFpsLimiter } from 'wallpaper-engine/helpers';\r\n */\r\n\r\n// ---------------------------------------------------------------------------\r\n// Color helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse a Wallpaper Engine color string (`\"R G B\"`, each channel 0–1) into\r\n * individual 0–255 integer channels.\r\n *\r\n * @example\r\n * const { r, g, b } = parseWallpaperColor(props.mycolor.value);\r\n * ctx.fillStyle = `rgb(${r},${g},${b})`;\r\n */\r\nexport function parseWallpaperColor(value: string): {\r\n r: number;\r\n g: number;\r\n b: number;\r\n} {\r\n const parts = value.split(\" \");\r\n return {\r\n r: Math.ceil(+(parts[0] ?? \"0\") * 255),\r\n g: Math.ceil(+(parts[1] ?? \"0\") * 255),\r\n b: Math.ceil(+(parts[2] ?? \"0\") * 255),\r\n };\r\n}\r\n\r\n/**\r\n * Convert a Wallpaper Engine color string to a CSS `rgb()` value.\r\n *\r\n * @example\r\n * el.style.color = wallpaperColorToRgb(props.mycolor.value);\r\n * // → \"rgb(255,128,0)\"\r\n */\r\nexport function wallpaperColorToRgb(value: string): string {\r\n const { r, g, b } = parseWallpaperColor(value);\r\n return `rgb(${r},${g},${b})`;\r\n}\r\n\r\n/**\r\n * Convert a Wallpaper Engine color string to a CSS hex color.\r\n *\r\n * @example\r\n * el.style.color = wallpaperColorToHex(props.mycolor.value);\r\n * // → \"#ff8000\"\r\n */\r\nexport function wallpaperColorToHex(value: string): string {\r\n const { r, g, b } = parseWallpaperColor(value);\r\n return \"#\" + [r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\");\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// File URL helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Prefix a Wallpaper Engine file/directory path with `file:///` to make it\r\n * usable as an `<img>` or `<video>` `src`.\r\n *\r\n * The raw path delivered by `applyUserProperties` is not a valid URL on its own.\r\n *\r\n * @example\r\n * img.src = toFileUrl(props.myimage.value);\r\n * // → \"file:///C:/Users/.../myimage.png\"\r\n */\r\nexport function toFileUrl(path: string): string {\r\n return \"file:///\" + path;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Audio helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Clamp all values in a Wallpaper Engine audio array to the 0–1 range.\r\n *\r\n * Due to the internal FFT implementation, values can occasionally exceed 1.0.\r\n * Clamping before use is strongly recommended by the official documentation.\r\n *\r\n * @example\r\n * window.wallpaperRegisterAudioListener((raw) => {\r\n * const audio = clampAudio(raw);\r\n * renderBars(audio);\r\n * });\r\n */\r\nexport function clampAudio(audioArray: number[]): number[] {\r\n return audioArray.map((v) => Math.min(v, 1));\r\n}\r\n\r\n/**\r\n * Extract the **left** audio channel (indices 0–63) from a Wallpaper Engine\r\n * audio array. Index 0 = bass, index 63 = treble.\r\n *\r\n * The full array has 128 values: 0–63 left, 64–127 right.\r\n */\r\nexport function leftChannel(audioArray: number[]): number[] {\r\n return audioArray.slice(0, 64);\r\n}\r\n\r\n/**\r\n * Extract the **right** audio channel (indices 64–127) from a Wallpaper Engine\r\n * audio array. Index 64 = bass, index 127 = treble.\r\n *\r\n * The full array has 128 values: 0–63 left, 64–127 right.\r\n */\r\nexport function rightChannel(audioArray: number[]): number[] {\r\n return audioArray.slice(64, 128);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// RGB / LED helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Encode an `HTMLCanvasElement` into the concatenated RGB byte string expected\r\n * by `window.wpPlugins.led.setAllDevicesByImageData` and\r\n * `window.cue.setLedColorsByImageData`.\r\n *\r\n * The alpha channel is discarded. Use a small canvas (e.g. 100×20 px) for\r\n * best performance — the plugins sample it to their device layout anyway.\r\n *\r\n * @example\r\n * const canvas = document.getElementById('RGBCanvas') as HTMLCanvasElement;\r\n * const encoded = encodeCanvasForLed(canvas);\r\n * window.wpPlugins.led.setAllDevicesByImageData(encoded, canvas.width, canvas.height);\r\n */\r\nexport function encodeCanvasForLed(canvas: HTMLCanvasElement): string {\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) throw new Error(\"Could not get 2D context from canvas\");\r\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n let result = \"\";\r\n for (let i = 0; i < data.length; i += 4) {\r\n result += String.fromCodePoint(data[i]!, data[i + 1]!, data[i + 2]!);\r\n }\r\n return result;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// FPS limiter\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Create a `requestAnimationFrame` loop with an optional FPS cap that mirrors\r\n * the limit delivered by `applyGeneralProperties`. Pass `0` for unlimited.\r\n *\r\n * @param draw - Called each allowed frame with `dt` — elapsed seconds since\r\n * the last allowed frame (capped at 1 s to avoid spiral-of-death on tab focus).\r\n * @returns An object with `start`, `stop`, and `setLimit` methods.\r\n *\r\n * @example\r\n * const loop = createFpsLimiter((dt) => renderFrame(dt));\r\n *\r\n * window.wallpaperPropertyListener = {\r\n * applyGeneralProperties(props) {\r\n * if (props.fps !== undefined) loop.setLimit(props.fps);\r\n * },\r\n * };\r\n *\r\n * window.onload = () => loop.start();\r\n */\r\nexport function createFpsLimiter(draw: (dt: number) => void): {\r\n /** Start (or restart) the animation loop. */\r\n start(): void;\r\n /** Stop the animation loop. */\r\n stop(): void;\r\n /**\r\n * Set the FPS cap. Pass `0` for unlimited.\r\n * Safe to call at any time — takes effect on the next frame.\r\n */\r\n setLimit(fps: number): void;\r\n} {\r\n let limit = 0;\r\n let last = 0;\r\n let threshold = 0;\r\n let rafId: number | null = null;\r\n\r\n function loop(timestamp: number): void {\r\n rafId = requestAnimationFrame(loop);\r\n const now = timestamp / 1000;\r\n const dt = Math.min(now - last, 1);\r\n last = now;\r\n\r\n if (limit > 0) {\r\n threshold += dt;\r\n if (threshold < 1 / limit) return;\r\n threshold -= 1 / limit;\r\n }\r\n\r\n draw(dt);\r\n }\r\n\r\n return {\r\n start() {\r\n if (rafId !== null) cancelAnimationFrame(rafId);\r\n last = performance.now() / 1000;\r\n threshold = 0;\r\n rafId = requestAnimationFrame(loop);\r\n },\r\n stop() {\r\n if (rafId !== null) {\r\n cancelAnimationFrame(rafId);\r\n rafId = null;\r\n }\r\n },\r\n setLimit(fps: number) {\r\n limit = fps;\r\n },\r\n };\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,SAAS,oBAAoB,OAIlC;AACA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,SAAO;AAAA,IACL,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,EACvC;AACF;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAC3B;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC5E;AAgBO,SAAS,UAAU,MAAsB;AAC9C,SAAO,aAAa;AACtB;AAkBO,SAAS,WAAW,YAAgC;AACzD,SAAO,WAAW,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AAC7C;AAQO,SAAS,YAAY,YAAgC;AAC1D,SAAO,WAAW,MAAM,GAAG,EAAE;AAC/B;AAQO,SAAS,aAAa,YAAgC;AAC3D,SAAO,WAAW,MAAM,IAAI,GAAG;AACjC;AAmBO,SAAS,mBAAmB,QAAmC;AACpE,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAChE,QAAM,EAAE,KAAK,IAAI,IAAI,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AACnE,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,cAAU,OAAO,cAAc,KAAK,CAAC,GAAI,KAAK,IAAI,CAAC,GAAI,KAAK,IAAI,CAAC,CAAE;AAAA,EACrE;AACA,SAAO;AACT;AAyBO,SAAS,iBAAiB,MAU/B;AACA,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,MAAI,YAAY;AAChB,MAAI,QAAuB;AAE3B,WAAS,KAAK,WAAyB;AACrC,YAAQ,sBAAsB,IAAI;AAClC,UAAM,MAAM,YAAY;AACxB,UAAM,KAAK,KAAK,IAAI,MAAM,MAAM,CAAC;AACjC,WAAO;AAEP,QAAI,QAAQ,GAAG;AACb,mBAAa;AACb,UAAI,YAAY,IAAI,MAAO;AAC3B,mBAAa,IAAI;AAAA,IACnB;AAEA,SAAK,EAAE;AAAA,EACT;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,UAAU,KAAM,sBAAqB,KAAK;AAC9C,aAAO,YAAY,IAAI,IAAI;AAC3B,kBAAY;AACZ,cAAQ,sBAAsB,IAAI;AAAA,IACpC;AAAA,IACA,OAAO;AACL,UAAI,UAAU,MAAM;AAClB,6BAAqB,KAAK;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,SAAS,KAAa;AACpB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/helpers.ts"],"sourcesContent":["/**\n * Common utility helpers for Wallpaper Engine web wallpapers.\n *\n * Every export is side-effect-free and tree-shakeable — import only what you need.\n *\n * @example\n * import { wallpaperColorToRgb, toFileUrl, clampAudio, createFpsLimiter } from 'wallpaper-engine/helpers';\n */\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a Wallpaper Engine color string (`\"R G B\"`, each channel 0–1) into\n * individual 0–255 integer channels.\n *\n * @example\n * const { r, g, b } = parseWallpaperColor(props.mycolor.value);\n * ctx.fillStyle = `rgb(${r},${g},${b})`;\n */\nexport function parseWallpaperColor(value: string): {\n r: number;\n g: number;\n b: number;\n} {\n const parts = value.split(\" \");\n return {\n r: Math.ceil(+(parts[0] ?? \"0\") * 255),\n g: Math.ceil(+(parts[1] ?? \"0\") * 255),\n b: Math.ceil(+(parts[2] ?? \"0\") * 255),\n };\n}\n\n/**\n * Convert a Wallpaper Engine color string to a CSS `rgb()` value.\n *\n * @example\n * el.style.color = wallpaperColorToRgb(props.mycolor.value);\n * // → \"rgb(255,128,0)\"\n */\nexport function wallpaperColorToRgb(value: string): string {\n const { r, g, b } = parseWallpaperColor(value);\n return `rgb(${r},${g},${b})`;\n}\n\n/**\n * Convert a Wallpaper Engine color string to a CSS hex color.\n *\n * @example\n * el.style.color = wallpaperColorToHex(props.mycolor.value);\n * // → \"#ff8000\"\n */\nexport function wallpaperColorToHex(value: string): string {\n const { r, g, b } = parseWallpaperColor(value);\n return \"#\" + [r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// ---------------------------------------------------------------------------\n// File URL helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Prefix a Wallpaper Engine file/directory path with `file:///` to make it\n * usable as an `<img>` or `<video>` `src`.\n *\n * The raw path delivered by `applyUserProperties` is not a valid URL on its own.\n *\n * @example\n * img.src = toFileUrl(props.myimage.value);\n * // → \"file:///C:/Users/.../myimage.png\"\n */\nexport function toFileUrl(path: string): string {\n return \"file:///\" + path;\n}\n\n// ---------------------------------------------------------------------------\n// Audio helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Clamp all values in a Wallpaper Engine audio array to the 0–1 range.\n *\n * Due to the internal FFT implementation, values can occasionally exceed 1.0.\n * Clamping before use is strongly recommended by the official documentation.\n *\n * @example\n * window.wallpaperRegisterAudioListener((raw) => {\n * const audio = clampAudio(raw);\n * renderBars(audio);\n * });\n */\nexport function clampAudio(audioArray: number[]): number[] {\n return audioArray.map((v) => Math.min(v, 1));\n}\n\n/**\n * Extract the **left** audio channel (indices 0–63) from a Wallpaper Engine\n * audio array. Index 0 = bass, index 63 = treble.\n *\n * The full array has 128 values: 0–63 left, 64–127 right.\n */\nexport function leftChannel(audioArray: number[]): number[] {\n return audioArray.slice(0, 64);\n}\n\n/**\n * Extract the **right** audio channel (indices 64–127) from a Wallpaper Engine\n * audio array. Index 64 = bass, index 127 = treble.\n *\n * The full array has 128 values: 0–63 left, 64–127 right.\n */\nexport function rightChannel(audioArray: number[]): number[] {\n return audioArray.slice(64, 128);\n}\n\n// ---------------------------------------------------------------------------\n// RGB / LED helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Encode an `HTMLCanvasElement` into the concatenated RGB byte string expected\n * by `window.wpPlugins.led.setAllDevicesByImageData` and\n * `window.cue.setLedColorsByImageData`.\n *\n * The alpha channel is discarded. Use a small canvas (e.g. 100×20 px) for\n * best performance — the plugins sample it to their device layout anyway.\n *\n * @example\n * const canvas = document.getElementById('RGBCanvas') as HTMLCanvasElement;\n * const encoded = encodeCanvasForLed(canvas);\n * window.wpPlugins.led.setAllDevicesByImageData(encoded, canvas.width, canvas.height);\n */\nexport function encodeCanvasForLed(canvas: HTMLCanvasElement): string {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Could not get 2D context from canvas\");\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let result = \"\";\n for (let i = 0; i < data.length; i += 4) {\n result += String.fromCodePoint(data[i]!, data[i + 1]!, data[i + 2]!);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// FPS limiter\n// ---------------------------------------------------------------------------\n\n/**\n * Create a `requestAnimationFrame` loop with an optional FPS cap that mirrors\n * the limit delivered by `applyGeneralProperties`. Pass `0` for unlimited.\n *\n * @param draw - Called each allowed frame with `dt` — elapsed seconds since\n * the last allowed frame (capped at 1 s to avoid spiral-of-death on tab focus).\n * @returns An object with `start`, `stop`, and `setLimit` methods.\n *\n * @example\n * const loop = createFpsLimiter((dt) => renderFrame(dt));\n *\n * window.wallpaperPropertyListener = {\n * applyGeneralProperties(props) {\n * if (props.fps !== undefined) loop.setLimit(props.fps);\n * },\n * };\n *\n * window.onload = () => loop.start();\n */\nexport function createFpsLimiter(draw: (dt: number) => void): {\n /** Start (or restart) the animation loop. */\n start(): void;\n /** Stop the animation loop. */\n stop(): void;\n /**\n * Set the FPS cap. Pass `0` for unlimited.\n * Safe to call at any time — takes effect on the next frame.\n */\n setLimit(fps: number): void;\n} {\n let limit = 0;\n let last = 0;\n let threshold = 0;\n let rafId: number | null = null;\n\n function loop(timestamp: number): void {\n rafId = requestAnimationFrame(loop);\n const now = timestamp / 1000;\n const dt = Math.min(now - last, 1);\n last = now;\n\n if (limit > 0) {\n threshold += dt;\n if (threshold < 1 / limit) return;\n threshold -= 1 / limit;\n }\n\n draw(dt);\n }\n\n return {\n start() {\n if (rafId !== null) cancelAnimationFrame(rafId);\n last = performance.now() / 1000;\n threshold = 0;\n rafId = requestAnimationFrame(loop);\n },\n stop() {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n },\n setLimit(fps: number) {\n limit = fps;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,SAAS,oBAAoB,OAIlC;AACA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,SAAO;AAAA,IACL,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,EACvC;AACF;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAC3B;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC5E;AAgBO,SAAS,UAAU,MAAsB;AAC9C,SAAO,aAAa;AACtB;AAkBO,SAAS,WAAW,YAAgC;AACzD,SAAO,WAAW,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AAC7C;AAQO,SAAS,YAAY,YAAgC;AAC1D,SAAO,WAAW,MAAM,GAAG,EAAE;AAC/B;AAQO,SAAS,aAAa,YAAgC;AAC3D,SAAO,WAAW,MAAM,IAAI,GAAG;AACjC;AAmBO,SAAS,mBAAmB,QAAmC;AACpE,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAChE,QAAM,EAAE,KAAK,IAAI,IAAI,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AACnE,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,cAAU,OAAO,cAAc,KAAK,CAAC,GAAI,KAAK,IAAI,CAAC,GAAI,KAAK,IAAI,CAAC,CAAE;AAAA,EACrE;AACA,SAAO;AACT;AAyBO,SAAS,iBAAiB,MAU/B;AACA,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,MAAI,YAAY;AAChB,MAAI,QAAuB;AAE3B,WAAS,KAAK,WAAyB;AACrC,YAAQ,sBAAsB,IAAI;AAClC,UAAM,MAAM,YAAY;AACxB,UAAM,KAAK,KAAK,IAAI,MAAM,MAAM,CAAC;AACjC,WAAO;AAEP,QAAI,QAAQ,GAAG;AACb,mBAAa;AACb,UAAI,YAAY,IAAI,MAAO;AAC3B,mBAAa,IAAI;AAAA,IACnB;AAEA,SAAK,EAAE;AAAA,EACT;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,UAAU,KAAM,sBAAqB,KAAK;AAC9C,aAAO,YAAY,IAAI,IAAI;AAC3B,kBAAY;AACZ,cAAQ,sBAAsB,IAAI;AAAA,IACpC;AAAA,IACA,OAAO;AACL,UAAI,UAAU,MAAM;AAClB,6BAAqB,KAAK;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,SAAS,KAAa;AACpB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/helpers.ts"],"sourcesContent":["/**\r\n * Common utility helpers for Wallpaper Engine web wallpapers.\r\n *\r\n * Every export is side-effect-free and tree-shakeable — import only what you need.\r\n *\r\n * @example\r\n * import { wallpaperColorToRgb, toFileUrl, clampAudio, createFpsLimiter } from 'wallpaper-engine/helpers';\r\n */\r\n\r\n// ---------------------------------------------------------------------------\r\n// Color helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Parse a Wallpaper Engine color string (`\"R G B\"`, each channel 0–1) into\r\n * individual 0–255 integer channels.\r\n *\r\n * @example\r\n * const { r, g, b } = parseWallpaperColor(props.mycolor.value);\r\n * ctx.fillStyle = `rgb(${r},${g},${b})`;\r\n */\r\nexport function parseWallpaperColor(value: string): {\r\n r: number;\r\n g: number;\r\n b: number;\r\n} {\r\n const parts = value.split(\" \");\r\n return {\r\n r: Math.ceil(+(parts[0] ?? \"0\") * 255),\r\n g: Math.ceil(+(parts[1] ?? \"0\") * 255),\r\n b: Math.ceil(+(parts[2] ?? \"0\") * 255),\r\n };\r\n}\r\n\r\n/**\r\n * Convert a Wallpaper Engine color string to a CSS `rgb()` value.\r\n *\r\n * @example\r\n * el.style.color = wallpaperColorToRgb(props.mycolor.value);\r\n * // → \"rgb(255,128,0)\"\r\n */\r\nexport function wallpaperColorToRgb(value: string): string {\r\n const { r, g, b } = parseWallpaperColor(value);\r\n return `rgb(${r},${g},${b})`;\r\n}\r\n\r\n/**\r\n * Convert a Wallpaper Engine color string to a CSS hex color.\r\n *\r\n * @example\r\n * el.style.color = wallpaperColorToHex(props.mycolor.value);\r\n * // → \"#ff8000\"\r\n */\r\nexport function wallpaperColorToHex(value: string): string {\r\n const { r, g, b } = parseWallpaperColor(value);\r\n return \"#\" + [r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\");\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// File URL helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Prefix a Wallpaper Engine file/directory path with `file:///` to make it\r\n * usable as an `<img>` or `<video>` `src`.\r\n *\r\n * The raw path delivered by `applyUserProperties` is not a valid URL on its own.\r\n *\r\n * @example\r\n * img.src = toFileUrl(props.myimage.value);\r\n * // → \"file:///C:/Users/.../myimage.png\"\r\n */\r\nexport function toFileUrl(path: string): string {\r\n return \"file:///\" + path;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Audio helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Clamp all values in a Wallpaper Engine audio array to the 0–1 range.\r\n *\r\n * Due to the internal FFT implementation, values can occasionally exceed 1.0.\r\n * Clamping before use is strongly recommended by the official documentation.\r\n *\r\n * @example\r\n * window.wallpaperRegisterAudioListener((raw) => {\r\n * const audio = clampAudio(raw);\r\n * renderBars(audio);\r\n * });\r\n */\r\nexport function clampAudio(audioArray: number[]): number[] {\r\n return audioArray.map((v) => Math.min(v, 1));\r\n}\r\n\r\n/**\r\n * Extract the **left** audio channel (indices 0–63) from a Wallpaper Engine\r\n * audio array. Index 0 = bass, index 63 = treble.\r\n *\r\n * The full array has 128 values: 0–63 left, 64–127 right.\r\n */\r\nexport function leftChannel(audioArray: number[]): number[] {\r\n return audioArray.slice(0, 64);\r\n}\r\n\r\n/**\r\n * Extract the **right** audio channel (indices 64–127) from a Wallpaper Engine\r\n * audio array. Index 64 = bass, index 127 = treble.\r\n *\r\n * The full array has 128 values: 0–63 left, 64–127 right.\r\n */\r\nexport function rightChannel(audioArray: number[]): number[] {\r\n return audioArray.slice(64, 128);\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// RGB / LED helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Encode an `HTMLCanvasElement` into the concatenated RGB byte string expected\r\n * by `window.wpPlugins.led.setAllDevicesByImageData` and\r\n * `window.cue.setLedColorsByImageData`.\r\n *\r\n * The alpha channel is discarded. Use a small canvas (e.g. 100×20 px) for\r\n * best performance — the plugins sample it to their device layout anyway.\r\n *\r\n * @example\r\n * const canvas = document.getElementById('RGBCanvas') as HTMLCanvasElement;\r\n * const encoded = encodeCanvasForLed(canvas);\r\n * window.wpPlugins.led.setAllDevicesByImageData(encoded, canvas.width, canvas.height);\r\n */\r\nexport function encodeCanvasForLed(canvas: HTMLCanvasElement): string {\r\n const ctx = canvas.getContext(\"2d\");\r\n if (!ctx) throw new Error(\"Could not get 2D context from canvas\");\r\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n let result = \"\";\r\n for (let i = 0; i < data.length; i += 4) {\r\n result += String.fromCodePoint(data[i]!, data[i + 1]!, data[i + 2]!);\r\n }\r\n return result;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// FPS limiter\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Create a `requestAnimationFrame` loop with an optional FPS cap that mirrors\r\n * the limit delivered by `applyGeneralProperties`. Pass `0` for unlimited.\r\n *\r\n * @param draw - Called each allowed frame with `dt` — elapsed seconds since\r\n * the last allowed frame (capped at 1 s to avoid spiral-of-death on tab focus).\r\n * @returns An object with `start`, `stop`, and `setLimit` methods.\r\n *\r\n * @example\r\n * const loop = createFpsLimiter((dt) => renderFrame(dt));\r\n *\r\n * window.wallpaperPropertyListener = {\r\n * applyGeneralProperties(props) {\r\n * if (props.fps !== undefined) loop.setLimit(props.fps);\r\n * },\r\n * };\r\n *\r\n * window.onload = () => loop.start();\r\n */\r\nexport function createFpsLimiter(draw: (dt: number) => void): {\r\n /** Start (or restart) the animation loop. */\r\n start(): void;\r\n /** Stop the animation loop. */\r\n stop(): void;\r\n /**\r\n * Set the FPS cap. Pass `0` for unlimited.\r\n * Safe to call at any time — takes effect on the next frame.\r\n */\r\n setLimit(fps: number): void;\r\n} {\r\n let limit = 0;\r\n let last = 0;\r\n let threshold = 0;\r\n let rafId: number | null = null;\r\n\r\n function loop(timestamp: number): void {\r\n rafId = requestAnimationFrame(loop);\r\n const now = timestamp / 1000;\r\n const dt = Math.min(now - last, 1);\r\n last = now;\r\n\r\n if (limit > 0) {\r\n threshold += dt;\r\n if (threshold < 1 / limit) return;\r\n threshold -= 1 / limit;\r\n }\r\n\r\n draw(dt);\r\n }\r\n\r\n return {\r\n start() {\r\n if (rafId !== null) cancelAnimationFrame(rafId);\r\n last = performance.now() / 1000;\r\n threshold = 0;\r\n rafId = requestAnimationFrame(loop);\r\n },\r\n stop() {\r\n if (rafId !== null) {\r\n cancelAnimationFrame(rafId);\r\n rafId = null;\r\n }\r\n },\r\n setLimit(fps: number) {\r\n limit = fps;\r\n },\r\n };\r\n}\r\n"],"mappings":";AAqBO,SAAS,oBAAoB,OAIlC;AACA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,SAAO;AAAA,IACL,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,EACvC;AACF;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAC3B;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC5E;AAgBO,SAAS,UAAU,MAAsB;AAC9C,SAAO,aAAa;AACtB;AAkBO,SAAS,WAAW,YAAgC;AACzD,SAAO,WAAW,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AAC7C;AAQO,SAAS,YAAY,YAAgC;AAC1D,SAAO,WAAW,MAAM,GAAG,EAAE;AAC/B;AAQO,SAAS,aAAa,YAAgC;AAC3D,SAAO,WAAW,MAAM,IAAI,GAAG;AACjC;AAmBO,SAAS,mBAAmB,QAAmC;AACpE,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAChE,QAAM,EAAE,KAAK,IAAI,IAAI,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AACnE,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,cAAU,OAAO,cAAc,KAAK,CAAC,GAAI,KAAK,IAAI,CAAC,GAAI,KAAK,IAAI,CAAC,CAAE;AAAA,EACrE;AACA,SAAO;AACT;AAyBO,SAAS,iBAAiB,MAU/B;AACA,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,MAAI,YAAY;AAChB,MAAI,QAAuB;AAE3B,WAAS,KAAK,WAAyB;AACrC,YAAQ,sBAAsB,IAAI;AAClC,UAAM,MAAM,YAAY;AACxB,UAAM,KAAK,KAAK,IAAI,MAAM,MAAM,CAAC;AACjC,WAAO;AAEP,QAAI,QAAQ,GAAG;AACb,mBAAa;AACb,UAAI,YAAY,IAAI,MAAO;AAC3B,mBAAa,IAAI;AAAA,IACnB;AAEA,SAAK,EAAE;AAAA,EACT;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,UAAU,KAAM,sBAAqB,KAAK;AAC9C,aAAO,YAAY,IAAI,IAAI;AAC3B,kBAAY;AACZ,cAAQ,sBAAsB,IAAI;AAAA,IACpC;AAAA,IACA,OAAO;AACL,UAAI,UAAU,MAAM;AAClB,6BAAqB,KAAK;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,SAAS,KAAa;AACpB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/helpers.ts"],"sourcesContent":["/**\n * Common utility helpers for Wallpaper Engine web wallpapers.\n *\n * Every export is side-effect-free and tree-shakeable — import only what you need.\n *\n * @example\n * import { wallpaperColorToRgb, toFileUrl, clampAudio, createFpsLimiter } from 'wallpaper-engine/helpers';\n */\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a Wallpaper Engine color string (`\"R G B\"`, each channel 0–1) into\n * individual 0–255 integer channels.\n *\n * @example\n * const { r, g, b } = parseWallpaperColor(props.mycolor.value);\n * ctx.fillStyle = `rgb(${r},${g},${b})`;\n */\nexport function parseWallpaperColor(value: string): {\n r: number;\n g: number;\n b: number;\n} {\n const parts = value.split(\" \");\n return {\n r: Math.ceil(+(parts[0] ?? \"0\") * 255),\n g: Math.ceil(+(parts[1] ?? \"0\") * 255),\n b: Math.ceil(+(parts[2] ?? \"0\") * 255),\n };\n}\n\n/**\n * Convert a Wallpaper Engine color string to a CSS `rgb()` value.\n *\n * @example\n * el.style.color = wallpaperColorToRgb(props.mycolor.value);\n * // → \"rgb(255,128,0)\"\n */\nexport function wallpaperColorToRgb(value: string): string {\n const { r, g, b } = parseWallpaperColor(value);\n return `rgb(${r},${g},${b})`;\n}\n\n/**\n * Convert a Wallpaper Engine color string to a CSS hex color.\n *\n * @example\n * el.style.color = wallpaperColorToHex(props.mycolor.value);\n * // → \"#ff8000\"\n */\nexport function wallpaperColorToHex(value: string): string {\n const { r, g, b } = parseWallpaperColor(value);\n return \"#\" + [r, g, b].map((c) => c.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// ---------------------------------------------------------------------------\n// File URL helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Prefix a Wallpaper Engine file/directory path with `file:///` to make it\n * usable as an `<img>` or `<video>` `src`.\n *\n * The raw path delivered by `applyUserProperties` is not a valid URL on its own.\n *\n * @example\n * img.src = toFileUrl(props.myimage.value);\n * // → \"file:///C:/Users/.../myimage.png\"\n */\nexport function toFileUrl(path: string): string {\n return \"file:///\" + path;\n}\n\n// ---------------------------------------------------------------------------\n// Audio helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Clamp all values in a Wallpaper Engine audio array to the 0–1 range.\n *\n * Due to the internal FFT implementation, values can occasionally exceed 1.0.\n * Clamping before use is strongly recommended by the official documentation.\n *\n * @example\n * window.wallpaperRegisterAudioListener((raw) => {\n * const audio = clampAudio(raw);\n * renderBars(audio);\n * });\n */\nexport function clampAudio(audioArray: number[]): number[] {\n return audioArray.map((v) => Math.min(v, 1));\n}\n\n/**\n * Extract the **left** audio channel (indices 0–63) from a Wallpaper Engine\n * audio array. Index 0 = bass, index 63 = treble.\n *\n * The full array has 128 values: 0–63 left, 64–127 right.\n */\nexport function leftChannel(audioArray: number[]): number[] {\n return audioArray.slice(0, 64);\n}\n\n/**\n * Extract the **right** audio channel (indices 64–127) from a Wallpaper Engine\n * audio array. Index 64 = bass, index 127 = treble.\n *\n * The full array has 128 values: 0–63 left, 64–127 right.\n */\nexport function rightChannel(audioArray: number[]): number[] {\n return audioArray.slice(64, 128);\n}\n\n// ---------------------------------------------------------------------------\n// RGB / LED helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Encode an `HTMLCanvasElement` into the concatenated RGB byte string expected\n * by `window.wpPlugins.led.setAllDevicesByImageData` and\n * `window.cue.setLedColorsByImageData`.\n *\n * The alpha channel is discarded. Use a small canvas (e.g. 100×20 px) for\n * best performance — the plugins sample it to their device layout anyway.\n *\n * @example\n * const canvas = document.getElementById('RGBCanvas') as HTMLCanvasElement;\n * const encoded = encodeCanvasForLed(canvas);\n * window.wpPlugins.led.setAllDevicesByImageData(encoded, canvas.width, canvas.height);\n */\nexport function encodeCanvasForLed(canvas: HTMLCanvasElement): string {\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Could not get 2D context from canvas\");\n const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let result = \"\";\n for (let i = 0; i < data.length; i += 4) {\n result += String.fromCodePoint(data[i]!, data[i + 1]!, data[i + 2]!);\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// FPS limiter\n// ---------------------------------------------------------------------------\n\n/**\n * Create a `requestAnimationFrame` loop with an optional FPS cap that mirrors\n * the limit delivered by `applyGeneralProperties`. Pass `0` for unlimited.\n *\n * @param draw - Called each allowed frame with `dt` — elapsed seconds since\n * the last allowed frame (capped at 1 s to avoid spiral-of-death on tab focus).\n * @returns An object with `start`, `stop`, and `setLimit` methods.\n *\n * @example\n * const loop = createFpsLimiter((dt) => renderFrame(dt));\n *\n * window.wallpaperPropertyListener = {\n * applyGeneralProperties(props) {\n * if (props.fps !== undefined) loop.setLimit(props.fps);\n * },\n * };\n *\n * window.onload = () => loop.start();\n */\nexport function createFpsLimiter(draw: (dt: number) => void): {\n /** Start (or restart) the animation loop. */\n start(): void;\n /** Stop the animation loop. */\n stop(): void;\n /**\n * Set the FPS cap. Pass `0` for unlimited.\n * Safe to call at any time — takes effect on the next frame.\n */\n setLimit(fps: number): void;\n} {\n let limit = 0;\n let last = 0;\n let threshold = 0;\n let rafId: number | null = null;\n\n function loop(timestamp: number): void {\n rafId = requestAnimationFrame(loop);\n const now = timestamp / 1000;\n const dt = Math.min(now - last, 1);\n last = now;\n\n if (limit > 0) {\n threshold += dt;\n if (threshold < 1 / limit) return;\n threshold -= 1 / limit;\n }\n\n draw(dt);\n }\n\n return {\n start() {\n if (rafId !== null) cancelAnimationFrame(rafId);\n last = performance.now() / 1000;\n threshold = 0;\n rafId = requestAnimationFrame(loop);\n },\n stop() {\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n },\n setLimit(fps: number) {\n limit = fps;\n },\n };\n}\n"],"mappings":";AAqBO,SAAS,oBAAoB,OAIlC;AACA,QAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,SAAO;AAAA,IACL,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,IACrC,GAAG,KAAK,KAAK,EAAE,MAAM,CAAC,KAAK,OAAO,GAAG;AAAA,EACvC;AACF;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;AAC3B;AASO,SAAS,oBAAoB,OAAuB;AACzD,QAAM,EAAE,GAAG,GAAG,EAAE,IAAI,oBAAoB,KAAK;AAC7C,SAAO,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC5E;AAgBO,SAAS,UAAU,MAAsB;AAC9C,SAAO,aAAa;AACtB;AAkBO,SAAS,WAAW,YAAgC;AACzD,SAAO,WAAW,IAAI,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AAC7C;AAQO,SAAS,YAAY,YAAgC;AAC1D,SAAO,WAAW,MAAM,GAAG,EAAE;AAC/B;AAQO,SAAS,aAAa,YAAgC;AAC3D,SAAO,WAAW,MAAM,IAAI,GAAG;AACjC;AAmBO,SAAS,mBAAmB,QAAmC;AACpE,QAAM,MAAM,OAAO,WAAW,IAAI;AAClC,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sCAAsC;AAChE,QAAM,EAAE,KAAK,IAAI,IAAI,aAAa,GAAG,GAAG,OAAO,OAAO,OAAO,MAAM;AACnE,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,cAAU,OAAO,cAAc,KAAK,CAAC,GAAI,KAAK,IAAI,CAAC,GAAI,KAAK,IAAI,CAAC,CAAE;AAAA,EACrE;AACA,SAAO;AACT;AAyBO,SAAS,iBAAiB,MAU/B;AACA,MAAI,QAAQ;AACZ,MAAI,OAAO;AACX,MAAI,YAAY;AAChB,MAAI,QAAuB;AAE3B,WAAS,KAAK,WAAyB;AACrC,YAAQ,sBAAsB,IAAI;AAClC,UAAM,MAAM,YAAY;AACxB,UAAM,KAAK,KAAK,IAAI,MAAM,MAAM,CAAC;AACjC,WAAO;AAEP,QAAI,QAAQ,GAAG;AACb,mBAAa;AACb,UAAI,YAAY,IAAI,MAAO;AAC3B,mBAAa,IAAI;AAAA,IACnB;AAEA,SAAK,EAAE;AAAA,EACT;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,UAAU,KAAM,sBAAqB,KAAK;AAC9C,aAAO,YAAY,IAAI,IAAI;AAC3B,kBAAY;AACZ,cAAQ,sBAAsB,IAAI;AAAA,IACpC;AAAA,IACA,OAAO;AACL,UAAI,UAAU,MAAM;AAClB,6BAAqB,KAAK;AAC1B,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,SAAS,KAAa;AACpB,cAAQ;AAAA,IACV;AAAA,EACF;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/types/listeners.ts"],"sourcesContent":["// Types for project.json structure\r\nexport type {\r\n WallpaperBoolProperty,\r\n WallpaperColorProperty,\r\n WallpaperComboOption,\r\n WallpaperComboProperty,\r\n WallpaperDirectoryProperty,\r\n WallpaperFileProperty,\r\n WallpaperFileType,\r\n WallpaperLocalization,\r\n WallpaperProject,\r\n WallpaperProjectGeneral,\r\n WallpaperPropertyDefinition,\r\n WallpaperSliderProperty,\r\n WallpaperTextInputProperty,\r\n} from \"./types/project\";\r\n\r\n// Runtime listener / event types\r\nexport type {\r\n CueDeviceInfo,\r\n CueLedColor,\r\n CueLedPosition,\r\n CueProtocolDetails,\r\n WallpaperBoolValue,\r\n WallpaperColorValue,\r\n WallpaperComboValue,\r\n WallpaperCuePlugin,\r\n WallpaperDirectoryValue,\r\n WallpaperFileValue,\r\n WallpaperGeneralProperties,\r\n WallpaperLedPlugin,\r\n WallpaperMediaPlaybackEvent,\r\n WallpaperMediaPlaybackState,\r\n WallpaperMediaPropertiesEvent,\r\n WallpaperMediaStatusEvent,\r\n WallpaperMediaThumbnailEvent,\r\n WallpaperMediaTimelineEvent,\r\n WallpaperPluginListener,\r\n WallpaperPropertyListener,\r\n WallpaperPropertyRuntimeValue,\r\n WallpaperSliderValue,\r\n WallpaperTextValue,\r\n WallpaperUserProperties,\r\n} from \"./types/listeners\";\r\n\r\nexport {\r\n PLAYBACK_PAUSED,\r\n PLAYBACK_PLAYING,\r\n PLAYBACK_STOPPED,\r\n} from \"./types/listeners\";\r\n\r\n// Window global augmentation — import this file (or the package root) in your\r\n// wallpaper entry file to get typed access to window.wallpaperPropertyListener,\r\n// window.wallpaperRegisterAudioListener, etc.\r\nimport \"./types/window\";\r\n","// ---------------------------------------------------------------------------\r\n// Runtime property values (received inside applyUserProperties)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Runtime value of a color property.\r\n * `value` is `\"R G B\"` where each channel is in the **0–1** range.\r\n * Multiply by 255 to convert for CSS `rgb()`.\r\n *\r\n * @example\r\n * const [r, g, b] = props.myColor.value.split(' ').map(c => Math.ceil(+c * 255));\r\n * el.style.color = `rgb(${r},${g},${b})`;\r\n */\r\nexport interface WallpaperColorValue {\r\n /** Color string `\"R G B\"` — each channel 0.0–1.0. */\r\n value: string;\r\n}\r\n\r\n/** Runtime value of a slider property. */\r\nexport interface WallpaperSliderValue {\r\n value: number;\r\n}\r\n\r\n/** Runtime value of a checkbox (`bool`) property. */\r\nexport interface WallpaperBoolValue {\r\n value: boolean;\r\n}\r\n\r\n/**\r\n * Runtime value of a combo (dropdown) property.\r\n * `value` is the hidden key configured in the editor;\r\n * `text` is the visible display label.\r\n */\r\nexport interface WallpaperComboValue {\r\n /** The hidden value configured on the combo option. */\r\n value: string;\r\n /** Display label of the selected option (may be a `ui_` localization token). */\r\n text: string;\r\n}\r\n\r\n/** Runtime value of a text-input property. */\r\nexport interface WallpaperTextValue {\r\n value: string;\r\n}\r\n\r\n/**\r\n * Runtime value of a file property.\r\n * Prepend `\"file:///\"` to `value` before using it as a URL.\r\n *\r\n * @example\r\n * img.src = 'file:///' + props.myImage.value;\r\n */\r\nexport interface WallpaperFileValue {\r\n /** File path — prepend `\"file:///\"` before use as a URL. */\r\n value: string;\r\n}\r\n\r\n/**\r\n * Runtime value of a directory property in `ondemand` mode.\r\n * An empty string means no directory is currently selected.\r\n * Call `wallpaperRequestRandomFileForProperty` to retrieve a random file.\r\n */\r\nexport interface WallpaperDirectoryValue {\r\n /** Directory path, or empty string when no directory is selected. */\r\n value: string;\r\n}\r\n\r\n/** Union of all possible runtime property values delivered by `applyUserProperties`. */\r\nexport type WallpaperPropertyRuntimeValue =\r\n | WallpaperColorValue\r\n | WallpaperSliderValue\r\n | WallpaperBoolValue\r\n | WallpaperComboValue\r\n | WallpaperTextValue\r\n | WallpaperFileValue\r\n | WallpaperDirectoryValue;\r\n\r\n/**\r\n * Map of property key → current runtime value passed to\r\n * `wallpaperPropertyListener.applyUserProperties`.\r\n * On first load all properties are present; on subsequent calls only the\r\n * changed ones are included — always guard with `if (properties.key)`.\r\n */\r\nexport type WallpaperUserProperties = Record<\r\n string,\r\n WallpaperPropertyRuntimeValue\r\n>;\r\n\r\n/** App-level settings delivered by `wallpaperPropertyListener.applyGeneralProperties`. */\r\nexport interface WallpaperGeneralProperties {\r\n /** Current FPS limit set by the user in Wallpaper Engine settings. `0` = unlimited. */\r\n fps?: number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// wallpaperPropertyListener\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Assign to `window.wallpaperPropertyListener` to receive property and\r\n * app-settings updates. **Must be a top-level global** — never assign inside\r\n * `window.onload` or other events, or startup events may be missed.\r\n *\r\n * @example\r\n * window.wallpaperPropertyListener = {\r\n * applyUserProperties(properties) {\r\n * if (properties.mycolor) { ... }\r\n * },\r\n * };\r\n */\r\nexport interface WallpaperPropertyListener {\r\n /**\r\n * Called once on wallpaper load (with all user properties) and again\r\n * whenever the user changes a property. Only changed properties are\r\n * included on subsequent calls — always guard with `if (properties.key)`.\r\n */\r\n applyUserProperties?: (properties: WallpaperUserProperties) => void;\r\n /**\r\n * Called on load and whenever the user changes app-level settings\r\n * such as the FPS limit. See {@link WallpaperGeneralProperties}.\r\n */\r\n applyGeneralProperties?: (properties: WallpaperGeneralProperties) => void;\r\n /**\r\n * Called for `directory` properties in `fetchall` mode when files are\r\n * added or modified in the selected directory. Prepend `\"file:///\"` to\r\n * each path before using it as a URL.\r\n */\r\n userDirectoryFilesAddedOrChanged?: (\r\n propertyName: string,\r\n changedFiles: string[],\r\n ) => void;\r\n /**\r\n * Called for `directory` properties in `fetchall` mode when files are\r\n * removed from the selected directory.\r\n */\r\n userDirectoryFilesRemoved?: (\r\n propertyName: string,\r\n removedFiles: string[],\r\n ) => void;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// wallpaperPluginListener\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Assign to `window.wallpaperPluginListener` to detect when RGB plugins load.\r\n * Check `name` to distinguish `\"led\"` (all RGB hardware via `wpPlugins.led`)\r\n * from `\"cue\"` (Corsair iCUE direct SDK, only needed for advanced features).\r\n *\r\n * @example\r\n * window.wallpaperPluginListener = {\r\n * onPluginLoaded(name, version) {\r\n * if (name === 'led') { // general RGB hardware ready\r\n * }\r\n * if (name === 'cue') { // Corsair iCUE SDK ready\r\n * }\r\n * },\r\n * };\r\n */\r\nexport interface WallpaperPluginListener {\r\n /**\r\n * Fired when a plugin finishes loading.\r\n * @param name - `\"led\"` for general RGB hardware or `\"cue\"` for Corsair iCUE.\r\n * @param version - Plugin version string.\r\n */\r\n onPluginLoaded?: (name: string, version: string) => void;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Media integration events\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaStatusListener` when the user enables\r\n * or disables media integration in the Wallpaper Engine app settings.\r\n */\r\nexport interface WallpaperMediaStatusEvent {\r\n /** `true` when media integration is active; `false` when disabled. */\r\n enabled: boolean;\r\n}\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaPropertiesListener` when the\r\n * currently playing track's metadata changes (title, artist, album, etc.).\r\n */\r\nexport interface WallpaperMediaPropertiesEvent {\r\n /** Title of the currently playing media. */\r\n title: string;\r\n /** Artist of the currently playing media. */\r\n artist: string;\r\n /** Optional sub-title (e.g. episode name for podcasts). */\r\n subTitle?: string;\r\n /** Optional album title. */\r\n albumTitle?: string;\r\n /** Optional album artist (may differ from `artist`). */\r\n albumArtist?: string;\r\n /** Optional comma-separated list of genres. */\r\n genres?: string;\r\n /** Media type: `\"music\"`, `\"video\"`, or `\"image\"`. */\r\n contentType: \"music\" | \"video\" | \"image\";\r\n}\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaThumbnailListener` when album art changes.\r\n * Assign `event.thumbnail` directly to `img.src`.\r\n *\r\n * @example\r\n * window.wallpaperRegisterMediaThumbnailListener((e) => {\r\n * document.getElementById('cover').src = e.thumbnail;\r\n * document.body.style.background = e.primaryColor;\r\n * titleEl.style.color = e.textColor;\r\n * });\r\n */\r\nexport interface WallpaperMediaThumbnailEvent {\r\n /** Base64-encoded PNG of the album art — usable directly as `img.src`. */\r\n thumbnail: string;\r\n /** Dominant color extracted from the thumbnail. */\r\n primaryColor: string;\r\n /** Secondary color from the thumbnail palette. */\r\n secondaryColor: string;\r\n /** Tertiary color from the thumbnail palette. */\r\n tertiaryColor: string;\r\n /**\r\n * Text color guaranteed to contrast sufficiently against `primaryColor`.\r\n * May be `secondaryColor` or `tertiaryColor` when the contrast is sufficient.\r\n */\r\n textColor: string;\r\n /** Either black or white — whichever contrasts more against `primaryColor`. */\r\n highContrastColor: string;\r\n}\r\n\r\n/** Media is actively playing on the system. */\r\nexport const PLAYBACK_PLAYING = 0 as const;\r\n/** Media was playing but has been temporarily paused by the user. */\r\nexport const PLAYBACK_PAUSED = 1 as const;\r\n/** Media playback is completely stopped. */\r\nexport const PLAYBACK_STOPPED = 2 as const;\r\n\r\n/**\r\n * Numeric playback state for {@link WallpaperMediaPlaybackEvent}.\r\n * Compare against `window.wallpaperMediaIntegration.PLAYBACK_PLAYING` etc.\r\n * or the module-level constants {@link PLAYBACK_PLAYING}, {@link PLAYBACK_PAUSED},\r\n * {@link PLAYBACK_STOPPED}.\r\n */\r\nexport type WallpaperMediaPlaybackState =\r\n | typeof PLAYBACK_PLAYING\r\n | typeof PLAYBACK_PAUSED\r\n | typeof PLAYBACK_STOPPED;\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaPlaybackListener` when playback\r\n * starts, pauses, or stops.\r\n */\r\nexport interface WallpaperMediaPlaybackEvent {\r\n /**\r\n * Current playback state. Compare against the `PLAYBACK_*` constants:\r\n * - `PLAYBACK_PLAYING` (`0`) — actively playing\r\n * - `PLAYBACK_PAUSED` (`1`) — temporarily paused\r\n * - `PLAYBACK_STOPPED` (`2`) — playback stopped\r\n */\r\n state: WallpaperMediaPlaybackState;\r\n}\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaTimelineListener` when track position\r\n * changes. **Not all media players support this** — ensure your wallpaper\r\n * works correctly even if this listener is never called.\r\n */\r\nexport interface WallpaperMediaTimelineEvent {\r\n /** Current playback position in seconds. */\r\n position: number;\r\n /** Total track duration in seconds. */\r\n duration: number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Corsair iCUE SDK types (window.cue)\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Returned by `window.cue.getProtocolDetails` */\r\nexport interface CueProtocolDetails {\r\n sdkVersion: string;\r\n serverVersion: string;\r\n sdkProtocolVersion: number;\r\n serverProtocolVersion: number;\r\n breakingChanges: boolean;\r\n}\r\n\r\n/** Returned by `window.cue.getDeviceInfo` — mirrors `CorsairDeviceInfo` in the C++ SDK */\r\nexport interface CueDeviceInfo {\r\n /** `CorsairDeviceType` enum value */\r\n type: number;\r\n /** Human-readable device model name */\r\n model: string;\r\n /** `CorsairPhysicalLayout` enum value */\r\n physicalLayout: number;\r\n /** `CorsairLogicalLayout` enum value */\r\n logicalLayout: number;\r\n /** Number of available LEDs on this device */\r\n ledCount: number;\r\n /** `CorsairDeviceCaps` bitmask */\r\n capsMask: number;\r\n}\r\n\r\n/** An entry in the array returned by `window.cue.getLedPositionsByDeviceIndex` */\r\nexport interface CueLedPosition {\r\n /** `CorsairLedId` as integer */\r\n ledId: number;\r\n /** `CorsairLedId` as string */\r\n ledIdName: string;\r\n /** Position in mm from the top of the device */\r\n top: number;\r\n /** Position in mm from the left of the device */\r\n left: number;\r\n width: number;\r\n height: number;\r\n}\r\n\r\n/** LED color entry used in `setLedsColorsAsync` / `setAllLedsColorsAsync` */\r\nexport interface CueLedColor {\r\n /** `CorsairLedId` as integer */\r\n ledId: number;\r\n r: number;\r\n g: number;\r\n b: number;\r\n}\r\n\r\n/**\r\n * Direct Corsair iCUE SDK access exposed as `window.cue`.\r\n * Only available after the `\"cue\"` plugin has loaded via `wallpaperPluginListener`.\r\n */\r\nexport interface WallpaperCuePlugin {\r\n /** Returns current iCUE SDK status and version info */\r\n getProtocolDetails(\r\n callback: (protocolDetails: CueProtocolDetails) => void,\r\n ): void;\r\n /** Returns the number of recognised iCUE-compatible devices */\r\n getDeviceCount(callback: (deviceCount: number) => void): void;\r\n /** Returns hardware details for a specific device by index */\r\n getDeviceInfo(\r\n deviceIndex: number,\r\n callback: (deviceInfo: CueDeviceInfo) => void,\r\n ): void;\r\n /** Returns all LED positions for a specific device */\r\n getLedPositionsByDeviceIndex(\r\n callback: (arrayOfLEDs: CueLedPosition[]) => void,\r\n ): void;\r\n /** Update specific LEDs by supplying an array of `CueLedColor` objects */\r\n setLedsColorsAsync(arrayOfLEDColors: CueLedColor[]): void;\r\n /** Set all LEDs on one or more devices to a single color */\r\n setAllLedsColorsAsync(\r\n deviceIndexOrArray: number | number[],\r\n LEDColor: CueLedColor,\r\n ): void;\r\n /**\r\n * Update device LEDs from an RGB bitmap string (same format as\r\n * `wpPlugins.led.setAllDevicesByImageData`, but targets specific devices).\r\n */\r\n setLedColorsByImageData(\r\n deviceIndexOrArray: number | number[],\r\n encodedImageData: string,\r\n width: number,\r\n height: number,\r\n ): void;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// RGB / LED plugin\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface WallpaperLedPlugin {\r\n /**\r\n * Push concatenated RGB bytes (as a string) to all connected LED devices.\r\n * Use a small canvas (e.g. 100×20) and convert it with `getImageData` for\r\n * best performance.\r\n */\r\n setAllDevicesByImageData(\r\n encodedImageData: string,\r\n width: number,\r\n height: number,\r\n ): void;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyOO,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/types/listeners.ts"],"sourcesContent":["// Types for project.json structure\nexport type {\n WallpaperBoolProperty,\n WallpaperColorProperty,\n WallpaperComboOption,\n WallpaperComboProperty,\n WallpaperDirectoryProperty,\n WallpaperFileProperty,\n WallpaperFileType,\n WallpaperLocalization,\n WallpaperProject,\n WallpaperProjectGeneral,\n WallpaperPropertyDefinition,\n WallpaperSliderProperty,\n WallpaperTextInputProperty,\n} from \"./types/project\";\n\n// Runtime listener / event types\nexport type {\n CueDeviceInfo,\n CueLedColor,\n CueLedPosition,\n CueProtocolDetails,\n WallpaperBoolValue,\n WallpaperColorValue,\n WallpaperComboValue,\n WallpaperCuePlugin,\n WallpaperDirectoryValue,\n WallpaperFileValue,\n WallpaperGeneralProperties,\n WallpaperLedPlugin,\n WallpaperMediaPlaybackEvent,\n WallpaperMediaPlaybackState,\n WallpaperMediaPropertiesEvent,\n WallpaperMediaStatusEvent,\n WallpaperMediaThumbnailEvent,\n WallpaperMediaTimelineEvent,\n WallpaperPluginListener,\n WallpaperPropertyListener,\n WallpaperPropertyRuntimeValue,\n WallpaperSliderValue,\n WallpaperTextValue,\n WallpaperUserProperties,\n} from \"./types/listeners\";\n\nexport {\n PLAYBACK_PAUSED,\n PLAYBACK_PLAYING,\n PLAYBACK_STOPPED,\n} from \"./types/listeners\";\n\n// Window global augmentation — import this file (or the package root) in your\n// wallpaper entry file to get typed access to window.wallpaperPropertyListener,\n// window.wallpaperRegisterAudioListener, etc.\nimport \"./types/window\";\n","// ---------------------------------------------------------------------------\n// Runtime property values (received inside applyUserProperties)\n// ---------------------------------------------------------------------------\n\n/**\n * Runtime value of a color property.\n * `value` is `\"R G B\"` where each channel is in the **0–1** range.\n * Multiply by 255 to convert for CSS `rgb()`.\n *\n * @example\n * const [r, g, b] = props.myColor.value.split(' ').map(c => Math.ceil(+c * 255));\n * el.style.color = `rgb(${r},${g},${b})`;\n */\nexport interface WallpaperColorValue {\n /** Color string `\"R G B\"` — each channel 0.0–1.0. */\n value: string;\n}\n\n/** Runtime value of a slider property. */\nexport interface WallpaperSliderValue {\n value: number;\n}\n\n/** Runtime value of a checkbox (`bool`) property. */\nexport interface WallpaperBoolValue {\n value: boolean;\n}\n\n/**\n * Runtime value of a combo (dropdown) property.\n * `value` is the hidden key configured in the editor;\n * `text` is the visible display label.\n */\nexport interface WallpaperComboValue {\n /** The hidden value configured on the combo option. */\n value: string;\n /** Display label of the selected option (may be a `ui_` localization token). */\n text: string;\n}\n\n/** Runtime value of a text-input property. */\nexport interface WallpaperTextValue {\n value: string;\n}\n\n/**\n * Runtime value of a file property.\n * Prepend `\"file:///\"` to `value` before using it as a URL.\n *\n * @example\n * img.src = 'file:///' + props.myImage.value;\n */\nexport interface WallpaperFileValue {\n /** File path — prepend `\"file:///\"` before use as a URL. */\n value: string;\n}\n\n/**\n * Runtime value of a directory property in `ondemand` mode.\n * An empty string means no directory is currently selected.\n * Call `wallpaperRequestRandomFileForProperty` to retrieve a random file.\n */\nexport interface WallpaperDirectoryValue {\n /** Directory path, or empty string when no directory is selected. */\n value: string;\n}\n\n/** Union of all possible runtime property values delivered by `applyUserProperties`. */\nexport type WallpaperPropertyRuntimeValue =\n | WallpaperColorValue\n | WallpaperSliderValue\n | WallpaperBoolValue\n | WallpaperComboValue\n | WallpaperTextValue\n | WallpaperFileValue\n | WallpaperDirectoryValue;\n\n/**\n * Map of property key → current runtime value passed to\n * `wallpaperPropertyListener.applyUserProperties`.\n * On first load all properties are present; on subsequent calls only the\n * changed ones are included — always guard with `if (properties.key)`.\n */\nexport type WallpaperUserProperties = Record<\n string,\n WallpaperPropertyRuntimeValue\n>;\n\n/** App-level settings delivered by `wallpaperPropertyListener.applyGeneralProperties`. */\nexport interface WallpaperGeneralProperties {\n /** Current FPS limit set by the user in Wallpaper Engine settings. `0` = unlimited. */\n fps?: number;\n}\n\n// ---------------------------------------------------------------------------\n// wallpaperPropertyListener\n// ---------------------------------------------------------------------------\n\n/**\n * Assign to `window.wallpaperPropertyListener` to receive property and\n * app-settings updates. **Must be a top-level global** — never assign inside\n * `window.onload` or other events, or startup events may be missed.\n *\n * @example\n * window.wallpaperPropertyListener = {\n * applyUserProperties(properties) {\n * if (properties.mycolor) { ... }\n * },\n * };\n */\nexport interface WallpaperPropertyListener {\n /**\n * Called once on wallpaper load (with all user properties) and again\n * whenever the user changes a property. Only changed properties are\n * included on subsequent calls — always guard with `if (properties.key)`.\n */\n applyUserProperties?: (properties: WallpaperUserProperties) => void;\n /**\n * Called on load and whenever the user changes app-level settings\n * such as the FPS limit. See {@link WallpaperGeneralProperties}.\n */\n applyGeneralProperties?: (properties: WallpaperGeneralProperties) => void;\n /**\n * Called for `directory` properties in `fetchall` mode when files are\n * added or modified in the selected directory. Prepend `\"file:///\"` to\n * each path before using it as a URL.\n */\n userDirectoryFilesAddedOrChanged?: (\n propertyName: string,\n changedFiles: string[],\n ) => void;\n /**\n * Called for `directory` properties in `fetchall` mode when files are\n * removed from the selected directory.\n */\n userDirectoryFilesRemoved?: (\n propertyName: string,\n removedFiles: string[],\n ) => void;\n}\n\n// ---------------------------------------------------------------------------\n// wallpaperPluginListener\n// ---------------------------------------------------------------------------\n\n/**\n * Assign to `window.wallpaperPluginListener` to detect when RGB plugins load.\n * Check `name` to distinguish `\"led\"` (all RGB hardware via `wpPlugins.led`)\n * from `\"cue\"` (Corsair iCUE direct SDK, only needed for advanced features).\n *\n * @example\n * window.wallpaperPluginListener = {\n * onPluginLoaded(name, version) {\n * if (name === 'led') { // general RGB hardware ready\n * }\n * if (name === 'cue') { // Corsair iCUE SDK ready\n * }\n * },\n * };\n */\nexport interface WallpaperPluginListener {\n /**\n * Fired when a plugin finishes loading.\n * @param name - `\"led\"` for general RGB hardware or `\"cue\"` for Corsair iCUE.\n * @param version - Plugin version string.\n */\n onPluginLoaded?: (name: string, version: string) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Media integration events\n// ---------------------------------------------------------------------------\n\n/**\n * Event fired by `wallpaperRegisterMediaStatusListener` when the user enables\n * or disables media integration in the Wallpaper Engine app settings.\n */\nexport interface WallpaperMediaStatusEvent {\n /** `true` when media integration is active; `false` when disabled. */\n enabled: boolean;\n}\n\n/**\n * Event fired by `wallpaperRegisterMediaPropertiesListener` when the\n * currently playing track's metadata changes (title, artist, album, etc.).\n */\nexport interface WallpaperMediaPropertiesEvent {\n /** Title of the currently playing media. */\n title: string;\n /** Artist of the currently playing media. */\n artist: string;\n /** Optional sub-title (e.g. episode name for podcasts). */\n subTitle?: string;\n /** Optional album title. */\n albumTitle?: string;\n /** Optional album artist (may differ from `artist`). */\n albumArtist?: string;\n /** Optional comma-separated list of genres. */\n genres?: string;\n /** Media type: `\"music\"`, `\"video\"`, or `\"image\"`. */\n contentType: \"music\" | \"video\" | \"image\";\n}\n\n/**\n * Event fired by `wallpaperRegisterMediaThumbnailListener` when album art changes.\n * Assign `event.thumbnail` directly to `img.src`.\n *\n * @example\n * window.wallpaperRegisterMediaThumbnailListener((e) => {\n * document.getElementById('cover').src = e.thumbnail;\n * document.body.style.background = e.primaryColor;\n * titleEl.style.color = e.textColor;\n * });\n */\nexport interface WallpaperMediaThumbnailEvent {\n /** Base64-encoded PNG of the album art — usable directly as `img.src`. */\n thumbnail: string;\n /** Dominant color extracted from the thumbnail. */\n primaryColor: string;\n /** Secondary color from the thumbnail palette. */\n secondaryColor: string;\n /** Tertiary color from the thumbnail palette. */\n tertiaryColor: string;\n /**\n * Text color guaranteed to contrast sufficiently against `primaryColor`.\n * May be `secondaryColor` or `tertiaryColor` when the contrast is sufficient.\n */\n textColor: string;\n /** Either black or white — whichever contrasts more against `primaryColor`. */\n highContrastColor: string;\n}\n\n/** Media is actively playing on the system. */\nexport const PLAYBACK_PLAYING = 0 as const;\n/** Media was playing but has been temporarily paused by the user. */\nexport const PLAYBACK_PAUSED = 1 as const;\n/** Media playback is completely stopped. */\nexport const PLAYBACK_STOPPED = 2 as const;\n\n/**\n * Numeric playback state for {@link WallpaperMediaPlaybackEvent}.\n * Compare against `window.wallpaperMediaIntegration.PLAYBACK_PLAYING` etc.\n * or the module-level constants {@link PLAYBACK_PLAYING}, {@link PLAYBACK_PAUSED},\n * {@link PLAYBACK_STOPPED}.\n */\nexport type WallpaperMediaPlaybackState =\n | typeof PLAYBACK_PLAYING\n | typeof PLAYBACK_PAUSED\n | typeof PLAYBACK_STOPPED;\n\n/**\n * Event fired by `wallpaperRegisterMediaPlaybackListener` when playback\n * starts, pauses, or stops.\n */\nexport interface WallpaperMediaPlaybackEvent {\n /**\n * Current playback state. Compare against the `PLAYBACK_*` constants:\n * - `PLAYBACK_PLAYING` (`0`) — actively playing\n * - `PLAYBACK_PAUSED` (`1`) — temporarily paused\n * - `PLAYBACK_STOPPED` (`2`) — playback stopped\n */\n state: WallpaperMediaPlaybackState;\n}\n\n/**\n * Event fired by `wallpaperRegisterMediaTimelineListener` when track position\n * changes. **Not all media players support this** — ensure your wallpaper\n * works correctly even if this listener is never called.\n */\nexport interface WallpaperMediaTimelineEvent {\n /** Current playback position in seconds. */\n position: number;\n /** Total track duration in seconds. */\n duration: number;\n}\n\n// ---------------------------------------------------------------------------\n// Corsair iCUE SDK types (window.cue)\n// ---------------------------------------------------------------------------\n\n/** Returned by `window.cue.getProtocolDetails` */\nexport interface CueProtocolDetails {\n sdkVersion: string;\n serverVersion: string;\n sdkProtocolVersion: number;\n serverProtocolVersion: number;\n breakingChanges: boolean;\n}\n\n/** Returned by `window.cue.getDeviceInfo` — mirrors `CorsairDeviceInfo` in the C++ SDK */\nexport interface CueDeviceInfo {\n /** `CorsairDeviceType` enum value */\n type: number;\n /** Human-readable device model name */\n model: string;\n /** `CorsairPhysicalLayout` enum value */\n physicalLayout: number;\n /** `CorsairLogicalLayout` enum value */\n logicalLayout: number;\n /** Number of available LEDs on this device */\n ledCount: number;\n /** `CorsairDeviceCaps` bitmask */\n capsMask: number;\n}\n\n/** An entry in the array returned by `window.cue.getLedPositionsByDeviceIndex` */\nexport interface CueLedPosition {\n /** `CorsairLedId` as integer */\n ledId: number;\n /** `CorsairLedId` as string */\n ledIdName: string;\n /** Position in mm from the top of the device */\n top: number;\n /** Position in mm from the left of the device */\n left: number;\n width: number;\n height: number;\n}\n\n/** LED color entry used in `setLedsColorsAsync` / `setAllLedsColorsAsync` */\nexport interface CueLedColor {\n /** `CorsairLedId` as integer */\n ledId: number;\n r: number;\n g: number;\n b: number;\n}\n\n/**\n * Direct Corsair iCUE SDK access exposed as `window.cue`.\n * Only available after the `\"cue\"` plugin has loaded via `wallpaperPluginListener`.\n */\nexport interface WallpaperCuePlugin {\n /** Returns current iCUE SDK status and version info */\n getProtocolDetails(\n callback: (protocolDetails: CueProtocolDetails) => void,\n ): void;\n /** Returns the number of recognised iCUE-compatible devices */\n getDeviceCount(callback: (deviceCount: number) => void): void;\n /** Returns hardware details for a specific device by index */\n getDeviceInfo(\n deviceIndex: number,\n callback: (deviceInfo: CueDeviceInfo) => void,\n ): void;\n /** Returns all LED positions for a specific device */\n getLedPositionsByDeviceIndex(\n callback: (arrayOfLEDs: CueLedPosition[]) => void,\n ): void;\n /** Update specific LEDs by supplying an array of `CueLedColor` objects */\n setLedsColorsAsync(arrayOfLEDColors: CueLedColor[]): void;\n /** Set all LEDs on one or more devices to a single color */\n setAllLedsColorsAsync(\n deviceIndexOrArray: number | number[],\n LEDColor: CueLedColor,\n ): void;\n /**\n * Update device LEDs from an RGB bitmap string (same format as\n * `wpPlugins.led.setAllDevicesByImageData`, but targets specific devices).\n */\n setLedColorsByImageData(\n deviceIndexOrArray: number | number[],\n encodedImageData: string,\n width: number,\n height: number,\n ): void;\n}\n\n// ---------------------------------------------------------------------------\n// RGB / LED plugin\n// ---------------------------------------------------------------------------\n\nexport interface WallpaperLedPlugin {\n /**\n * Push concatenated RGB bytes (as a string) to all connected LED devices.\n * Use a small canvas (e.g. 100×20) and convert it with `getImageData` for\n * best performance.\n */\n setAllDevicesByImageData(\n encodedImageData: string,\n width: number,\n height: number,\n ): void;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyOO,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;","names":[]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types/listeners.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\r\n// Runtime property values (received inside applyUserProperties)\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Runtime value of a color property.\r\n * `value` is `\"R G B\"` where each channel is in the **0–1** range.\r\n * Multiply by 255 to convert for CSS `rgb()`.\r\n *\r\n * @example\r\n * const [r, g, b] = props.myColor.value.split(' ').map(c => Math.ceil(+c * 255));\r\n * el.style.color = `rgb(${r},${g},${b})`;\r\n */\r\nexport interface WallpaperColorValue {\r\n /** Color string `\"R G B\"` — each channel 0.0–1.0. */\r\n value: string;\r\n}\r\n\r\n/** Runtime value of a slider property. */\r\nexport interface WallpaperSliderValue {\r\n value: number;\r\n}\r\n\r\n/** Runtime value of a checkbox (`bool`) property. */\r\nexport interface WallpaperBoolValue {\r\n value: boolean;\r\n}\r\n\r\n/**\r\n * Runtime value of a combo (dropdown) property.\r\n * `value` is the hidden key configured in the editor;\r\n * `text` is the visible display label.\r\n */\r\nexport interface WallpaperComboValue {\r\n /** The hidden value configured on the combo option. */\r\n value: string;\r\n /** Display label of the selected option (may be a `ui_` localization token). */\r\n text: string;\r\n}\r\n\r\n/** Runtime value of a text-input property. */\r\nexport interface WallpaperTextValue {\r\n value: string;\r\n}\r\n\r\n/**\r\n * Runtime value of a file property.\r\n * Prepend `\"file:///\"` to `value` before using it as a URL.\r\n *\r\n * @example\r\n * img.src = 'file:///' + props.myImage.value;\r\n */\r\nexport interface WallpaperFileValue {\r\n /** File path — prepend `\"file:///\"` before use as a URL. */\r\n value: string;\r\n}\r\n\r\n/**\r\n * Runtime value of a directory property in `ondemand` mode.\r\n * An empty string means no directory is currently selected.\r\n * Call `wallpaperRequestRandomFileForProperty` to retrieve a random file.\r\n */\r\nexport interface WallpaperDirectoryValue {\r\n /** Directory path, or empty string when no directory is selected. */\r\n value: string;\r\n}\r\n\r\n/** Union of all possible runtime property values delivered by `applyUserProperties`. */\r\nexport type WallpaperPropertyRuntimeValue =\r\n | WallpaperColorValue\r\n | WallpaperSliderValue\r\n | WallpaperBoolValue\r\n | WallpaperComboValue\r\n | WallpaperTextValue\r\n | WallpaperFileValue\r\n | WallpaperDirectoryValue;\r\n\r\n/**\r\n * Map of property key → current runtime value passed to\r\n * `wallpaperPropertyListener.applyUserProperties`.\r\n * On first load all properties are present; on subsequent calls only the\r\n * changed ones are included — always guard with `if (properties.key)`.\r\n */\r\nexport type WallpaperUserProperties = Record<\r\n string,\r\n WallpaperPropertyRuntimeValue\r\n>;\r\n\r\n/** App-level settings delivered by `wallpaperPropertyListener.applyGeneralProperties`. */\r\nexport interface WallpaperGeneralProperties {\r\n /** Current FPS limit set by the user in Wallpaper Engine settings. `0` = unlimited. */\r\n fps?: number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// wallpaperPropertyListener\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Assign to `window.wallpaperPropertyListener` to receive property and\r\n * app-settings updates. **Must be a top-level global** — never assign inside\r\n * `window.onload` or other events, or startup events may be missed.\r\n *\r\n * @example\r\n * window.wallpaperPropertyListener = {\r\n * applyUserProperties(properties) {\r\n * if (properties.mycolor) { ... }\r\n * },\r\n * };\r\n */\r\nexport interface WallpaperPropertyListener {\r\n /**\r\n * Called once on wallpaper load (with all user properties) and again\r\n * whenever the user changes a property. Only changed properties are\r\n * included on subsequent calls — always guard with `if (properties.key)`.\r\n */\r\n applyUserProperties?: (properties: WallpaperUserProperties) => void;\r\n /**\r\n * Called on load and whenever the user changes app-level settings\r\n * such as the FPS limit. See {@link WallpaperGeneralProperties}.\r\n */\r\n applyGeneralProperties?: (properties: WallpaperGeneralProperties) => void;\r\n /**\r\n * Called for `directory` properties in `fetchall` mode when files are\r\n * added or modified in the selected directory. Prepend `\"file:///\"` to\r\n * each path before using it as a URL.\r\n */\r\n userDirectoryFilesAddedOrChanged?: (\r\n propertyName: string,\r\n changedFiles: string[],\r\n ) => void;\r\n /**\r\n * Called for `directory` properties in `fetchall` mode when files are\r\n * removed from the selected directory.\r\n */\r\n userDirectoryFilesRemoved?: (\r\n propertyName: string,\r\n removedFiles: string[],\r\n ) => void;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// wallpaperPluginListener\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Assign to `window.wallpaperPluginListener` to detect when RGB plugins load.\r\n * Check `name` to distinguish `\"led\"` (all RGB hardware via `wpPlugins.led`)\r\n * from `\"cue\"` (Corsair iCUE direct SDK, only needed for advanced features).\r\n *\r\n * @example\r\n * window.wallpaperPluginListener = {\r\n * onPluginLoaded(name, version) {\r\n * if (name === 'led') { // general RGB hardware ready\r\n * }\r\n * if (name === 'cue') { // Corsair iCUE SDK ready\r\n * }\r\n * },\r\n * };\r\n */\r\nexport interface WallpaperPluginListener {\r\n /**\r\n * Fired when a plugin finishes loading.\r\n * @param name - `\"led\"` for general RGB hardware or `\"cue\"` for Corsair iCUE.\r\n * @param version - Plugin version string.\r\n */\r\n onPluginLoaded?: (name: string, version: string) => void;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Media integration events\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaStatusListener` when the user enables\r\n * or disables media integration in the Wallpaper Engine app settings.\r\n */\r\nexport interface WallpaperMediaStatusEvent {\r\n /** `true` when media integration is active; `false` when disabled. */\r\n enabled: boolean;\r\n}\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaPropertiesListener` when the\r\n * currently playing track's metadata changes (title, artist, album, etc.).\r\n */\r\nexport interface WallpaperMediaPropertiesEvent {\r\n /** Title of the currently playing media. */\r\n title: string;\r\n /** Artist of the currently playing media. */\r\n artist: string;\r\n /** Optional sub-title (e.g. episode name for podcasts). */\r\n subTitle?: string;\r\n /** Optional album title. */\r\n albumTitle?: string;\r\n /** Optional album artist (may differ from `artist`). */\r\n albumArtist?: string;\r\n /** Optional comma-separated list of genres. */\r\n genres?: string;\r\n /** Media type: `\"music\"`, `\"video\"`, or `\"image\"`. */\r\n contentType: \"music\" | \"video\" | \"image\";\r\n}\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaThumbnailListener` when album art changes.\r\n * Assign `event.thumbnail` directly to `img.src`.\r\n *\r\n * @example\r\n * window.wallpaperRegisterMediaThumbnailListener((e) => {\r\n * document.getElementById('cover').src = e.thumbnail;\r\n * document.body.style.background = e.primaryColor;\r\n * titleEl.style.color = e.textColor;\r\n * });\r\n */\r\nexport interface WallpaperMediaThumbnailEvent {\r\n /** Base64-encoded PNG of the album art — usable directly as `img.src`. */\r\n thumbnail: string;\r\n /** Dominant color extracted from the thumbnail. */\r\n primaryColor: string;\r\n /** Secondary color from the thumbnail palette. */\r\n secondaryColor: string;\r\n /** Tertiary color from the thumbnail palette. */\r\n tertiaryColor: string;\r\n /**\r\n * Text color guaranteed to contrast sufficiently against `primaryColor`.\r\n * May be `secondaryColor` or `tertiaryColor` when the contrast is sufficient.\r\n */\r\n textColor: string;\r\n /** Either black or white — whichever contrasts more against `primaryColor`. */\r\n highContrastColor: string;\r\n}\r\n\r\n/** Media is actively playing on the system. */\r\nexport const PLAYBACK_PLAYING = 0 as const;\r\n/** Media was playing but has been temporarily paused by the user. */\r\nexport const PLAYBACK_PAUSED = 1 as const;\r\n/** Media playback is completely stopped. */\r\nexport const PLAYBACK_STOPPED = 2 as const;\r\n\r\n/**\r\n * Numeric playback state for {@link WallpaperMediaPlaybackEvent}.\r\n * Compare against `window.wallpaperMediaIntegration.PLAYBACK_PLAYING` etc.\r\n * or the module-level constants {@link PLAYBACK_PLAYING}, {@link PLAYBACK_PAUSED},\r\n * {@link PLAYBACK_STOPPED}.\r\n */\r\nexport type WallpaperMediaPlaybackState =\r\n | typeof PLAYBACK_PLAYING\r\n | typeof PLAYBACK_PAUSED\r\n | typeof PLAYBACK_STOPPED;\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaPlaybackListener` when playback\r\n * starts, pauses, or stops.\r\n */\r\nexport interface WallpaperMediaPlaybackEvent {\r\n /**\r\n * Current playback state. Compare against the `PLAYBACK_*` constants:\r\n * - `PLAYBACK_PLAYING` (`0`) — actively playing\r\n * - `PLAYBACK_PAUSED` (`1`) — temporarily paused\r\n * - `PLAYBACK_STOPPED` (`2`) — playback stopped\r\n */\r\n state: WallpaperMediaPlaybackState;\r\n}\r\n\r\n/**\r\n * Event fired by `wallpaperRegisterMediaTimelineListener` when track position\r\n * changes. **Not all media players support this** — ensure your wallpaper\r\n * works correctly even if this listener is never called.\r\n */\r\nexport interface WallpaperMediaTimelineEvent {\r\n /** Current playback position in seconds. */\r\n position: number;\r\n /** Total track duration in seconds. */\r\n duration: number;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Corsair iCUE SDK types (window.cue)\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Returned by `window.cue.getProtocolDetails` */\r\nexport interface CueProtocolDetails {\r\n sdkVersion: string;\r\n serverVersion: string;\r\n sdkProtocolVersion: number;\r\n serverProtocolVersion: number;\r\n breakingChanges: boolean;\r\n}\r\n\r\n/** Returned by `window.cue.getDeviceInfo` — mirrors `CorsairDeviceInfo` in the C++ SDK */\r\nexport interface CueDeviceInfo {\r\n /** `CorsairDeviceType` enum value */\r\n type: number;\r\n /** Human-readable device model name */\r\n model: string;\r\n /** `CorsairPhysicalLayout` enum value */\r\n physicalLayout: number;\r\n /** `CorsairLogicalLayout` enum value */\r\n logicalLayout: number;\r\n /** Number of available LEDs on this device */\r\n ledCount: number;\r\n /** `CorsairDeviceCaps` bitmask */\r\n capsMask: number;\r\n}\r\n\r\n/** An entry in the array returned by `window.cue.getLedPositionsByDeviceIndex` */\r\nexport interface CueLedPosition {\r\n /** `CorsairLedId` as integer */\r\n ledId: number;\r\n /** `CorsairLedId` as string */\r\n ledIdName: string;\r\n /** Position in mm from the top of the device */\r\n top: number;\r\n /** Position in mm from the left of the device */\r\n left: number;\r\n width: number;\r\n height: number;\r\n}\r\n\r\n/** LED color entry used in `setLedsColorsAsync` / `setAllLedsColorsAsync` */\r\nexport interface CueLedColor {\r\n /** `CorsairLedId` as integer */\r\n ledId: number;\r\n r: number;\r\n g: number;\r\n b: number;\r\n}\r\n\r\n/**\r\n * Direct Corsair iCUE SDK access exposed as `window.cue`.\r\n * Only available after the `\"cue\"` plugin has loaded via `wallpaperPluginListener`.\r\n */\r\nexport interface WallpaperCuePlugin {\r\n /** Returns current iCUE SDK status and version info */\r\n getProtocolDetails(\r\n callback: (protocolDetails: CueProtocolDetails) => void,\r\n ): void;\r\n /** Returns the number of recognised iCUE-compatible devices */\r\n getDeviceCount(callback: (deviceCount: number) => void): void;\r\n /** Returns hardware details for a specific device by index */\r\n getDeviceInfo(\r\n deviceIndex: number,\r\n callback: (deviceInfo: CueDeviceInfo) => void,\r\n ): void;\r\n /** Returns all LED positions for a specific device */\r\n getLedPositionsByDeviceIndex(\r\n callback: (arrayOfLEDs: CueLedPosition[]) => void,\r\n ): void;\r\n /** Update specific LEDs by supplying an array of `CueLedColor` objects */\r\n setLedsColorsAsync(arrayOfLEDColors: CueLedColor[]): void;\r\n /** Set all LEDs on one or more devices to a single color */\r\n setAllLedsColorsAsync(\r\n deviceIndexOrArray: number | number[],\r\n LEDColor: CueLedColor,\r\n ): void;\r\n /**\r\n * Update device LEDs from an RGB bitmap string (same format as\r\n * `wpPlugins.led.setAllDevicesByImageData`, but targets specific devices).\r\n */\r\n setLedColorsByImageData(\r\n deviceIndexOrArray: number | number[],\r\n encodedImageData: string,\r\n width: number,\r\n height: number,\r\n ): void;\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// RGB / LED plugin\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface WallpaperLedPlugin {\r\n /**\r\n * Push concatenated RGB bytes (as a string) to all connected LED devices.\r\n * Use a small canvas (e.g. 100×20) and convert it with `getImageData` for\r\n * best performance.\r\n */\r\n setAllDevicesByImageData(\r\n encodedImageData: string,\r\n width: number,\r\n height: number,\r\n ): void;\r\n}\r\n"],"mappings":";AAyOO,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;","names":[]}
1
+ {"version":3,"sources":["../src/types/listeners.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Runtime property values (received inside applyUserProperties)\n// ---------------------------------------------------------------------------\n\n/**\n * Runtime value of a color property.\n * `value` is `\"R G B\"` where each channel is in the **0–1** range.\n * Multiply by 255 to convert for CSS `rgb()`.\n *\n * @example\n * const [r, g, b] = props.myColor.value.split(' ').map(c => Math.ceil(+c * 255));\n * el.style.color = `rgb(${r},${g},${b})`;\n */\nexport interface WallpaperColorValue {\n /** Color string `\"R G B\"` — each channel 0.0–1.0. */\n value: string;\n}\n\n/** Runtime value of a slider property. */\nexport interface WallpaperSliderValue {\n value: number;\n}\n\n/** Runtime value of a checkbox (`bool`) property. */\nexport interface WallpaperBoolValue {\n value: boolean;\n}\n\n/**\n * Runtime value of a combo (dropdown) property.\n * `value` is the hidden key configured in the editor;\n * `text` is the visible display label.\n */\nexport interface WallpaperComboValue {\n /** The hidden value configured on the combo option. */\n value: string;\n /** Display label of the selected option (may be a `ui_` localization token). */\n text: string;\n}\n\n/** Runtime value of a text-input property. */\nexport interface WallpaperTextValue {\n value: string;\n}\n\n/**\n * Runtime value of a file property.\n * Prepend `\"file:///\"` to `value` before using it as a URL.\n *\n * @example\n * img.src = 'file:///' + props.myImage.value;\n */\nexport interface WallpaperFileValue {\n /** File path — prepend `\"file:///\"` before use as a URL. */\n value: string;\n}\n\n/**\n * Runtime value of a directory property in `ondemand` mode.\n * An empty string means no directory is currently selected.\n * Call `wallpaperRequestRandomFileForProperty` to retrieve a random file.\n */\nexport interface WallpaperDirectoryValue {\n /** Directory path, or empty string when no directory is selected. */\n value: string;\n}\n\n/** Union of all possible runtime property values delivered by `applyUserProperties`. */\nexport type WallpaperPropertyRuntimeValue =\n | WallpaperColorValue\n | WallpaperSliderValue\n | WallpaperBoolValue\n | WallpaperComboValue\n | WallpaperTextValue\n | WallpaperFileValue\n | WallpaperDirectoryValue;\n\n/**\n * Map of property key → current runtime value passed to\n * `wallpaperPropertyListener.applyUserProperties`.\n * On first load all properties are present; on subsequent calls only the\n * changed ones are included — always guard with `if (properties.key)`.\n */\nexport type WallpaperUserProperties = Record<\n string,\n WallpaperPropertyRuntimeValue\n>;\n\n/** App-level settings delivered by `wallpaperPropertyListener.applyGeneralProperties`. */\nexport interface WallpaperGeneralProperties {\n /** Current FPS limit set by the user in Wallpaper Engine settings. `0` = unlimited. */\n fps?: number;\n}\n\n// ---------------------------------------------------------------------------\n// wallpaperPropertyListener\n// ---------------------------------------------------------------------------\n\n/**\n * Assign to `window.wallpaperPropertyListener` to receive property and\n * app-settings updates. **Must be a top-level global** — never assign inside\n * `window.onload` or other events, or startup events may be missed.\n *\n * @example\n * window.wallpaperPropertyListener = {\n * applyUserProperties(properties) {\n * if (properties.mycolor) { ... }\n * },\n * };\n */\nexport interface WallpaperPropertyListener {\n /**\n * Called once on wallpaper load (with all user properties) and again\n * whenever the user changes a property. Only changed properties are\n * included on subsequent calls — always guard with `if (properties.key)`.\n */\n applyUserProperties?: (properties: WallpaperUserProperties) => void;\n /**\n * Called on load and whenever the user changes app-level settings\n * such as the FPS limit. See {@link WallpaperGeneralProperties}.\n */\n applyGeneralProperties?: (properties: WallpaperGeneralProperties) => void;\n /**\n * Called for `directory` properties in `fetchall` mode when files are\n * added or modified in the selected directory. Prepend `\"file:///\"` to\n * each path before using it as a URL.\n */\n userDirectoryFilesAddedOrChanged?: (\n propertyName: string,\n changedFiles: string[],\n ) => void;\n /**\n * Called for `directory` properties in `fetchall` mode when files are\n * removed from the selected directory.\n */\n userDirectoryFilesRemoved?: (\n propertyName: string,\n removedFiles: string[],\n ) => void;\n}\n\n// ---------------------------------------------------------------------------\n// wallpaperPluginListener\n// ---------------------------------------------------------------------------\n\n/**\n * Assign to `window.wallpaperPluginListener` to detect when RGB plugins load.\n * Check `name` to distinguish `\"led\"` (all RGB hardware via `wpPlugins.led`)\n * from `\"cue\"` (Corsair iCUE direct SDK, only needed for advanced features).\n *\n * @example\n * window.wallpaperPluginListener = {\n * onPluginLoaded(name, version) {\n * if (name === 'led') { // general RGB hardware ready\n * }\n * if (name === 'cue') { // Corsair iCUE SDK ready\n * }\n * },\n * };\n */\nexport interface WallpaperPluginListener {\n /**\n * Fired when a plugin finishes loading.\n * @param name - `\"led\"` for general RGB hardware or `\"cue\"` for Corsair iCUE.\n * @param version - Plugin version string.\n */\n onPluginLoaded?: (name: string, version: string) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Media integration events\n// ---------------------------------------------------------------------------\n\n/**\n * Event fired by `wallpaperRegisterMediaStatusListener` when the user enables\n * or disables media integration in the Wallpaper Engine app settings.\n */\nexport interface WallpaperMediaStatusEvent {\n /** `true` when media integration is active; `false` when disabled. */\n enabled: boolean;\n}\n\n/**\n * Event fired by `wallpaperRegisterMediaPropertiesListener` when the\n * currently playing track's metadata changes (title, artist, album, etc.).\n */\nexport interface WallpaperMediaPropertiesEvent {\n /** Title of the currently playing media. */\n title: string;\n /** Artist of the currently playing media. */\n artist: string;\n /** Optional sub-title (e.g. episode name for podcasts). */\n subTitle?: string;\n /** Optional album title. */\n albumTitle?: string;\n /** Optional album artist (may differ from `artist`). */\n albumArtist?: string;\n /** Optional comma-separated list of genres. */\n genres?: string;\n /** Media type: `\"music\"`, `\"video\"`, or `\"image\"`. */\n contentType: \"music\" | \"video\" | \"image\";\n}\n\n/**\n * Event fired by `wallpaperRegisterMediaThumbnailListener` when album art changes.\n * Assign `event.thumbnail` directly to `img.src`.\n *\n * @example\n * window.wallpaperRegisterMediaThumbnailListener((e) => {\n * document.getElementById('cover').src = e.thumbnail;\n * document.body.style.background = e.primaryColor;\n * titleEl.style.color = e.textColor;\n * });\n */\nexport interface WallpaperMediaThumbnailEvent {\n /** Base64-encoded PNG of the album art — usable directly as `img.src`. */\n thumbnail: string;\n /** Dominant color extracted from the thumbnail. */\n primaryColor: string;\n /** Secondary color from the thumbnail palette. */\n secondaryColor: string;\n /** Tertiary color from the thumbnail palette. */\n tertiaryColor: string;\n /**\n * Text color guaranteed to contrast sufficiently against `primaryColor`.\n * May be `secondaryColor` or `tertiaryColor` when the contrast is sufficient.\n */\n textColor: string;\n /** Either black or white — whichever contrasts more against `primaryColor`. */\n highContrastColor: string;\n}\n\n/** Media is actively playing on the system. */\nexport const PLAYBACK_PLAYING = 0 as const;\n/** Media was playing but has been temporarily paused by the user. */\nexport const PLAYBACK_PAUSED = 1 as const;\n/** Media playback is completely stopped. */\nexport const PLAYBACK_STOPPED = 2 as const;\n\n/**\n * Numeric playback state for {@link WallpaperMediaPlaybackEvent}.\n * Compare against `window.wallpaperMediaIntegration.PLAYBACK_PLAYING` etc.\n * or the module-level constants {@link PLAYBACK_PLAYING}, {@link PLAYBACK_PAUSED},\n * {@link PLAYBACK_STOPPED}.\n */\nexport type WallpaperMediaPlaybackState =\n | typeof PLAYBACK_PLAYING\n | typeof PLAYBACK_PAUSED\n | typeof PLAYBACK_STOPPED;\n\n/**\n * Event fired by `wallpaperRegisterMediaPlaybackListener` when playback\n * starts, pauses, or stops.\n */\nexport interface WallpaperMediaPlaybackEvent {\n /**\n * Current playback state. Compare against the `PLAYBACK_*` constants:\n * - `PLAYBACK_PLAYING` (`0`) — actively playing\n * - `PLAYBACK_PAUSED` (`1`) — temporarily paused\n * - `PLAYBACK_STOPPED` (`2`) — playback stopped\n */\n state: WallpaperMediaPlaybackState;\n}\n\n/**\n * Event fired by `wallpaperRegisterMediaTimelineListener` when track position\n * changes. **Not all media players support this** — ensure your wallpaper\n * works correctly even if this listener is never called.\n */\nexport interface WallpaperMediaTimelineEvent {\n /** Current playback position in seconds. */\n position: number;\n /** Total track duration in seconds. */\n duration: number;\n}\n\n// ---------------------------------------------------------------------------\n// Corsair iCUE SDK types (window.cue)\n// ---------------------------------------------------------------------------\n\n/** Returned by `window.cue.getProtocolDetails` */\nexport interface CueProtocolDetails {\n sdkVersion: string;\n serverVersion: string;\n sdkProtocolVersion: number;\n serverProtocolVersion: number;\n breakingChanges: boolean;\n}\n\n/** Returned by `window.cue.getDeviceInfo` — mirrors `CorsairDeviceInfo` in the C++ SDK */\nexport interface CueDeviceInfo {\n /** `CorsairDeviceType` enum value */\n type: number;\n /** Human-readable device model name */\n model: string;\n /** `CorsairPhysicalLayout` enum value */\n physicalLayout: number;\n /** `CorsairLogicalLayout` enum value */\n logicalLayout: number;\n /** Number of available LEDs on this device */\n ledCount: number;\n /** `CorsairDeviceCaps` bitmask */\n capsMask: number;\n}\n\n/** An entry in the array returned by `window.cue.getLedPositionsByDeviceIndex` */\nexport interface CueLedPosition {\n /** `CorsairLedId` as integer */\n ledId: number;\n /** `CorsairLedId` as string */\n ledIdName: string;\n /** Position in mm from the top of the device */\n top: number;\n /** Position in mm from the left of the device */\n left: number;\n width: number;\n height: number;\n}\n\n/** LED color entry used in `setLedsColorsAsync` / `setAllLedsColorsAsync` */\nexport interface CueLedColor {\n /** `CorsairLedId` as integer */\n ledId: number;\n r: number;\n g: number;\n b: number;\n}\n\n/**\n * Direct Corsair iCUE SDK access exposed as `window.cue`.\n * Only available after the `\"cue\"` plugin has loaded via `wallpaperPluginListener`.\n */\nexport interface WallpaperCuePlugin {\n /** Returns current iCUE SDK status and version info */\n getProtocolDetails(\n callback: (protocolDetails: CueProtocolDetails) => void,\n ): void;\n /** Returns the number of recognised iCUE-compatible devices */\n getDeviceCount(callback: (deviceCount: number) => void): void;\n /** Returns hardware details for a specific device by index */\n getDeviceInfo(\n deviceIndex: number,\n callback: (deviceInfo: CueDeviceInfo) => void,\n ): void;\n /** Returns all LED positions for a specific device */\n getLedPositionsByDeviceIndex(\n callback: (arrayOfLEDs: CueLedPosition[]) => void,\n ): void;\n /** Update specific LEDs by supplying an array of `CueLedColor` objects */\n setLedsColorsAsync(arrayOfLEDColors: CueLedColor[]): void;\n /** Set all LEDs on one or more devices to a single color */\n setAllLedsColorsAsync(\n deviceIndexOrArray: number | number[],\n LEDColor: CueLedColor,\n ): void;\n /**\n * Update device LEDs from an RGB bitmap string (same format as\n * `wpPlugins.led.setAllDevicesByImageData`, but targets specific devices).\n */\n setLedColorsByImageData(\n deviceIndexOrArray: number | number[],\n encodedImageData: string,\n width: number,\n height: number,\n ): void;\n}\n\n// ---------------------------------------------------------------------------\n// RGB / LED plugin\n// ---------------------------------------------------------------------------\n\nexport interface WallpaperLedPlugin {\n /**\n * Push concatenated RGB bytes (as a string) to all connected LED devices.\n * Use a small canvas (e.g. 100×20) and convert it with `getImageData` for\n * best performance.\n */\n setAllDevicesByImageData(\n encodedImageData: string,\n width: number,\n height: number,\n ): void;\n}\n"],"mappings":";AAyOO,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AAExB,IAAM,mBAAmB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugin/index.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\r\nimport type {\r\n WallpaperBoolValue,\r\n WallpaperColorValue,\r\n WallpaperComboValue,\r\n WallpaperDirectoryValue,\r\n WallpaperFileValue,\r\n WallpaperSliderValue,\r\n WallpaperTextValue,\r\n} from \"../types/listeners\";\r\nimport type {\r\n WallpaperBoolProperty,\r\n WallpaperColorProperty,\r\n WallpaperComboProperty,\r\n WallpaperDirectoryProperty,\r\n WallpaperFileProperty,\r\n WallpaperLocalization,\r\n WallpaperProject,\r\n WallpaperProjectGeneral,\r\n WallpaperPropertyDefinition,\r\n WallpaperSliderProperty,\r\n WallpaperTextInputProperty,\r\n} from \"../types/project\";\r\n\r\nexport type {\r\n WallpaperBoolProperty,\r\n WallpaperColorProperty,\r\n WallpaperComboProperty,\r\n WallpaperDirectoryProperty,\r\n WallpaperFileProperty,\r\n WallpaperLocalization,\r\n WallpaperProject,\r\n WallpaperProjectGeneral,\r\n WallpaperPropertyDefinition,\r\n WallpaperSliderProperty,\r\n WallpaperTextInputProperty,\r\n};\r\nexport type { WallpaperComboOption, WallpaperFileType } from \"../types/project\";\r\n\r\n// ---------------------------------------------------------------------------\r\n// Property builder helpers\r\n// ---------------------------------------------------------------------------\r\n\r\ntype Without<T, K extends keyof T> = Omit<T, K>;\r\n\r\n/** Define a color property (value: `\"R G B\"` in 0–1 range) */\r\nexport function colorProperty(\r\n opts: Without<WallpaperColorProperty, \"type\">,\r\n): WallpaperColorProperty {\r\n return { type: \"color\", ...opts };\r\n}\r\n\r\n/** Define a numeric slider property */\r\nexport function sliderProperty(\r\n opts: Without<WallpaperSliderProperty, \"type\">,\r\n): WallpaperSliderProperty {\r\n return { type: \"slider\", ...opts };\r\n}\r\n\r\n/** Define a boolean checkbox property */\r\nexport function boolProperty(\r\n opts: Without<WallpaperBoolProperty, \"type\">,\r\n): WallpaperBoolProperty {\r\n return { type: \"bool\", ...opts };\r\n}\r\n\r\n/** Define a dropdown (combo) property */\r\nexport function comboProperty(\r\n opts: Without<WallpaperComboProperty, \"type\">,\r\n): WallpaperComboProperty {\r\n return { type: \"combo\", ...opts };\r\n}\r\n\r\n/** Define a text input property */\r\nexport function textInputProperty(\r\n opts: Without<WallpaperTextInputProperty, \"type\">,\r\n): WallpaperTextInputProperty {\r\n return { type: \"textinput\", ...opts };\r\n}\r\n\r\n/** Define a file picker property */\r\nexport function fileProperty(\r\n opts: Without<WallpaperFileProperty, \"type\">,\r\n): WallpaperFileProperty {\r\n return { type: \"file\", ...opts };\r\n}\r\n\r\n/** Define a directory picker property */\r\nexport function directoryProperty(\r\n opts: Without<WallpaperDirectoryProperty, \"type\">,\r\n): WallpaperDirectoryProperty {\r\n return { type: \"directory\", ...opts };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Property definition → runtime value type mapping\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Maps a single property **definition** type to its **runtime value** type.\r\n *\r\n * Useful for building generic helpers that operate on any property kind.\r\n *\r\n * @example\r\n * type T = PropertyDefinitionToValue<WallpaperColorProperty>;\r\n * // → WallpaperColorValue\r\n */\r\nexport type PropertyDefinitionToValue<T extends WallpaperPropertyDefinition> =\r\n T extends { type: \"color\" }\r\n ? WallpaperColorValue\r\n : T extends { type: \"slider\" }\r\n ? WallpaperSliderValue\r\n : T extends { type: \"bool\" }\r\n ? WallpaperBoolValue\r\n : T extends { type: \"combo\" }\r\n ? WallpaperComboValue\r\n : T extends { type: \"textinput\" }\r\n ? WallpaperTextValue\r\n : T extends { type: \"file\" }\r\n ? WallpaperFileValue\r\n : T extends { type: \"directory\" }\r\n ? WallpaperDirectoryValue\r\n : never;\r\n\r\n/**\r\n * Infer the strongly-typed `applyUserProperties` argument from a property\r\n * definition record. Define your properties once in a shared file, then pass\r\n * `typeof yourProperties` here to get full autocomplete on every property key\r\n * and its value type.\r\n *\r\n * @example\r\n * // properties.ts — shared between vite.config.ts and wallpaper source\r\n * import { colorProperty, sliderProperty } from 'wallpaper-engine/plugin';\r\n *\r\n * export const myProperties = {\r\n * bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),\r\n * speed: sliderProperty({ text: 'Speed', value: 1, min: 0, max: 5 }),\r\n * };\r\n *\r\n * // vite.config.ts\r\n * import { wallpaperEnginePlugin } from 'wallpaper-engine/plugin';\r\n * import { myProperties } from './properties';\r\n *\r\n * export default defineConfig({\r\n * plugins: [wallpaperEnginePlugin({ title: 'My Wallpaper', properties: myProperties })],\r\n * });\r\n *\r\n * // wallpaper.ts\r\n * import type { WallpaperUserPropertiesOf } from 'wallpaper-engine/plugin';\r\n * import type { myProperties } from './properties';\r\n *\r\n * type MyProps = WallpaperUserPropertiesOf<typeof myProperties>;\r\n * // → { bgcolor: WallpaperColorValue; speed: WallpaperSliderValue }\r\n *\r\n * window.wallpaperPropertyListener = {\r\n * applyUserProperties(props: Partial<MyProps>) {\r\n * if (props.bgcolor) el.style.background = wallpaperColorToRgb(props.bgcolor.value);\r\n * if (props.speed !== undefined) setSpeed(props.speed.value); // number ✓\r\n * },\r\n * };\r\n */\r\nexport type WallpaperUserPropertiesOf<\r\n T extends Record<string, WallpaperPropertyDefinition>,\r\n> = {\r\n readonly [K in keyof T]: PropertyDefinitionToValue<T[K]>;\r\n};\r\n\r\n// ---------------------------------------------------------------------------\r\n// Vite plugin\r\n// ---------------------------------------------------------------------------\r\n\r\nexport interface WallpaperEnginePluginOptions {\r\n /**\r\n * Entry HTML file name relative to the project root.\r\n * @default \"index.html\"\r\n */\r\n file?: string;\r\n /** Wallpaper title shown in the Wallpaper Engine UI */\r\n title: string;\r\n /**\r\n * Enable audio data delivery to `wallpaperRegisterAudioListener`.\r\n * Wallpaper Engine can auto-detect this, but you can set it explicitly.\r\n * @default false\r\n */\r\n supportsAudioProcessing?: boolean;\r\n /**\r\n * User-configurable properties exposed in the Wallpaper Engine properties panel.\r\n * Keys become the property identifiers accessed in `applyUserProperties`.\r\n *\r\n * @example\r\n * properties: {\r\n * bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),\r\n * speed: sliderProperty({ text: 'Speed', value: 1, min: 0, max: 5 }),\r\n * }\r\n */\r\n properties?: Record<string, WallpaperPropertyDefinition>;\r\n /**\r\n * Localization strings for property labels and combo option labels.\r\n * Keys are BCP 47 language codes (e.g. `\"en-us\"`, `\"de-de\"`, `\"zh-chs\"`).\r\n * Property labels that should be translated must start with `ui_`.\r\n *\r\n * @example\r\n * localization: {\r\n * 'en-us': { 'ui_bgcolor': 'Background Color' },\r\n * 'de-de': { 'ui_bgcolor': 'Hintergrundfarbe' },\r\n * }\r\n */\r\n localization?: WallpaperLocalization;\r\n /**\r\n * Enable the in-browser dev overlay during `vite dev`. The overlay stubs\r\n * every `window.wallpaper*` global, renders a draggable panel to edit each\r\n * property in real time, and lets you fire audio/media/plugin events\r\n * manually — so wallpapers can be developed without round-tripping through\r\n * the Wallpaper Engine host application.\r\n *\r\n * Disabled automatically for production builds regardless of this setting.\r\n *\r\n * @default true\r\n */\r\n devtools?: boolean;\r\n}\r\n\r\n/**\r\n * Vite plugin that auto-generates `project.json` into the build output.\r\n *\r\n * @example\r\n * // vite.config.ts\r\n * import { wallpaperEnginePlugin, colorProperty } from 'wallpaper-engine/plugin';\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * wallpaperEnginePlugin({\r\n * title: 'My Wallpaper',\r\n * properties: {\r\n * bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),\r\n * },\r\n * }),\r\n * ],\r\n * });\r\n */\r\nexport function wallpaperEnginePlugin(\r\n options: WallpaperEnginePluginOptions,\r\n): Plugin {\r\n const devtoolsEnabled = options.devtools !== false;\r\n const VIRTUAL_ID = \"virtual:wallpaper-engine/devtools\";\r\n const RESOLVED_ID = \"\\0\" + VIRTUAL_ID;\r\n let isServe = false;\r\n let cachedClientCode: string | undefined;\r\n const loadClientCode = async (): Promise<string> => {\r\n if (cachedClientCode !== undefined) return cachedClientCode;\r\n // Reads the Vue UI bundle emitted by `vite build -c vite.devtools.config.ts`\r\n // into `dist/plugin/devtools/client.js`. Imports are dynamic so this file\r\n // stays browser-safe — property builders below are imported by user code.\r\n const { readFileSync } = await import(\"node:fs\");\r\n const { fileURLToPath } = await import(\"node:url\");\r\n const url = new URL(\"./devtools/client.js\", import.meta.url);\r\n cachedClientCode = readFileSync(fileURLToPath(url), \"utf8\");\r\n return cachedClientCode;\r\n };\r\n\r\n return {\r\n name: \"wallpaper-engine\",\r\n\r\n configResolved(config) {\r\n isServe = config.command === \"serve\";\r\n },\r\n\r\n configureServer(server) {\r\n if (!devtoolsEnabled) return;\r\n void Promise.all([import(\"node:fs\"), import(\"node:url\")]).then(\r\n ([fs, { fileURLToPath }]) => {\r\n const clientPath = fileURLToPath(\r\n new URL(\"./devtools/client.js\", import.meta.url),\r\n );\r\n // Use stat-polling watchFile instead of chokidar: avoids Windows\r\n // path-normalisation mismatches (forward vs back slashes) and works\r\n // correctly across Bun workspace symlinks.\r\n fs.watchFile(clientPath, { interval: 500 }, () => {\r\n cachedClientCode = undefined;\r\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID);\r\n if (mod) server.moduleGraph.invalidateModule(mod);\r\n server.ws.send({ type: \"full-reload\" });\r\n });\r\n server.httpServer?.once(\"close\", () => {\r\n fs.unwatchFile(clientPath);\r\n });\r\n },\r\n );\r\n },\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_ID) return RESOLVED_ID;\r\n return null;\r\n },\r\n\r\n async load(id) {\r\n if (id !== RESOLVED_ID) return null;\r\n const properties = options.properties\r\n ? assignIndices(options.properties)\r\n : {};\r\n const cfg = {\r\n title: options.title,\r\n properties,\r\n localization: options.localization ?? {},\r\n };\r\n return (\r\n \"window.__WE_DEVTOOLS_CONFIG__ = \" +\r\n JSON.stringify(cfg) +\r\n \";\\n\" +\r\n (await loadClientCode())\r\n );\r\n },\r\n\r\n transformIndexHtml() {\r\n if (!isServe || !devtoolsEnabled) return;\r\n return [\r\n {\r\n tag: \"script\",\r\n attrs: { type: \"module\", src: \"/@id/\" + VIRTUAL_ID },\r\n injectTo: \"head-prepend\",\r\n },\r\n ];\r\n },\r\n\r\n generateBundle() {\r\n const properties = options.properties\r\n ? assignIndices(options.properties)\r\n : undefined;\r\n\r\n const general: WallpaperProjectGeneral = {};\r\n if (properties) general.properties = properties;\r\n if (options.localization) general.localization = options.localization;\r\n\r\n const project: WallpaperProject = {\r\n file: options.file ?? \"index.html\",\r\n title: options.title,\r\n type: \"web\",\r\n };\r\n\r\n if (options.supportsAudioProcessing) {\r\n project.supportsaudioprocessing = true;\r\n }\r\n\r\n if (Object.keys(general).length > 0) {\r\n project.general = general;\r\n }\r\n\r\n this.emitFile({\r\n type: \"asset\",\r\n fileName: \"project.json\",\r\n source: JSON.stringify(project, null, \"\\t\"),\r\n });\r\n },\r\n };\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Internal helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/**\r\n * Auto-assigns `index` and `order` to any properties that don't already have\r\n * them, based on their insertion order in the record.\r\n */\r\nfunction assignIndices(\r\n properties: Record<string, WallpaperPropertyDefinition>,\r\n): Record<string, WallpaperPropertyDefinition> {\r\n const result: Record<string, WallpaperPropertyDefinition> = {};\r\n let i = 0;\r\n\r\n for (const [key, prop] of Object.entries(properties)) {\r\n result[key] = {\r\n index: i,\r\n order: i,\r\n ...prop,\r\n };\r\n i++;\r\n }\r\n\r\n return result;\r\n}\r\n"],"mappings":";AA8CO,SAAS,cACd,MACwB;AACxB,SAAO,EAAE,MAAM,SAAS,GAAG,KAAK;AAClC;AAGO,SAAS,eACd,MACyB;AACzB,SAAO,EAAE,MAAM,UAAU,GAAG,KAAK;AACnC;AAGO,SAAS,aACd,MACuB;AACvB,SAAO,EAAE,MAAM,QAAQ,GAAG,KAAK;AACjC;AAGO,SAAS,cACd,MACwB;AACxB,SAAO,EAAE,MAAM,SAAS,GAAG,KAAK;AAClC;AAGO,SAAS,kBACd,MAC4B;AAC5B,SAAO,EAAE,MAAM,aAAa,GAAG,KAAK;AACtC;AAGO,SAAS,aACd,MACuB;AACvB,SAAO,EAAE,MAAM,QAAQ,GAAG,KAAK;AACjC;AAGO,SAAS,kBACd,MAC4B;AAC5B,SAAO,EAAE,MAAM,aAAa,GAAG,KAAK;AACtC;AAoJO,SAAS,sBACd,SACQ;AACR,QAAM,kBAAkB,QAAQ,aAAa;AAC7C,QAAM,aAAa;AACnB,QAAM,cAAc,OAAO;AAC3B,MAAI,UAAU;AACd,MAAI;AACJ,QAAM,iBAAiB,YAA6B;AAClD,QAAI,qBAAqB,OAAW,QAAO;AAI3C,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,IAAS;AAC/C,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,KAAU;AACjD,UAAM,MAAM,IAAI,IAAI,wBAAwB,YAAY,GAAG;AAC3D,uBAAmB,aAAa,cAAc,GAAG,GAAG,MAAM;AAC1D,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,eAAe,QAAQ;AACrB,gBAAU,OAAO,YAAY;AAAA,IAC/B;AAAA,IAEA,gBAAgB,QAAQ;AACtB,UAAI,CAAC,gBAAiB;AACtB,WAAK,QAAQ,IAAI,CAAC,OAAO,IAAS,GAAG,OAAO,KAAU,CAAC,CAAC,EAAE;AAAA,QACxD,CAAC,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM;AAC3B,gBAAM,aAAa;AAAA,YACjB,IAAI,IAAI,wBAAwB,YAAY,GAAG;AAAA,UACjD;AAIA,aAAG,UAAU,YAAY,EAAE,UAAU,IAAI,GAAG,MAAM;AAChD,+BAAmB;AACnB,kBAAM,MAAM,OAAO,YAAY,cAAc,WAAW;AACxD,gBAAI,IAAK,QAAO,YAAY,iBAAiB,GAAG;AAChD,mBAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;AAAA,UACxC,CAAC;AACD,iBAAO,YAAY,KAAK,SAAS,MAAM;AACrC,eAAG,YAAY,UAAU;AAAA,UAC3B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IAEA,UAAU,IAAI;AACZ,UAAI,OAAO,WAAY,QAAO;AAC9B,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,KAAK,IAAI;AACb,UAAI,OAAO,YAAa,QAAO;AAC/B,YAAM,aAAa,QAAQ,aACvB,cAAc,QAAQ,UAAU,IAChC,CAAC;AACL,YAAM,MAAM;AAAA,QACV,OAAO,QAAQ;AAAA,QACf;AAAA,QACA,cAAc,QAAQ,gBAAgB,CAAC;AAAA,MACzC;AACA,aACE,qCACA,KAAK,UAAU,GAAG,IAClB,QACC,MAAM,eAAe;AAAA,IAE1B;AAAA,IAEA,qBAAqB;AACnB,UAAI,CAAC,WAAW,CAAC,gBAAiB;AAClC,aAAO;AAAA,QACL;AAAA,UACE,KAAK;AAAA,UACL,OAAO,EAAE,MAAM,UAAU,KAAK,UAAU,WAAW;AAAA,UACnD,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IAEA,iBAAiB;AACf,YAAM,aAAa,QAAQ,aACvB,cAAc,QAAQ,UAAU,IAChC;AAEJ,YAAM,UAAmC,CAAC;AAC1C,UAAI,WAAY,SAAQ,aAAa;AACrC,UAAI,QAAQ,aAAc,SAAQ,eAAe,QAAQ;AAEzD,YAAM,UAA4B;AAAA,QAChC,MAAM,QAAQ,QAAQ;AAAA,QACtB,OAAO,QAAQ;AAAA,QACf,MAAM;AAAA,MACR;AAEA,UAAI,QAAQ,yBAAyB;AACnC,gBAAQ,0BAA0B;AAAA,MACpC;AAEA,UAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,gBAAQ,UAAU;AAAA,MACpB;AAEA,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,KAAK,UAAU,SAAS,MAAM,GAAI;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAUA,SAAS,cACP,YAC6C;AAC7C,QAAM,SAAsD,CAAC;AAC7D,MAAI,IAAI;AAER,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACpD,WAAO,GAAG,IAAI;AAAA,MACZ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,GAAG;AAAA,IACL;AACA;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/plugin/index.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport type {\n WallpaperBoolValue,\n WallpaperColorValue,\n WallpaperComboValue,\n WallpaperDirectoryValue,\n WallpaperFileValue,\n WallpaperSliderValue,\n WallpaperTextValue,\n} from \"../types/listeners\";\nimport type {\n WallpaperBoolProperty,\n WallpaperColorProperty,\n WallpaperComboProperty,\n WallpaperDirectoryProperty,\n WallpaperFileProperty,\n WallpaperLocalization,\n WallpaperProject,\n WallpaperProjectGeneral,\n WallpaperPropertyDefinition,\n WallpaperSliderProperty,\n WallpaperTextInputProperty,\n} from \"../types/project\";\n\nexport type {\n WallpaperBoolProperty,\n WallpaperColorProperty,\n WallpaperComboProperty,\n WallpaperDirectoryProperty,\n WallpaperFileProperty,\n WallpaperLocalization,\n WallpaperProject,\n WallpaperProjectGeneral,\n WallpaperPropertyDefinition,\n WallpaperSliderProperty,\n WallpaperTextInputProperty,\n};\nexport type { WallpaperComboOption, WallpaperFileType } from \"../types/project\";\n\n// ---------------------------------------------------------------------------\n// Property builder helpers\n// ---------------------------------------------------------------------------\n\ntype Without<T, K extends keyof T> = Omit<T, K>;\n\n/** Define a color property (value: `\"R G B\"` in 0–1 range) */\nexport function colorProperty(\n opts: Without<WallpaperColorProperty, \"type\">,\n): WallpaperColorProperty {\n return { type: \"color\", ...opts };\n}\n\n/** Define a numeric slider property */\nexport function sliderProperty(\n opts: Without<WallpaperSliderProperty, \"type\">,\n): WallpaperSliderProperty {\n return { type: \"slider\", ...opts };\n}\n\n/** Define a boolean checkbox property */\nexport function boolProperty(\n opts: Without<WallpaperBoolProperty, \"type\">,\n): WallpaperBoolProperty {\n return { type: \"bool\", ...opts };\n}\n\n/** Define a dropdown (combo) property */\nexport function comboProperty(\n opts: Without<WallpaperComboProperty, \"type\">,\n): WallpaperComboProperty {\n return { type: \"combo\", ...opts };\n}\n\n/** Define a text input property */\nexport function textInputProperty(\n opts: Without<WallpaperTextInputProperty, \"type\">,\n): WallpaperTextInputProperty {\n return { type: \"textinput\", ...opts };\n}\n\n/** Define a file picker property */\nexport function fileProperty(\n opts: Without<WallpaperFileProperty, \"type\">,\n): WallpaperFileProperty {\n return { type: \"file\", ...opts };\n}\n\n/** Define a directory picker property */\nexport function directoryProperty(\n opts: Without<WallpaperDirectoryProperty, \"type\">,\n): WallpaperDirectoryProperty {\n return { type: \"directory\", ...opts };\n}\n\n// ---------------------------------------------------------------------------\n// Property definition → runtime value type mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Maps a single property **definition** type to its **runtime value** type.\n *\n * Useful for building generic helpers that operate on any property kind.\n *\n * @example\n * type T = PropertyDefinitionToValue<WallpaperColorProperty>;\n * // → WallpaperColorValue\n */\nexport type PropertyDefinitionToValue<T extends WallpaperPropertyDefinition> =\n T extends { type: \"color\" }\n ? WallpaperColorValue\n : T extends { type: \"slider\" }\n ? WallpaperSliderValue\n : T extends { type: \"bool\" }\n ? WallpaperBoolValue\n : T extends { type: \"combo\" }\n ? WallpaperComboValue\n : T extends { type: \"textinput\" }\n ? WallpaperTextValue\n : T extends { type: \"file\" }\n ? WallpaperFileValue\n : T extends { type: \"directory\" }\n ? WallpaperDirectoryValue\n : never;\n\n/**\n * Infer the strongly-typed `applyUserProperties` argument from a property\n * definition record. Define your properties once in a shared file, then pass\n * `typeof yourProperties` here to get full autocomplete on every property key\n * and its value type.\n *\n * @example\n * // properties.ts — shared between vite.config.ts and wallpaper source\n * import { colorProperty, sliderProperty } from 'wallpaper-engine/plugin';\n *\n * export const myProperties = {\n * bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),\n * speed: sliderProperty({ text: 'Speed', value: 1, min: 0, max: 5 }),\n * };\n *\n * // vite.config.ts\n * import { wallpaperEnginePlugin } from 'wallpaper-engine/plugin';\n * import { myProperties } from './properties';\n *\n * export default defineConfig({\n * plugins: [wallpaperEnginePlugin({ title: 'My Wallpaper', properties: myProperties })],\n * });\n *\n * // wallpaper.ts\n * import type { WallpaperUserPropertiesOf } from 'wallpaper-engine/plugin';\n * import type { myProperties } from './properties';\n *\n * type MyProps = WallpaperUserPropertiesOf<typeof myProperties>;\n * // → { bgcolor: WallpaperColorValue; speed: WallpaperSliderValue }\n *\n * window.wallpaperPropertyListener = {\n * applyUserProperties(props: Partial<MyProps>) {\n * if (props.bgcolor) el.style.background = wallpaperColorToRgb(props.bgcolor.value);\n * if (props.speed !== undefined) setSpeed(props.speed.value); // number ✓\n * },\n * };\n */\nexport type WallpaperUserPropertiesOf<\n T extends Record<string, WallpaperPropertyDefinition>,\n> = {\n readonly [K in keyof T]: PropertyDefinitionToValue<T[K]>;\n};\n\n// ---------------------------------------------------------------------------\n// Vite plugin\n// ---------------------------------------------------------------------------\n\nexport interface WallpaperEnginePluginOptions {\n /**\n * Entry HTML file name relative to the project root.\n * @default \"index.html\"\n */\n file?: string;\n /** Wallpaper title shown in the Wallpaper Engine UI */\n title: string;\n /**\n * Enable audio data delivery to `wallpaperRegisterAudioListener`.\n * Wallpaper Engine can auto-detect this, but you can set it explicitly.\n * @default false\n */\n supportsAudioProcessing?: boolean;\n /**\n * User-configurable properties exposed in the Wallpaper Engine properties panel.\n * Keys become the property identifiers accessed in `applyUserProperties`.\n *\n * @example\n * properties: {\n * bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),\n * speed: sliderProperty({ text: 'Speed', value: 1, min: 0, max: 5 }),\n * }\n */\n properties?: Record<string, WallpaperPropertyDefinition>;\n /**\n * Localization strings for property labels and combo option labels.\n * Keys are BCP 47 language codes (e.g. `\"en-us\"`, `\"de-de\"`, `\"zh-chs\"`).\n * Property labels that should be translated must start with `ui_`.\n *\n * @example\n * localization: {\n * 'en-us': { 'ui_bgcolor': 'Background Color' },\n * 'de-de': { 'ui_bgcolor': 'Hintergrundfarbe' },\n * }\n */\n localization?: WallpaperLocalization;\n /**\n * Enable the in-browser dev overlay during `vite dev`. The overlay stubs\n * every `window.wallpaper*` global, renders a draggable panel to edit each\n * property in real time, and lets you fire audio/media/plugin events\n * manually — so wallpapers can be developed without round-tripping through\n * the Wallpaper Engine host application.\n *\n * Disabled automatically for production builds regardless of this setting.\n *\n * @default true\n */\n devtools?: boolean;\n}\n\n/**\n * Vite plugin that auto-generates `project.json` into the build output.\n *\n * @example\n * // vite.config.ts\n * import { wallpaperEnginePlugin, colorProperty } from 'wallpaper-engine/plugin';\n *\n * export default defineConfig({\n * plugins: [\n * wallpaperEnginePlugin({\n * title: 'My Wallpaper',\n * properties: {\n * bgcolor: colorProperty({ text: 'Background Color', value: '0 0 0' }),\n * },\n * }),\n * ],\n * });\n */\nexport function wallpaperEnginePlugin(\n options: WallpaperEnginePluginOptions,\n): Plugin {\n const devtoolsEnabled = options.devtools !== false;\n const VIRTUAL_ID = \"virtual:wallpaper-engine/devtools\";\n const RESOLVED_ID = \"\\0\" + VIRTUAL_ID;\n let isServe = false;\n let cachedClientCode: string | undefined;\n const loadClientCode = async (): Promise<string> => {\n if (cachedClientCode !== undefined) return cachedClientCode;\n // Reads the Vue UI bundle emitted by `vite build -c vite.devtools.config.ts`\n // into `dist/plugin/devtools/client.js`. Imports are dynamic so this file\n // stays browser-safe — property builders below are imported by user code.\n const { readFileSync } = await import(\"node:fs\");\n const { fileURLToPath } = await import(\"node:url\");\n const url = new URL(\"./devtools/client.js\", import.meta.url);\n cachedClientCode = readFileSync(fileURLToPath(url), \"utf8\");\n return cachedClientCode;\n };\n\n return {\n name: \"wallpaper-engine\",\n\n configResolved(config) {\n isServe = config.command === \"serve\";\n },\n\n configureServer(server) {\n if (!devtoolsEnabled) return;\n void Promise.all([import(\"node:fs\"), import(\"node:url\")]).then(\n ([fs, { fileURLToPath }]) => {\n const clientPath = fileURLToPath(\n new URL(\"./devtools/client.js\", import.meta.url),\n );\n // Use stat-polling watchFile instead of chokidar: avoids Windows\n // path-normalisation mismatches (forward vs back slashes) and works\n // correctly across Bun workspace symlinks.\n fs.watchFile(clientPath, { interval: 500 }, () => {\n cachedClientCode = undefined;\n const mod = server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: \"full-reload\" });\n });\n server.httpServer?.once(\"close\", () => {\n fs.unwatchFile(clientPath);\n });\n },\n );\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ID) return RESOLVED_ID;\n return null;\n },\n\n async load(id) {\n if (id !== RESOLVED_ID) return null;\n const properties = options.properties\n ? assignIndices(options.properties)\n : {};\n const cfg = {\n title: options.title,\n properties,\n localization: options.localization ?? {},\n };\n return (\n \"window.__WE_DEVTOOLS_CONFIG__ = \" +\n JSON.stringify(cfg) +\n \";\\n\" +\n (await loadClientCode())\n );\n },\n\n transformIndexHtml() {\n if (!isServe || !devtoolsEnabled) return;\n return [\n {\n tag: \"script\",\n attrs: { type: \"module\", src: \"/@id/\" + VIRTUAL_ID },\n injectTo: \"head-prepend\",\n },\n ];\n },\n\n generateBundle() {\n const properties = options.properties\n ? assignIndices(options.properties)\n : undefined;\n\n const general: WallpaperProjectGeneral = {};\n if (properties) general.properties = properties;\n if (options.localization) general.localization = options.localization;\n\n const project: WallpaperProject = {\n file: options.file ?? \"index.html\",\n title: options.title,\n type: \"web\",\n };\n\n if (options.supportsAudioProcessing) {\n project.supportsaudioprocessing = true;\n }\n\n if (Object.keys(general).length > 0) {\n project.general = general;\n }\n\n this.emitFile({\n type: \"asset\",\n fileName: \"project.json\",\n source: JSON.stringify(project, null, \"\\t\"),\n });\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Auto-assigns `index` and `order` to any properties that don't already have\n * them, based on their insertion order in the record.\n */\nfunction assignIndices(\n properties: Record<string, WallpaperPropertyDefinition>,\n): Record<string, WallpaperPropertyDefinition> {\n const result: Record<string, WallpaperPropertyDefinition> = {};\n let i = 0;\n\n for (const [key, prop] of Object.entries(properties)) {\n result[key] = {\n index: i,\n order: i,\n ...prop,\n };\n i++;\n }\n\n return result;\n}\n"],"mappings":";AA8CO,SAAS,cACd,MACwB;AACxB,SAAO,EAAE,MAAM,SAAS,GAAG,KAAK;AAClC;AAGO,SAAS,eACd,MACyB;AACzB,SAAO,EAAE,MAAM,UAAU,GAAG,KAAK;AACnC;AAGO,SAAS,aACd,MACuB;AACvB,SAAO,EAAE,MAAM,QAAQ,GAAG,KAAK;AACjC;AAGO,SAAS,cACd,MACwB;AACxB,SAAO,EAAE,MAAM,SAAS,GAAG,KAAK;AAClC;AAGO,SAAS,kBACd,MAC4B;AAC5B,SAAO,EAAE,MAAM,aAAa,GAAG,KAAK;AACtC;AAGO,SAAS,aACd,MACuB;AACvB,SAAO,EAAE,MAAM,QAAQ,GAAG,KAAK;AACjC;AAGO,SAAS,kBACd,MAC4B;AAC5B,SAAO,EAAE,MAAM,aAAa,GAAG,KAAK;AACtC;AAoJO,SAAS,sBACd,SACQ;AACR,QAAM,kBAAkB,QAAQ,aAAa;AAC7C,QAAM,aAAa;AACnB,QAAM,cAAc,OAAO;AAC3B,MAAI,UAAU;AACd,MAAI;AACJ,QAAM,iBAAiB,YAA6B;AAClD,QAAI,qBAAqB,OAAW,QAAO;AAI3C,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,IAAS;AAC/C,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,KAAU;AACjD,UAAM,MAAM,IAAI,IAAI,wBAAwB,YAAY,GAAG;AAC3D,uBAAmB,aAAa,cAAc,GAAG,GAAG,MAAM;AAC1D,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IAEN,eAAe,QAAQ;AACrB,gBAAU,OAAO,YAAY;AAAA,IAC/B;AAAA,IAEA,gBAAgB,QAAQ;AACtB,UAAI,CAAC,gBAAiB;AACtB,WAAK,QAAQ,IAAI,CAAC,OAAO,IAAS,GAAG,OAAO,KAAU,CAAC,CAAC,EAAE;AAAA,QACxD,CAAC,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM;AAC3B,gBAAM,aAAa;AAAA,YACjB,IAAI,IAAI,wBAAwB,YAAY,GAAG;AAAA,UACjD;AAIA,aAAG,UAAU,YAAY,EAAE,UAAU,IAAI,GAAG,MAAM;AAChD,+BAAmB;AACnB,kBAAM,MAAM,OAAO,YAAY,cAAc,WAAW;AACxD,gBAAI,IAAK,QAAO,YAAY,iBAAiB,GAAG;AAChD,mBAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;AAAA,UACxC,CAAC;AACD,iBAAO,YAAY,KAAK,SAAS,MAAM;AACrC,eAAG,YAAY,UAAU;AAAA,UAC3B,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IAEA,UAAU,IAAI;AACZ,UAAI,OAAO,WAAY,QAAO;AAC9B,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,KAAK,IAAI;AACb,UAAI,OAAO,YAAa,QAAO;AAC/B,YAAM,aAAa,QAAQ,aACvB,cAAc,QAAQ,UAAU,IAChC,CAAC;AACL,YAAM,MAAM;AAAA,QACV,OAAO,QAAQ;AAAA,QACf;AAAA,QACA,cAAc,QAAQ,gBAAgB,CAAC;AAAA,MACzC;AACA,aACE,qCACA,KAAK,UAAU,GAAG,IAClB,QACC,MAAM,eAAe;AAAA,IAE1B;AAAA,IAEA,qBAAqB;AACnB,UAAI,CAAC,WAAW,CAAC,gBAAiB;AAClC,aAAO;AAAA,QACL;AAAA,UACE,KAAK;AAAA,UACL,OAAO,EAAE,MAAM,UAAU,KAAK,UAAU,WAAW;AAAA,UACnD,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IAEA,iBAAiB;AACf,YAAM,aAAa,QAAQ,aACvB,cAAc,QAAQ,UAAU,IAChC;AAEJ,YAAM,UAAmC,CAAC;AAC1C,UAAI,WAAY,SAAQ,aAAa;AACrC,UAAI,QAAQ,aAAc,SAAQ,eAAe,QAAQ;AAEzD,YAAM,UAA4B;AAAA,QAChC,MAAM,QAAQ,QAAQ;AAAA,QACtB,OAAO,QAAQ;AAAA,QACf,MAAM;AAAA,MACR;AAEA,UAAI,QAAQ,yBAAyB;AACnC,gBAAQ,0BAA0B;AAAA,MACpC;AAEA,UAAI,OAAO,KAAK,OAAO,EAAE,SAAS,GAAG;AACnC,gBAAQ,UAAU;AAAA,MACpB;AAEA,WAAK,SAAS;AAAA,QACZ,MAAM;AAAA,QACN,UAAU;AAAA,QACV,QAAQ,KAAK,UAAU,SAAS,MAAM,GAAI;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAUA,SAAS,cACP,YAC6C;AAC7C,QAAM,SAAsD,CAAC;AAC7D,MAAI,IAAI;AAER,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACpD,WAAO,GAAG,IAAI;AAAA,MACZ,OAAO;AAAA,MACP,OAAO;AAAA,MACP,GAAG;AAAA,IACL;AACA;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
package/package.json CHANGED
@@ -1,70 +1,70 @@
1
- {
2
- "name": "wallpaper-engine",
3
- "version": "1.0.0",
4
- "description": "TypeScript types, Vite plugin, and runtime helpers for Wallpaper Engine web wallpapers",
5
- "keywords": [
6
- "wallpaper-engine",
7
- "wallpaper engine",
8
- "web wallpaper",
9
- "vite plugin",
10
- "typescript"
11
- ],
12
- "license": "MIT",
13
- "author": "ShadowNineX",
14
- "homepage": "https://github.com/ShadowNineX/wallpaper-engine#readme",
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/ShadowNineX/wallpaper-engine.git"
18
- },
19
- "bugs": {
20
- "url": "https://github.com/ShadowNineX/wallpaper-engine/issues"
21
- },
22
- "type": "module",
23
- "main": "./dist/index.cjs",
24
- "module": "./dist/index.js",
25
- "types": "./dist/index.d.ts",
26
- "exports": {
27
- ".": {
28
- "types": "./dist/index.d.ts",
29
- "import": "./dist/index.js",
30
- "require": "./dist/index.cjs"
31
- },
32
- "./plugin": {
33
- "types": "./dist/plugin/index.d.ts",
34
- "import": "./dist/plugin/index.js"
35
- },
36
- "./helpers": {
37
- "types": "./dist/helpers.d.ts",
38
- "import": "./dist/helpers.js",
39
- "require": "./dist/helpers.cjs"
40
- }
41
- },
42
- "files": [
43
- "dist",
44
- "README.md",
45
- "LICENSE"
46
- ],
47
- "scripts": {
48
- "build": "tsup",
49
- "build:lib": "tsup",
50
- "dev": "tsup --watch",
51
- "typecheck": "tsc --noEmit",
52
- "test": "vitest",
53
- "test:run": "vitest run",
54
- "prepublishOnly": "bun run --cwd ../.. build"
55
- },
56
- "peerDependencies": {
57
- "vite": ">=5.0.0"
58
- },
59
- "peerDependenciesMeta": {
60
- "vite": {
61
- "optional": true
62
- }
63
- },
64
- "devDependencies": {
65
- "@vitest/coverage-v8": "^4.1.6",
66
- "tsup": "^8.5.1",
67
- "vite": "^8.0.13",
68
- "vitest": "^4.1.6"
69
- }
70
- }
1
+ {
2
+ "name": "wallpaper-engine",
3
+ "version": "1.0.2",
4
+ "description": "TypeScript types, Vite plugin, and runtime helpers for Wallpaper Engine web wallpapers",
5
+ "keywords": [
6
+ "wallpaper-engine",
7
+ "wallpaper engine",
8
+ "web wallpaper",
9
+ "vite plugin",
10
+ "typescript"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "ShadowNineX",
14
+ "homepage": "https://github.com/ShadowNineX/wallpaper-engine#readme",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ShadowNineX/wallpaper-engine.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/ShadowNineX/wallpaper-engine/issues"
21
+ },
22
+ "type": "module",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs"
31
+ },
32
+ "./plugin": {
33
+ "types": "./dist/plugin/index.d.ts",
34
+ "import": "./dist/plugin/index.js"
35
+ },
36
+ "./helpers": {
37
+ "types": "./dist/helpers.d.ts",
38
+ "import": "./dist/helpers.js",
39
+ "require": "./dist/helpers.cjs"
40
+ }
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "build:lib": "tsup",
50
+ "dev": "tsup --watch",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest",
53
+ "test:run": "vitest run",
54
+ "prepublishOnly": "bun run --cwd ../.. build && node -e \"require('fs').copyFileSync('../../README.md', 'README.md')\""
55
+ },
56
+ "peerDependencies": {
57
+ "vite": ">=5.0.0"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "vite": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "devDependencies": {
65
+ "@vitest/coverage-v8": "^4.1.6",
66
+ "tsup": "^8.5.1",
67
+ "vite": "^8.0.13",
68
+ "vitest": "^4.1.6"
69
+ }
70
+ }