wallpaper-engine-dsh 0.1.6 → 0.1.7
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 +3 -2
- package/README.zh.md +2 -1
- package/lib/client.js +91 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,8 +9,9 @@ A DSH bundle that turns your local **Wallpaper Engine** library into the live ba
|
|
|
9
9
|
- **Whole-library browsing** — video and web wallpapers render live; scene and application wallpapers show their preview as a static background. Everything with a preview or playable file is selectable, badged by type (Video / Web / Still).
|
|
10
10
|
- **Search** — filter the grid instantly by title or workshop id.
|
|
11
11
|
- **Rotation lists** — any number of carousel lists, each with its own wallpapers, interval (1–120 min) and order (sequence/random). Your first playable Wallpaper Engine playlist is imported automatically; others can be imported into any list.
|
|
12
|
-
- **Four live sliders** —
|
|
13
|
-
- **
|
|
12
|
+
- **Four live sliders** — Wallpaper blur, Scrim, Borders, Glass — all instant and persisted.
|
|
13
|
+
- **Legibility on any wallpaper** — the veil opposes the text colour (white veil in light theme, black in dark) and a Smart veil toggle samples the wallpaper's brightness every few seconds, automatically strengthening the veil when a scene gets too bright or too dark for the current theme. Dropdowns, menus, and dialogs keep DSH's opaque surfaces untouched.
|
|
14
|
+
- **Canvas fit** — Cover/Contain/Stretch/Original sizing plus five-way alignment for video and still wallpapers.
|
|
14
15
|
- **Resource monitor** — FPS readout with a low-framerate hint, plus auto-pause when the tab is hidden or the battery runs low (both optional).
|
|
15
16
|
- **Bilingual** — the settings UI follows the shell's Language preference (Chinese/English), with a manual override in the picker.
|
|
16
17
|
- **Crossfade** — wallpaper switches and clearing fade smoothly instead of snapping.
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
- **全库浏览** —— 视频与网页壁纸动态渲染;场景与应用壁纸显示预览图作为静态背景。凡是有预览图或可播放文件的壁纸都可选用,按类型标注角标(视频 / 网页 / 静态)。
|
|
10
10
|
- **搜索** —— 按标题或创意工坊 ID 即时筛选网格。
|
|
11
11
|
- **轮播列表** —— 可建任意多个列表,各自拥有壁纸集合、切换间隔(1–120 分钟)和顺序(顺序/随机)。首次运行自动导入第一个可播放的 Wallpaper Engine 播放列表,其余列表可随时导入。
|
|
12
|
-
- **四个实时滑杆** ——
|
|
12
|
+
- **四个实时滑杆** —— 壁纸模糊、暗化、边框、玻璃,即时生效并持久保存。
|
|
13
|
+
- **任意壁纸都可读** —— 遮罩颜色与文字颜色相对(浅色主题提亮、深色主题压暗);"智能可视"开关每隔几秒采样一次壁纸亮度,画面过亮或过暗时自动加强遮罩。下拉菜单、弹窗等浮层保持 DSH 原生不透明表面,不受影响。
|
|
13
14
|
- **画布排布** —— 视频与静态壁纸支持填充/适应/拉伸/原始四种排布和五向对齐。
|
|
14
15
|
- **资源监控** —— 帧率读数与低帧率提示,页面隐藏或电量不足时自动暂停(均可开关)。
|
|
15
16
|
- **中英双语** —— 设置界面跟随 DSH 的"语言"偏好自动切换,也可在面板内手动指定。
|
package/lib/client.js
CHANGED
|
@@ -79,6 +79,8 @@ window.__ModuleLoader__.load({
|
|
|
79
79
|
glass: "玻璃",
|
|
80
80
|
pauseOnHidden: "页面隐藏时暂停",
|
|
81
81
|
pauseOnBattery: "低电量时暂停",
|
|
82
|
+
autoScrim: "智能可视",
|
|
83
|
+
autoScrimTitle: "采样壁纸亮度,过亮或过暗时自动加强遮罩,保证文字可读",
|
|
82
84
|
fpsTitle: "页面当前渲染帧率",
|
|
83
85
|
lowFpsHint: "帧率偏低,可尝试暂停视频、增大壁纸模糊或换用静态壁纸",
|
|
84
86
|
language: "语言",
|
|
@@ -157,6 +159,8 @@ window.__ModuleLoader__.load({
|
|
|
157
159
|
glass: "Glass",
|
|
158
160
|
pauseOnHidden: "Pause when tab hidden",
|
|
159
161
|
pauseOnBattery: "Pause on low battery",
|
|
162
|
+
autoScrim: "Smart veil",
|
|
163
|
+
autoScrimTitle: "Samples wallpaper brightness and strengthens the veil when needed so text stays readable",
|
|
160
164
|
fpsTitle: "Current page frame rate",
|
|
161
165
|
lowFpsHint: "Low frame rate — try pausing the video, raising wallpaper blur, or using a still wallpaper",
|
|
162
166
|
language: "Language",
|
|
@@ -218,6 +222,7 @@ window.__ModuleLoader__.load({
|
|
|
218
222
|
rotationSeeded: false,
|
|
219
223
|
pauseOnHidden: true,
|
|
220
224
|
pauseOnBattery: true,
|
|
225
|
+
autoScrim: true,
|
|
221
226
|
lang: "auto",
|
|
222
227
|
fit: "cover",
|
|
223
228
|
position: "center",
|
|
@@ -268,6 +273,7 @@ window.__ModuleLoader__.load({
|
|
|
268
273
|
rotationSeeded: o.rotationSeeded === true,
|
|
269
274
|
pauseOnHidden: o.pauseOnHidden !== false,
|
|
270
275
|
pauseOnBattery: o.pauseOnBattery !== false,
|
|
276
|
+
autoScrim: o.autoScrim !== false,
|
|
271
277
|
lang: o.lang === "zh" || o.lang === "en" ? o.lang : "auto",
|
|
272
278
|
fit: FIT_MODES.indexOf(o.fit) >= 0 ? o.fit : DEFAULTS.fit,
|
|
273
279
|
position: POSITIONS.indexOf(o.position) >= 0 ? o.position : DEFAULTS.position,
|
|
@@ -319,6 +325,7 @@ window.__ModuleLoader__.load({
|
|
|
319
325
|
rotationSeeded: selection.rotationSeeded,
|
|
320
326
|
pauseOnHidden: selection.pauseOnHidden,
|
|
321
327
|
pauseOnBattery: selection.pauseOnBattery,
|
|
328
|
+
autoScrim: selection.autoScrim,
|
|
322
329
|
lang: selection.lang,
|
|
323
330
|
fit: selection.fit,
|
|
324
331
|
position: selection.position,
|
|
@@ -660,6 +667,14 @@ window.__ModuleLoader__.load({
|
|
|
660
667
|
media.setAttribute("decoding", "async");
|
|
661
668
|
media.className = "webg-media";
|
|
662
669
|
}
|
|
670
|
+
// First luminance sample as soon as the frame is decodable (video/still
|
|
671
|
+
// only — iframe contents are opaque to us). Later samples ride the timer.
|
|
672
|
+
if (sel.type === "video" || sel.type === "image") {
|
|
673
|
+
media.addEventListener(sel.type === "video" ? "loadeddata" : "load", () => {
|
|
674
|
+
sampleWallpaperLuminance();
|
|
675
|
+
applyEffects();
|
|
676
|
+
});
|
|
677
|
+
}
|
|
663
678
|
media.addEventListener("error", () => {
|
|
664
679
|
const id = sel.id;
|
|
665
680
|
const count = (mediaFailures.get(id) || 0) + 1;
|
|
@@ -765,9 +780,52 @@ window.__ModuleLoader__.load({
|
|
|
765
780
|
}
|
|
766
781
|
|
|
767
782
|
// ── Effect application: push the knobs into CSS variables ───────────────────
|
|
783
|
+
// Wallpaper luminance sampled from the live media (null until the first
|
|
784
|
+
// sample; iframes of web wallpapers can never be sampled — opaque origin).
|
|
785
|
+
let sampledLuminance = null;
|
|
786
|
+
let sampleCanvas = null;
|
|
787
|
+
|
|
788
|
+
/** Average Rec.601 luminance (0..1) of the current video/img, or null. */
|
|
789
|
+
function sampleWallpaperLuminance() {
|
|
790
|
+
if (typeof document === "undefined") return;
|
|
791
|
+
const layer = document.getElementById(LAYER_ID);
|
|
792
|
+
const media = layer && (layer.querySelector("video") || layer.querySelector("img"));
|
|
793
|
+
if (!media) { sampledLuminance = null; return; }
|
|
794
|
+
try {
|
|
795
|
+
if (!sampleCanvas) {
|
|
796
|
+
const c = document.createElement("canvas");
|
|
797
|
+
if (typeof c.getContext !== "function") return; // no canvas → no sampling
|
|
798
|
+
c.width = 16; c.height = 16;
|
|
799
|
+
sampleCanvas = c;
|
|
800
|
+
}
|
|
801
|
+
const g = sampleCanvas.getContext("2d", { willReadFrequently: true });
|
|
802
|
+
g.drawImage(media, 0, 0, 16, 16);
|
|
803
|
+
const d = g.getImageData(0, 0, 16, 16).data;
|
|
804
|
+
let sum = 0;
|
|
805
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
806
|
+
sum += 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
|
|
807
|
+
}
|
|
808
|
+
sampledLuminance = sum / (d.length / 4) / 255;
|
|
809
|
+
} catch { /* frame not ready / unreadable → keep the previous sample */ }
|
|
810
|
+
}
|
|
811
|
+
|
|
768
812
|
function applyEffects() {
|
|
769
813
|
const s = document.body.style;
|
|
770
|
-
|
|
814
|
+
// The veil OPPOSES the text colour: dark theme (light text) darkens bright
|
|
815
|
+
// wallpapers, light theme (dark text) brightens dark ones — either way the
|
|
816
|
+
// text side wins. This is what makes ANY wallpaper survivable.
|
|
817
|
+
const dark = Boolean(document.body.hasAttribute && document.body.hasAttribute("data-ds-dark-theme"));
|
|
818
|
+
let alpha = selection.scrim;
|
|
819
|
+
if (selection.autoScrim && sampledLuminance !== null) {
|
|
820
|
+
// Distance of the wallpaper's brightness from the "comfortable middle"
|
|
821
|
+
// towards the text colour's weakness, mapped to a veil-alpha floor.
|
|
822
|
+
const need = dark
|
|
823
|
+
? Math.min(0.55, Math.max(0, sampledLuminance - 0.5))
|
|
824
|
+
: Math.min(0.55, Math.max(0, 0.5 - sampledLuminance));
|
|
825
|
+
alpha = Math.max(alpha, Math.round(need * 1000) / 1000);
|
|
826
|
+
}
|
|
827
|
+
const veilColor = "rgba(" + (dark ? "0,0,0" : "255,255,255") + "," + alpha + ")";
|
|
828
|
+
s.setProperty("--webg-scrim-color", veilColor);
|
|
771
829
|
s.setProperty("--webg-border-alpha", String(selection.border));
|
|
772
830
|
s.setProperty("--webg-blur", selection.blur + "px");
|
|
773
831
|
// The glass "colour melt" scales with blur radius: 0 blur → no melt.
|
|
@@ -785,7 +843,7 @@ window.__ModuleLoader__.load({
|
|
|
785
843
|
// Write the scrim colour directly too, then force a synchronous layout, so
|
|
786
844
|
// slider feedback lands on this frame even on stalled compositors.
|
|
787
845
|
const scrim = document.getElementById(SCRIM_ID);
|
|
788
|
-
if (scrim) scrim.style.background =
|
|
846
|
+
if (scrim) scrim.style.background = veilColor;
|
|
789
847
|
if (document.body && document.body.offsetHeight !== undefined) {
|
|
790
848
|
void document.body.offsetHeight;
|
|
791
849
|
}
|
|
@@ -947,6 +1005,12 @@ window.__ModuleLoader__.load({
|
|
|
947
1005
|
persistSelection();
|
|
948
1006
|
emit();
|
|
949
1007
|
};
|
|
1008
|
+
const onToggleAutoScrim = () => {
|
|
1009
|
+
selection.autoScrim = !selection.autoScrim;
|
|
1010
|
+
persistSelection();
|
|
1011
|
+
applyEffects();
|
|
1012
|
+
emit();
|
|
1013
|
+
};
|
|
950
1014
|
|
|
951
1015
|
const onScrim = (pct) => { selection.scrim = pct / 100; persistSelection(); applyEffects(); emit(); };
|
|
952
1016
|
const onBorder = (pct) => { selection.border = pct / 100; persistSelection(); applyEffects(); emit(); };
|
|
@@ -1242,6 +1306,12 @@ window.__ModuleLoader__.load({
|
|
|
1242
1306
|
}),
|
|
1243
1307
|
t.pauseOnBattery,
|
|
1244
1308
|
),
|
|
1309
|
+
React.createElement("label", { className: "webg-toggle", title: t.autoScrimTitle },
|
|
1310
|
+
React.createElement("input", {
|
|
1311
|
+
type: "checkbox", checked: sel.autoScrim, onChange: onToggleAutoScrim,
|
|
1312
|
+
}),
|
|
1313
|
+
t.autoScrim,
|
|
1314
|
+
),
|
|
1245
1315
|
sel.fps !== null && React.createElement("span", {
|
|
1246
1316
|
className: "webg-hint webg-fps" + (sel.fps < LOW_FPS ? " webg-fps--low" : ""),
|
|
1247
1317
|
title: t.fpsTitle,
|
|
@@ -1612,9 +1682,28 @@ window.__ModuleLoader__.load({
|
|
|
1612
1682
|
const unsubEffects = subscribe(applyEffects);
|
|
1613
1683
|
syncLayers();
|
|
1614
1684
|
applyEffects();
|
|
1685
|
+
// Luminance sampler: re-reads the wallpaper periodically so a bright
|
|
1686
|
+
// scene arriving mid-video re-raises the veil. (rAF-free: 2.5 s is
|
|
1687
|
+
// plenty for legibility, and an idle timer costs ~nothing.)
|
|
1688
|
+
let samplerId = null;
|
|
1689
|
+
if (typeof window !== "undefined" && typeof window.setInterval === "function") {
|
|
1690
|
+
samplerId = window.setInterval(() => {
|
|
1691
|
+
sampleWallpaperLuminance();
|
|
1692
|
+
applyEffects();
|
|
1693
|
+
}, 2500);
|
|
1694
|
+
}
|
|
1695
|
+
// A theme switch flips the veil colour immediately (black ↔ white).
|
|
1696
|
+
let observer = null;
|
|
1697
|
+
if (typeof MutationObserver === "function") {
|
|
1698
|
+
observer = new MutationObserver(() => applyEffects());
|
|
1699
|
+
observer.observe(document.body, { attributes: true, attributeFilter: ["data-ds-dark-theme"] });
|
|
1700
|
+
}
|
|
1615
1701
|
return () => {
|
|
1616
1702
|
unsubLayers();
|
|
1617
1703
|
unsubEffects();
|
|
1704
|
+
if (samplerId !== null) window.clearInterval(samplerId);
|
|
1705
|
+
if (observer) observer.disconnect();
|
|
1706
|
+
sampledLuminance = null;
|
|
1618
1707
|
clearRotationTimer();
|
|
1619
1708
|
// Cancel pending crossfade/clear timers so they never fire on
|
|
1620
1709
|
// torn-down nodes after dispose.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wallpaper-engine-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Wallpaper Engine library as the DSH web GUI background — video/web/still wallpapers, rotation lists, search, resource monitor, bilingual UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|