dsh-mpkg-wallpaper 3.8.2 → 3.9.0
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.en.md +256 -168
- package/README.md +242 -156
- package/lib/client.js +1645 -103
- package/lib/index.js +82 -8
- package/lib/web-wallpaper.js +27 -2
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -6,26 +6,125 @@
|
|
|
6
6
|
|
|
7
7
|
Adds background wallpapers to the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web UI (`dsh web`): **Wallpaper Engine `.mpkg` parsing, Steam Workshop folders, video/web/image wallpapers, time-of-day switching for Time Variation wallpapers, a full-screen blur system, theme colours and glass surfaces, a local wallpaper library, scheduled rotation, a Now playing widget and one-click update**. Almost every appearance detail is adjustable.
|
|
8
8
|
|
|
9
|
-
> Version scope: this document describes the implementation shipped as **`3.
|
|
9
|
+
> Version scope: this document describes the implementation shipped as **`3.9.0`** in `package.json`. The publish surface is **15 files** (`lib/` runtime files + `package.json`, `icon.svg`, `cordis.patch.yml`, `README.md`, `README.en.md`, `THIRD-PARTY.md`, `LICENSE`; measured with `npm pack --dry-run`: 15 files / 2,088,240 B unpacked); `lib/liquid-glass/**`, `lib/liquid-glass-bundle.js`, `dist/`, `tools/` and `docs/` never enter the npm package (`package.json:8-21`). Defaults that changed: **`npNowPlaying` off → on** (since 3.8.0) and **`powPauseHidden` off → on** (since 3.9.0, migrating only profiles that never set it; see [What changed in this version](#what-changed-in-this-version)).
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Download · Install
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
The plugin is published on npm (`dsh-mpkg-wallpaper`). Four ways to load it — pick one from this table, then read its section:
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
| Option | Who it is for | How to update | Client UI |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| 1 `dsh plugin add` (recommended) | the default choice; the market shows "installed" | `dsh plugin --profile web update …` | full |
|
|
20
|
+
| 2 pnpm manual install | you manage the profile's dependency table yourself | same (through the dependency table) | full |
|
|
21
|
+
| 3 Git clone | developers / offline / you want to edit the code | `git pull` | full |
|
|
22
|
+
| 4 single-file bundle | offline emergencies; reusing the routes from a non-DSH host | regenerate and replace that `.mjs` | **host half only** |
|
|
23
|
+
|
|
24
|
+
### Option 1: `dsh plugin add` (recommended, market-recognized)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
dsh plugin --profile web add dsh-mpkg-wallpaper
|
|
28
|
+
# restart dsh web, then Ctrl+F5 in the browser
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Option 2: pnpm manual install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm --dir $DSH_HOME/profiles/<profile> add dsh-mpkg-wallpaper
|
|
35
|
+
# restart dsh web, then Ctrl+F5
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Same source as option 1, just without the `dsh plugin` wrapper.
|
|
39
|
+
|
|
40
|
+
### Option 3: Git clone (developers / offline)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/XHR666/dsh-mpkg-wallpaper.git $DSH_HOME/profiles/<profile>/node_modules/dsh-mpkg-wallpaper
|
|
44
|
+
# then register in the profile's cordis.patch.yml:
|
|
45
|
+
# - insert:
|
|
46
|
+
# - id: dsh-mpkg-wallpaper
|
|
47
|
+
# name: dsh-mpkg-wallpaper
|
|
48
|
+
# restart to take effect
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> Option 3 writes no dependency entry ⇒ the market does not show "installed" (display only, functionality unaffected).
|
|
52
|
+
|
|
53
|
+
### Option 4: single-file bundle (offline / drop-in; **host half only**)
|
|
54
|
+
|
|
55
|
+
Inline the host half into a self-contained ESM and register that:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
cd /path/to/dsh-mpkg-wallpaper
|
|
59
|
+
node tools/build-bundle.mjs # output: dist/dsh-mpkg-wallpaper.bundle.mjs (measured 449,671 B / 439.1KB; the gate output is authoritative)
|
|
60
|
+
node tools/build-bundle.mjs --check # cross-check against the source: export surface / route table / ping JSON shape (20 assertions)
|
|
61
|
+
node tools/bundle-equivalence-test.mjs # the fuller equivalence gate (38 assertions; gate step 11)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Copy `dist/dsh-mpkg-wallpaper.bundle.mjs` anywhere (e.g. `~/.dsh/plugins/`), register it by **absolute path** in the profile's `cordis.patch.yml`, then restart `dsh web`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# $DSH_HOME/profiles/<profile>/cordis.patch.yml
|
|
68
|
+
- insert:
|
|
69
|
+
- id: dsh-mpkg-wallpaper
|
|
70
|
+
name: /absolute/path/dsh-mpkg-wallpaper.bundle.mjs # ← points at the .mjs file itself
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**What this path loads and what it does not** (code and gate facts):
|
|
74
|
+
|
|
75
|
+
| Item | Option 4 behaviour | Evidence |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| Host half (upload/Range streaming, scene extraction, audio lists, settings persistence, diag reporting — **41 routes**) | **Complete** (four host modules inlined; node builtins only) | route table identical `[41 entries]` |
|
|
78
|
+
| `/api/mpkg-wallpaper/ping` | `{ok, version, betterSidebar, betterSidebarVersion}` key set identical to the source | `build-bundle.mjs --check` |
|
|
79
|
+
| **Client half (settings panel / wallpaper layer / frost / Now playing)** | **Not loaded** — only the host export surface (`apply` / `inject` / `__mpwTest`) is present | DSH finds the client half **per package** (Loader entries declaring `dsh.client` → `exports["./client"]`); a bare `.mjs` has no package.json |
|
|
80
|
+
| `GET /api/mpkg-wallpaper/lg/*` (legacy WebGL hosting route, no client caller) | **404** unless a `liquid-glass/` folder sits next to the bundle | the route resolves `liquid-glass/` from `import.meta.url` (`lib/index.js:3453`) |
|
|
81
|
+
| `ping.version` | `null` when the parent directory has no `package.json` (version display only) | `new URL('../package.json', import.meta.url)` (`lib/index.js:1622`) |
|
|
82
|
+
| "Check for updates / one-click update" | No companion `package.json` ⇒ `update-check` 500s and `update-apply` writes next to the bundle: **not recommended here** | `lib/index.js:1792-1860` |
|
|
83
|
+
| Uninstall | Delete that `.mjs` and its line in `cordis.patch.yml` | — |
|
|
84
|
+
|
|
85
|
+
> Conclusion: **option 4 is a degraded "host capability only" load** (handy offline, as an emergency path, or when reusing the routes from a non-DSH host); use options 1/2/3 for the full UI. The artifact is **not committed** (`dist/` is in `.gitignore`: it is a pure derivative of `lib/*.js`, two builds are byte-identical — section ② of `tools/bundle-equivalence-test.mjs`; generate it at release time and publish the hash).
|
|
86
|
+
|
|
87
|
+
### Update
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# options 1 / 2: through npm's latest tag
|
|
91
|
+
dsh plugin --profile web update dsh-mpkg-wallpaper
|
|
92
|
+
|
|
93
|
+
# option 3: inside the clone
|
|
94
|
+
git pull
|
|
24
95
|
|
|
25
|
-
|
|
96
|
+
# option 4: regenerate and replace that .mjs
|
|
97
|
+
node tools/build-bundle.mjs
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Every route ends the same way: restart `dsh web`, then press `Ctrl+F5`.
|
|
101
|
+
|
|
102
|
+
### Uninstall
|
|
103
|
+
|
|
104
|
+
Options 1/2/3: `dsh plugin --profile web remove dsh-mpkg-wallpaper`.
|
|
105
|
+
Option 4: delete the `.mjs` and its line in `cordis.patch.yml`.
|
|
106
|
+
Leftover data (optional cleanup): browser `localStorage['dsh.mpkg-wallpaper.v2']` and the host directory `~/.dsh-mpkg-wallpaper/` (`settings.json`, `web-store.json`, `media-audio.json`, uploaded mpkg files, transcode cache, `diag-*.json`).
|
|
107
|
+
|
|
108
|
+
## 30-second quick start
|
|
109
|
+
|
|
110
|
+
The shortest path from installed to wallpaper on screen — three steps.
|
|
111
|
+
|
|
112
|
+
1. **Install and restart**: pick any option in the previous section, restart `dsh web`, press `Ctrl+F5`.
|
|
113
|
+
2. **Open the panel**: left sidebar → Settings → *MPKG Wallpaper*.
|
|
114
|
+
3. **Pick a wallpaper**, any one of:
|
|
115
|
+
- drop in an `.mpkg` file (video wallpapers play directly; scene wallpapers use the static frame / layer composite)
|
|
116
|
+
- choose a local image/video, or paste an image URL
|
|
117
|
+
- *Custom local wallpaper folder*: point it at any folder (the Steam `steamapps/workshop/content/431960` root works — every subfolder counts as one wallpaper)
|
|
118
|
+
|
|
119
|
+
The defaults are already usable: master switch on, hybrid mode on, unified blur on (30px), Now playing mounted in the left sidebar.
|
|
120
|
+
To fine-tune, three controls are enough to start: **Wallpaper → Frosted blur** (0–40), **Surface unify → Full-screen blur degree** (0–40), **Wallpaper → Lens zoom** (10–2000%).
|
|
121
|
+
|
|
122
|
+
> If nothing happens, press **one-click diagnostics report** in the *Other* tab first (it downloads a JSON file when the host is unavailable), then take it to [Bug reports](#bug-reports).
|
|
26
123
|
|
|
27
124
|
## Core features
|
|
28
125
|
|
|
126
|
+
This section groups features by **what you can perceive** (sources, time variation, blur, appearance, playback, library and rotation, safety, backup) rather than by code module.
|
|
127
|
+
|
|
29
128
|
**📦 Wallpaper sources**
|
|
30
129
|
- **Wallpaper Engine `.mpkg`**: the container is parsed in the browser (nothing is uploaded to a third party); video wallpapers play their embedded mp4 / video textures, scene wallpapers have their assets extracted, and **Time Variation** wallpapers pick the material for the current time slot
|
|
31
130
|
- **Steam Workshop folders**: Wallpaper Engine installs are discovered automatically (registry + `libraryfolders.vdf`, non-default drives included) and listed as `video / web / scene`; the **workshop root** (`steamapps/workshop/content/431960`) can also be set as a custom folder — every subfolder is auto-detected as one wallpaper
|
|
@@ -74,13 +173,15 @@ Current version = **3.8.0** in `package.json`. Release prerequisites, commands a
|
|
|
74
173
|
|
|
75
174
|
## Supported types and boundaries
|
|
76
175
|
|
|
176
|
+
This section answers "does my material work, and how well"; the boundary list under the table explains why some things are out of reach.
|
|
177
|
+
|
|
77
178
|
| Type | Status | What it can / cannot control |
|
|
78
179
|
|---|---|---|
|
|
79
180
|
| **mpkg (video)** | ✅ Full | Embedded mp4 / video textures play directly; mute, speed, pause, blur/zoom/brightness all adjustable |
|
|
80
|
-
| **mpkg (scene)** | 🟡 Compromise |
|
|
181
|
+
| **mpkg (scene)** | 🟡 Compromise | Static frame / layer composite / embedded video slots; **Live2D puppets, shaders and scripts are out of reach** |
|
|
81
182
|
| **Time Variation** | ✅ Multi-slot | Automatic switching + manual lock; only the current slot is extracted |
|
|
82
183
|
| **Video (mp4/webm/mov/m4v)** | ✅ Full | Plays directly; fps/resolution caps require ffmpeg transcoding |
|
|
83
|
-
| **Web (HTML)** | 🟡 Experimental | Sandboxed iframe + WE API shim; **Live2D-class
|
|
184
|
+
| **Web (HTML)** | 🟡 Experimental | Sandboxed iframe + WE API shim; **Live2D-class settings are editable**; external-SDK / heavily interactive ones are not adapted |
|
|
84
185
|
| **scene.pkg loose folder** | 🟡 Compromise | Same as mpkg scene wallpapers |
|
|
85
186
|
| **preview.gif / image / animation** | ✅ Full | Falls back to the author's preview animation when a scene has nothing better (`lib/client.js:887`, `:12272`) |
|
|
86
187
|
| **Application (exe)** | ❌ Excluded | Content detection yields `unknown/excluded-application`; never read, never executed (`lib/web-wallpaper.js:199`) |
|
|
@@ -93,9 +194,9 @@ Current version = **3.8.0** in `package.json`. Release prerequisites, commands a
|
|
|
93
194
|
- Web wallpapers **cannot** reproduce CSS `:hover/:active`, `isTrusted:true`, in-frame `contextmenu` or pointer lock/fullscreen/download popups (inherent limits of synthetic events, `docs/WEB-WALLPAPER.md` §11.4)
|
|
94
195
|
- Assets above 600MB work only in **hybrid mode**; browser-only mode additionally caps video textures at 250MB, images at 200MB and local image files at 100MB
|
|
95
196
|
|
|
96
|
-
## Settings (7 tabs)
|
|
197
|
+
## Settings reference (7 tabs)
|
|
97
198
|
|
|
98
|
-
The tab order is fixed: `TAB_ORDER = ["source","wallpaper","appearance","unify","blur","other","liquid"]` (`lib/client.js:10730`), labelled **Background source / Wallpaper / Appearance / Surface unify / UI blur / Other / Liquid Glass (test)**.
|
|
199
|
+
This is the **authoritative table of every setting**: each row = panel label + internal key + default + what it does + how to turn it off. The tab order matches the panel and is fixed: `TAB_ORDER = ["source","wallpaper","appearance","unify","blur","other","liquid"]` (`lib/client.js:10730`), labelled **Background source / Wallpaper / Appearance / Surface unify / UI blur / Other / Liquid Glass (test)**.
|
|
99
200
|
|
|
100
201
|
### 1. Background source (source)
|
|
101
202
|
|
|
@@ -117,7 +218,8 @@ Contains the *Wallpaper picture* and *Power saving* sub-sections.
|
|
|
117
218
|
| Label | Key | Default | Purpose | Off / rollback |
|
|
118
219
|
|---|---|---|---|---|
|
|
119
220
|
| Mute (web wallpapers) | `mute` | on | Web-wallpaper audio; off lets the wallpaper make sound | off |
|
|
120
|
-
| **Now playing (above Settings in the sidebar)** | `npNowPlaying` | **on** |
|
|
221
|
+
| **Now playing (above Settings in the sidebar)** | `npNowPlaying` | **on** | Left-sidebar player whose transport row controls wallpaper sound. **Off = zero injection**; yields to another plugin (`data-mpw-np-yield`) | off / restore defaults |
|
|
222
|
+
| Play/pause also controls the wallpaper | `npLinkWallpaper` | on | Now playing's play/pause and position also drive the wallpaper itself; off = only this plugin's player (when the wallpaper media is the only source the controls are **honestly disabled**) | off |
|
|
121
223
|
| Horizontal flip (mirror) | `flipX` | off | Mirrors the wallpaper horizontally | off |
|
|
122
224
|
| Vertical flip (mirror) | `flipY` | off | Mirrors the wallpaper vertically | off |
|
|
123
225
|
| Decode fps cap | `fpsCap` | unlimited | Host ffmpeg frame extraction when the source exceeds the cap (24/30/48/60) | "unlimited" |
|
|
@@ -214,20 +316,72 @@ These keys **exist and participate in the logic** but have no widget on the sett
|
|
|
214
316
|
|
|
215
317
|
| Key | Default | Notes |
|
|
216
318
|
|---|---|---|
|
|
217
|
-
| `clock` / `clock24h` / `clockSec` / `clockDate` / `clockPos` / `clockSize` | off / on / off / off / `tr` / 40 |
|
|
218
|
-
| `bsAlpha` | off | better-sidebar panels follow the theme base colour
|
|
219
|
-
| `bsBottomAvoid` | off | A settled **deliberate no-op** (alignment belongs to better-sidebar's
|
|
220
|
-
| `newStyle` | off | Only changes settings-page widget looks
|
|
319
|
+
| `clock` / `clock24h` / `clockSec` / `clockDate` / `clockPos` / `clockSize` | off / on / off / off / `tr` / 40 | Runtime compatibility item: old configurations still render the clock, the settings page has no toggle |
|
|
320
|
+
| `bsAlpha` | off | better-sidebar panels follow the theme base colour (CSS reads it, no widget) |
|
|
321
|
+
| `bsBottomAvoid` | off | A settled **deliberate no-op** (alignment belongs to better-sidebar's ResizeObserver) |
|
|
322
|
+
| `newStyle` | off | Only changes settings-page widget looks; never enters `buildCss` |
|
|
221
323
|
| `forceEnabled` | off | Runtime priority flag for forcing the feature back on past conflict detection |
|
|
222
|
-
| `opacity` | 82 | The "panel opacity" slider was removed (unified blur uses `sidebarAlpha`
|
|
223
|
-
| `aquaTintStrength` | 45 | How much of the wallpaper
|
|
224
|
-
| `glassColor` / `glassAlpha` | empty / 12 |
|
|
225
|
-
| `webInteraction` | `pointer` | Web-wallpaper interaction mode (`off`/`pointer`/`full`)
|
|
226
|
-
| `sceneRendererUrl` | `http://127.0.0.1:8899/` | Scene-renderer address, overridable
|
|
227
|
-
| `
|
|
324
|
+
| `opacity` | 82 | The "panel opacity" slider was removed (unified blur uses `sidebarAlpha`); the value is still read |
|
|
325
|
+
| `aquaTintStrength` | 45 | How much of the wallpaper colour is mixed into panel tints; read at runtime |
|
|
326
|
+
| `glassColor` / `glassAlpha` | empty / 12 | Early WebGL liquid-glass leftovers: backup/restore only, **no widget and no reader** |
|
|
327
|
+
| `webInteraction` | `pointer` | Web-wallpaper interaction mode (`off`/`pointer`/`full`); use `?mpwinteract=…` or write the stored settings |
|
|
328
|
+
| `sceneRendererUrl` | `http://127.0.0.1:8899/` | Scene-renderer address, overridable |
|
|
329
|
+
| `npVolume` | 100 | The **volume level** in the Now playing card (0..100, applied to the real element); the default profile **never writes element volume**, only your own change does |
|
|
330
|
+
| `glassWindow` | — | **Retired and deleted** (2026-09-19): no widget, no reader; covered by `settingsBlur` + `dialogBlur`/`popoverBlur` |
|
|
331
|
+
|
|
332
|
+
## Now playing and wallpaper sound
|
|
333
|
+
|
|
334
|
+
This section covers **where the sidebar player mounts, what it shows and what it can control** — its source is the wallpaper's own sound.
|
|
335
|
+
|
|
336
|
+
> Location convention: this section refers to the implementation by **symbol name** (`resolveAnchor` / `shouldHide` / `occupantOf` / `PlayMark` / `markYield` in `lib/now-playing.js`, `opsX` and friends in `lib/now-playing-math.js`, `npResolveMedia` / `npActiveVideo` / `npAudioScope` / `npApplyMute` / `applyNowPlaying` in `lib/client.js`) — **line numbers drift between versions; symbols are authoritative**. The shape is "source + generated inline": `lib/now-playing-math.js` + `lib/now-playing.js` are inlined byte-for-byte into the `MPW-NP-GEN-START/END` region of `lib/client.js` by `tools/build-now-playing.mjs`.
|
|
337
|
+
|
|
338
|
+
- **Mount point**: the host slot `sidebar.footer.action` (the `createSlotAction` registration in `lib/client.js`: `id:"mpw-now-playing"`, `order:60`). When the slot is unavailable, `resolveAnchor()` falls back by mode: `slot` → `settings-slot` (before the host's settings cell) → `settings-area` (before `[class*="settingsArea"]`) → `foot` (first position in `[class*="footArea"]`); if none holds it **creates no node at all** and logs a `console.warn`. **A late anchor still gets mounted**: when no landing spot exists it watches the document and mounts itself as soon as the host's slot outlet renders (the old code only warned and returned, so after a wallpaper switch on a real machine the widget never came back).
|
|
339
|
+
- **Yielding (the counterpart of being on by default)**: `occupantOf(container, mode, selfNode)` walks the container's children and lets three kinds through — our own nodes, host-owned nodes (the slot outlet / settings cell) and effectively empty nodes; the first remaining element counts as an **occupant** ⇒ we do not mount (before mounting) or we retract (after mounting, via `MutationObserver` with `subtree:true`), write `data-mpw-np-yield="foreign-occupant"` and log one readable warning; when the occupant leaves we come back. The criteria are **two-way**: neither our own nodes nor the host's cells may be misread as occupants.
|
|
340
|
+
- **Hidden when the sidebar is collapsed**: `data-mpw-np-hidden` + CSS `display:none`. The criterion is **physical width first** (`shouldHide(width, hostCollapsed)`: a measured width ≥ `NP_COLLAPSE_MAX_W = 96` forbids hiding); host signals (slot `wide` / `data-sidebar-collapsed` / root class `collapsed`) are a fallback only when the width **cannot be measured**, and there is a **one-shot** re-check after the anchor moves. When space is tight the whole widget scales by `--mpw-np-fit = clamp(avail/260, 0.5, 1)`, where `avail` measures **our own container** (not `[class*="sidebarCol"]` — that class name appears more than once on a real machine).
|
|
341
|
+
- **Shape**: one pill that expands into a card — artwork (the current wallpaper's thumbnail), title/byline, progress rail + clock and a full-card hit target. **Four keys when expanded**: previous / play-pause / next / mute-unmute; **three keys when collapsed** — the mute key appears with the card, because the collapsed transport row is positioned as an 88px three-key row at `opsX(0) = 206` and forcing a fourth key in would overflow the right padding. Expanding is a self-stopping 0→1 tween (no resident rAF); play/pause is **not a swapped icon** but the pair of eight-point quads, now shaped by a **tween of the playback state itself** (`mark`: 0 = paused, 1 = playing), while the morph progress `p` only drives size and position.
|
|
342
|
+
- **Data sources** (`npResolveMedia`: **we only report what we actually know**):
|
|
343
|
+
|
|
344
|
+
| Current wallpaper | What NP shows | What it can control |
|
|
345
|
+
|---|---|---|
|
|
346
|
+
| Video wallpaper (the `<video>` that is **really playing** right now) | Real playback state, duration and progress; when we know there is no audio track the byline says so | Play / pause / mute-unmute (no audio info ⇒ we do not guess, mute stays enabled); **no track list ⇒ previous/next honestly disabled** |
|
|
347
|
+
| Audio files that **really exist** in the wallpaper folder (custom folder / library / scene folder) | File name + list index; progress and duration from the media element | Our own `<audio>`: play / pause / **previous · next step through the list in order and wrap around** (with only one track both side keys are disabled) / mute |
|
|
348
|
+
| Web wallpaper (no separate audio file in the folder) | The byline says "web wallpaper sound" | **Mute is the only channel** (`canPlay=false`; we do not pretend we can pause in-frame WebAudio) |
|
|
349
|
+
| No source (static image / list not there yet / scene without separate audio) | Idle state (title = nothing playing) | No "does nothing when clicked" buttons; pressing play produces a panel note plus one console line — **no fake actions** |
|
|
350
|
+
|
|
351
|
+
- **Two new controls (NP-4)**: ① a **volume level** — the volume bar in the card, key `npVolume` (0..100), really applied to the element (the default profile **never writes element volume**, only your own change does); ② a **draggable position (seek)**. Both are governed by the switch below.
|
|
352
|
+
- **"Play/pause also controls the wallpaper" (`npLinkWallpaper`, default on)**: on = the transport and the progress bar drive the wallpaper's own playback/position too (default on = byte-identical to the previous behaviour); off = only this plugin's own player is driven and the wallpaper media is **not touched at all**; when the wallpaper media is the only sound source the controls are **honestly disabled** and the byline says why (`np.note.linkOff`).
|
|
353
|
+
- **What it cannot do** (listed explicitly, no fake actions; `docs/NOW-PLAYING-DSH.md` §7.7.7 and §7.8): there is no system media source (that is what `lib/media-session.js` is for, and it is not wired up yet); a web wallpaper's in-frame sound supports **mute only** — its own play/pause is out of reach; a video wallpaper has no previous/next; the **mute key only appears in the expanded state** (expand the card first to unmute — a geometry trade-off, not a broken key); the heart button is not rendered; no waveform, no keyboard shortcuts; audio inside a package (`scene.pkg`) **can be listed but not played** (`np.note.pkgListOnly`); the progress bar is draggable, but **honestly not draggable when the link switch is off or nothing is seekable** (`np.note.noseek`). The double-audio combination where "the wallpaper also plays the same track" **has no on-device sample to verify against** (the mitigation in place: the frame is force-muted while our player is playing).
|
|
354
|
+
- **Rollback**: turn `npNowPlaying` off (or "Restore all defaults") ⇒ back to zero injection (no DOM, no observers, not a single NP rule in the output). Regression: `node tools/now-playing-test.mjs` (**83 passed / 0 failed**, including 7 mutations) + `node tools/np-media-test.mjs` (**82 passed / 0 failed**, including 12 mutation self-proofs; `--no-mutations` leaves 70 main assertions) — both registered in step 2 of `tools/check.sh`. On-device probes (need `:3080` + headless Firefox, not part of the standing gate): `node tools/np-sidebar-live-probe.mjs` (12 criteria) and `node tools/np-media-live-probe.mjs` (16 PASS / 22 FAIL before the fix → 45 PASS / 0 FAIL after).
|
|
355
|
+
- **Attribution**: the component is a **line-by-line port of Bencho's "Now playing" (MIT)** with the upstream comments kept verbatim; the sidebar mount controller, yield criteria, self-drawn icons, token mapping, data wiring and gates are written here. Registered in `THIRD-PARTY.md` §6.
|
|
356
|
+
|
|
357
|
+
## System media session (MPRIS / SMTC)
|
|
358
|
+
|
|
359
|
+
This section covers the **host half** of "show what the system is playing": the capability matrix, the honest unavailable path, and the current wiring status.
|
|
360
|
+
|
|
361
|
+
`lib/media-session.js` (1052 lines, MIT, written in this repository, no third-party code) is the **host half** of the "let Now playing show what the *system* is playing" chain. Contract: `createMediaSession({run, platform, env, now, timeoutMs, log})` → `{probe(), snapshot(), control(op,arg), stats(), lastProbe()}` (`lib/media-session.js:22-40`).
|
|
362
|
+
|
|
363
|
+
**Capability matrix**
|
|
364
|
+
|
|
365
|
+
| Platform | Channel | Adapters (by priority) | Metadata / state / position / artwork | Control |
|
|
366
|
+
|---|---|---|---|---|
|
|
367
|
+
| Linux / FreeBSD / OpenBSD | MPRIS over D-Bus (`org.mpris.MediaPlayer2.*`) | `playerctl` → `dbus-send` | One call returns 7 fields: `status / mpris:length / position / xesam:title / xesam:artist / xesam:album / mpris:artUrl` | `play` `pause` `playpause` `next` `prev` `seek` |
|
|
368
|
+
| Windows 10/11 | SMTC (`GlobalSystemMediaTransportControlsSessionManager`) | `smtc` (`powershell.exe -NoProfile -NonInteractive`) | Real WinRT calls for properties / timeline / playback controls / thumbnail (as a base64 data URL) | Same ops (op and position passed as separate argv elements) |
|
|
369
|
+
| macOS | — | — | ❌ Not implemented: `unsupported-platform`, **0 commands** | ❌ |
|
|
370
|
+
| Anything else / no adapter | — | `none` | ❌ `not-installed` | ❌ |
|
|
371
|
+
|
|
372
|
+
- **Shape**: the `snapshot` always has its 21 keys; unreadable values are empty/neutral (`duration`/`position` are milliseconds and `null` when unknown — it **never invents 0**); when `available:true`, `title` is non-empty (`lib/media-session.js:42-56`, `:220-244`).
|
|
373
|
+
- **With no desktop session bus it reports unavailable honestly**: `probe()` really probes (`dbus-send --session ListNames`, falling back to `busctl --user list`) and returns `available:false / reason:'no-session-bus'` with a `detail` line such as "总线不可达(via dbus-send):…" (`lib/media-session.js:684-706`, `:775-783`). This is **derived, not hard-coded** — a machine with no desktop environment (container/Termux) really takes this path.
|
|
374
|
+
- **It never throws**: `snapshot()` / `control()` never reject; every failure is a return value whose `reason` is one of 15 values (`unsupported-platform / disabled-by-env / not-installed / no-session-bus / no-player / no-metadata / empty-output / unparsable / timeout / not-available / bad-op / bad-arg / bad-player / error`).
|
|
375
|
+
- **Security**: command names and arguments are passed **separately** (argv arrays, never through `sh -c`); player names must match `/^[A-Za-z0-9_.-]{1,64}$/` or the result is `bad-player` with 0 commands; `op` is allow-listed and `seek` is bounded to `0..24h`; the timeout defaults to 800 ms (50–5000 ms); reads are single-flight with a global serial queue (at most one command at a time).
|
|
376
|
+
- **Zero dependencies**: only `node:child_process`; `package.json` gained no dependency at all.
|
|
377
|
+
- **Environment variables**: `MPW_MEDIA_ADAPTER` (pin an adapter), `MPW_MEDIA_PLAYER` (pin a player name), `MPW_MEDIA_TIMEOUT_MS`.
|
|
378
|
+
|
|
379
|
+
⚠ **Wiring status (honest)**: the module is **not wired into the plugin yet** — `lib/index.js`, `lib/client.js` and `tools/build-bundle.mjs` do not import it, and there is no `/media-session` or `/media-control` host route, so **the Now playing widget still shows the wallpaper's own media, not a system player**. Host routes and UI display are planned in [`docs/MEDIA-SESSION.md`](docs/MEDIA-SESSION.md) §9/§10 but marked as not done. Gate: `node tools/media-session-test.mjs` (95 assertions = 89 main + 6 mutation self-proofs), **not part of `tools/check.sh`**.
|
|
228
380
|
|
|
229
381
|
## Diagnostics and troubleshooting
|
|
230
382
|
|
|
383
|
+
Start here when something is wrong: one click sends state back to the local host, and individual diagnostics switches are only for narrowing things down.
|
|
384
|
+
|
|
231
385
|
### Plugin side (this plugin's own switches)
|
|
232
386
|
|
|
233
387
|
- **One-click diagnostics report** (*Other* tab): packs whether frost/sidebar/timeline were affected, the wallpaper type and path, whether the shim was injected, video decoding, surface tokens and scene health into one payload → `POST /api/mpkg-wallpaper/diag`, written to `~/.dsh/.dsh-mpkg-wallpaper/diag-<epochms>.json` (`lib/index.js:1605-1620`; directory capped at 50 files / 32MB, `lib/index.js:1523-1524`; client-side single-payload cap 512KB). **When the host is unavailable it automatically downloads `mpw-diag-<ISO>.json` instead**, so state can be reported offline. Every field carries `provenance`; unreadable ones are `value:null + degraded` ([`docs/DIAGNOSTICS.md`](docs/DIAGNOSTICS.md)).
|
|
@@ -269,138 +423,38 @@ Two different numbers — do not mix them up:
|
|
|
269
423
|
|
|
270
424
|
> These are **renderer** URL parameters, not plugin settings; the plugin only appends them to the scene iframe (`lib/client.js:13981-14000`, `lib/client.js:3163`). The panel copy and its offline mirror must match the `common` set of `diag-flags.json`, asserted by `tools/panel-smoke.mjs` (`tools/panel-smoke.mjs:316-372`).
|
|
271
425
|
|
|
272
|
-
##
|
|
273
|
-
|
|
274
|
-
`lib/media-session.js` (1052 lines, MIT, written in this repository, no third-party code) is the **host half** of the "let Now playing show what the *system* is playing" chain. Contract: `createMediaSession({run, platform, env, now, timeoutMs, log})` → `{probe(), snapshot(), control(op,arg), stats(), lastProbe()}` (`lib/media-session.js:22-40`).
|
|
275
|
-
|
|
276
|
-
**Capability matrix**
|
|
277
|
-
|
|
278
|
-
| Platform | Channel | Adapters (by priority) | Metadata / state / position / artwork | Control |
|
|
279
|
-
|---|---|---|---|---|
|
|
280
|
-
| Linux / FreeBSD / OpenBSD | MPRIS over D-Bus (`org.mpris.MediaPlayer2.*`) | `playerctl` → `dbus-send` | One call returns 7 fields: `status / mpris:length / position / xesam:title / xesam:artist / xesam:album / mpris:artUrl` | `play` `pause` `playpause` `next` `prev` `seek` |
|
|
281
|
-
| Windows 10/11 | SMTC (`GlobalSystemMediaTransportControlsSessionManager`) | `smtc` (`powershell.exe -NoProfile -NonInteractive`) | Real WinRT calls for properties / timeline / playback controls / thumbnail (as a base64 data URL) | Same ops (op and position passed as separate argv elements) |
|
|
282
|
-
| macOS | — | — | ❌ Not implemented: `unsupported-platform`, **0 commands** | ❌ |
|
|
283
|
-
| Anything else / no adapter | — | `none` | ❌ `not-installed` | ❌ |
|
|
284
|
-
|
|
285
|
-
- **Shape**: the `snapshot` always has its 21 keys; unreadable values are empty/neutral (`duration`/`position` are milliseconds and `null` when unknown — it **never invents 0**); when `available:true`, `title` is non-empty (`lib/media-session.js:42-56`, `:220-244`).
|
|
286
|
-
- **With no desktop session bus it reports unavailable honestly**: `probe()` really probes (`dbus-send --session ListNames`, falling back to `busctl --user list`) and returns `available:false / reason:'no-session-bus'` with a `detail` line such as "总线不可达(via dbus-send):…" (`lib/media-session.js:684-706`, `:775-783`). This is **derived, not hard-coded** — a machine with no desktop environment (container/Termux) really takes this path.
|
|
287
|
-
- **It never throws**: `snapshot()` / `control()` never reject; every failure is a return value whose `reason` is one of 15 values (`unsupported-platform / disabled-by-env / not-installed / no-session-bus / no-player / no-metadata / empty-output / unparsable / timeout / not-available / bad-op / bad-arg / bad-player / error`).
|
|
288
|
-
- **Security**: command names and arguments are passed **separately** (argv arrays, never through `sh -c`); player names must match `/^[A-Za-z0-9_.-]{1,64}$/` or the result is `bad-player` with 0 commands; `op` is allow-listed and `seek` is bounded to `0..24h`; the timeout defaults to 800 ms (50–5000 ms); reads are single-flight with a global serial queue (at most one command at a time).
|
|
289
|
-
- **Zero dependencies**: only `node:child_process`; `package.json` gained no dependency at all.
|
|
290
|
-
- **Environment variables**: `MPW_MEDIA_ADAPTER` (pin an adapter), `MPW_MEDIA_PLAYER` (pin a player name), `MPW_MEDIA_TIMEOUT_MS`.
|
|
291
|
-
|
|
292
|
-
⚠ **Wiring status (honest)**: the module is **not wired into the plugin yet** — `lib/index.js`, `lib/client.js` and `tools/build-bundle.mjs` do not import it, and there is no `/media-session` or `/media-control` host route, so **the Now playing widget still shows the wallpaper's own media, not a system player**. Host routes and UI display are planned in [`docs/MEDIA-SESSION.md`](docs/MEDIA-SESSION.md) §9/§10 but marked as not done. Gate: `node tools/media-session-test.mjs` (95 assertions = 89 main + 6 mutation self-proofs), **not part of `tools/check.sh`**.
|
|
293
|
-
|
|
294
|
-
## Now playing widget
|
|
295
|
-
|
|
296
|
-
> Location convention: this section refers to the implementation by **symbol name** (`resolveAnchor` / `shouldHide` / `occupantOf` / `PlayMark` / `markYield` in `lib/now-playing.js`, `opsX` and friends in `lib/now-playing-math.js`, `npResolveMedia` / `npActiveVideo` / `npAudioScope` / `npApplyMute` / `applyNowPlaying` in `lib/client.js`) — **line numbers drift between versions; symbols are authoritative**. The shape is "source + generated inline": `lib/now-playing-math.js` + `lib/now-playing.js` are inlined byte-for-byte into the `MPW-NP-GEN-START/END` region of `lib/client.js` by `tools/build-now-playing.mjs`.
|
|
297
|
-
|
|
298
|
-
- **Mount point**: the host slot `sidebar.footer.action` (the `createSlotAction` registration in `lib/client.js`: `id:"mpw-now-playing"`, `order:60`). When the slot is unavailable, `resolveAnchor()` falls back by mode: `slot` → `settings-slot` (before the host's settings cell) → `settings-area` (before `[class*="settingsArea"]`) → `foot` (first position in `[class*="footArea"]`); if none holds it **creates no node at all** and logs a `console.warn`. **A late anchor still gets mounted**: when no landing spot exists it watches the document and mounts itself as soon as the host's slot outlet renders (the old code only warned and returned, so after a wallpaper switch on a real machine the widget never came back).
|
|
299
|
-
- **Yielding (the counterpart of being on by default)**: `occupantOf(container, mode, selfNode)` walks the container's children and lets three kinds through — our own nodes, host-owned nodes (the slot outlet / settings cell) and effectively empty nodes; the first remaining element counts as an **occupant** ⇒ we do not mount (before mounting) or we retract (after mounting, via `MutationObserver` with `subtree:true`), write `data-mpw-np-yield="foreign-occupant"` and log one readable warning; when the occupant leaves we come back. The criteria are **two-way**: neither our own nodes nor the host's cells may be misread as occupants.
|
|
300
|
-
- **Hidden when the sidebar is collapsed**: `data-mpw-np-hidden` + CSS `display:none`. The criterion is **physical width first** (`shouldHide(width, hostCollapsed)`: a measured width ≥ `NP_COLLAPSE_MAX_W = 96` forbids hiding); host signals (slot `wide` / `data-sidebar-collapsed` / root class `collapsed`) are a fallback only when the width **cannot be measured**, and there is a **one-shot** re-check after the anchor moves. When space is tight the whole widget scales by `--mpw-np-fit = clamp(avail/260, 0.5, 1)`, where `avail` measures **our own container** (not `[class*="sidebarCol"]` — that class name appears more than once on a real machine).
|
|
301
|
-
- **Shape**: one pill that expands into a card — artwork (the current wallpaper's thumbnail), title/byline, progress rail + clock and a full-card hit target. **Four keys when expanded**: previous / play-pause / next / mute-unmute; **three keys when collapsed** — the mute key appears with the card, because the collapsed transport row is positioned as an 88px three-key row at `opsX(0) = 206` and forcing a fourth key in would overflow the right padding. Expanding is a self-stopping 0→1 tween (no resident rAF); play/pause is **not a swapped icon** but the pair of eight-point quads, now shaped by a **tween of the playback state itself** (`mark`: 0 = paused, 1 = playing), while the morph progress `p` only drives size and position.
|
|
302
|
-
- **Data sources** (`npResolveMedia`: **we only report what we actually know**):
|
|
303
|
-
|
|
304
|
-
| Current wallpaper | What NP shows | What it can control |
|
|
305
|
-
|---|---|---|
|
|
306
|
-
| Video wallpaper (the `<video>` that is **really playing** right now) | Real playback state plus duration/progress; when we positively know there is no audio track the byline says "this video has no audio track" | Play / pause / mute-unmute (when we cannot tell whether there is audio we **do not guess** and leave the mute key enabled); **there is no track list ⇒ previous/next are honestly disabled** |
|
|
307
|
-
| Audio files that **really exist** in the wallpaper folder (custom folder / library / scene folder) | File name plus its index in the list; progress and duration come from the media element | Our own `<audio>`: play / pause / **previous · next step through the list in order and wrap around** (with only one track both side keys are disabled) / mute |
|
|
308
|
-
| Web wallpaper (no separate audio file in the folder) | The byline says "web wallpaper sound" | **Mute is the only channel** (`canPlay=false`; we do not pretend we can pause in-frame WebAudio) |
|
|
309
|
-
| No source (static image / list not there yet / scene without separate audio) | Idle state (title = nothing playing) | No "does nothing when clicked" buttons; pressing play produces a panel note plus one console line — **no fake actions** |
|
|
310
|
-
|
|
311
|
-
- **What it cannot do** (listed explicitly, no fake actions; `docs/NOW-PLAYING-DSH.md` §7.7.7): there is no system media source (that is what `lib/media-session.js` is for, and it is not wired up yet); a web wallpaper's in-frame sound supports **mute only** — play/pause is out of reach; **volume is a mute switch, not 0..1 fine control** (the only existing channel is the `mute` boolean plus the host `/media-audio` contract); a video wallpaper has no previous/next; the **mute key only appears in the expanded state** (expand the card first to unmute — a geometry trade-off, not a broken key); the heart button is not rendered; no seeking, no waveform, no keyboard shortcuts. The double-audio combination where "the wallpaper also plays the same track" **has no on-device sample to verify against** (the mitigation in place: the frame is force-muted while our player is playing).
|
|
312
|
-
- **Rollback**: turn `npNowPlaying` off (or "Restore all defaults") ⇒ back to zero injection (no DOM, no observers, not a single NP rule in the output). Regression: `node tools/now-playing-test.mjs` (**83 passed / 0 failed**, including 7 mutations) + `node tools/np-media-test.mjs` (**82 passed / 0 failed**, including 12 mutation self-proofs; `--no-mutations` leaves 70 main assertions) — both registered in step 2 of `tools/check.sh`. On-device probes (need `:3080` + headless Firefox, not part of the standing gate): `node tools/np-sidebar-live-probe.mjs` (12 criteria) and `node tools/np-media-live-probe.mjs` (16 PASS / 22 FAIL before the fix → 45 PASS / 0 FAIL after).
|
|
313
|
-
- **Attribution**: the component is a **line-by-line port of Bencho's "Now playing" (MIT)** with the upstream comments kept verbatim; the sidebar mount controller, yield criteria, self-drawn icons, token mapping, data wiring and gates are written here. Registered in `THIRD-PARTY.md` §6.
|
|
314
|
-
|
|
315
|
-
## Installation
|
|
316
|
-
|
|
317
|
-
The plugin is published on npm (`dsh-mpkg-wallpaper`). Four ways to load it:
|
|
318
|
-
|
|
319
|
-
### Option 1: `dsh plugin add` (recommended, market-recognized)
|
|
320
|
-
|
|
321
|
-
```bash
|
|
322
|
-
dsh plugin --profile web add dsh-mpkg-wallpaper
|
|
323
|
-
# restart dsh web, then Ctrl+F5 in the browser
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
Update: `dsh plugin --profile web update dsh-mpkg-wallpaper` (+ restart `dsh web` + Ctrl+F5). This resolves the `latest` tag.
|
|
327
|
-
|
|
328
|
-
### Option 2: pnpm manual install
|
|
329
|
-
|
|
330
|
-
```bash
|
|
331
|
-
pnpm --dir $DSH_HOME/profiles/<profile> add dsh-mpkg-wallpaper
|
|
332
|
-
# restart dsh web, then Ctrl+F5
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Same source as option 1, just without the `dsh plugin` wrapper; updates go through the dependency table too.
|
|
336
|
-
|
|
337
|
-
### Option 3: Git clone (developers / offline)
|
|
338
|
-
|
|
339
|
-
```bash
|
|
340
|
-
git clone https://github.com/XHR666/dsh-mpkg-wallpaper.git $DSH_HOME/profiles/<profile>/node_modules/dsh-mpkg-wallpaper
|
|
341
|
-
# then register in the profile's cordis.patch.yml:
|
|
342
|
-
# - insert:
|
|
343
|
-
# - id: dsh-mpkg-wallpaper
|
|
344
|
-
# name: dsh-mpkg-wallpaper
|
|
345
|
-
# restart to take effect
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
> Option 3 writes no dependency entry ⇒ the market does not show "installed" (display only, functionality unaffected); update with `git pull`.
|
|
349
|
-
|
|
350
|
-
### Option 4: single-file bundle (offline / drop-in; **host half only**)
|
|
351
|
-
|
|
352
|
-
Inline the host half into a self-contained ESM and register that:
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
cd /path/to/dsh-mpkg-wallpaper
|
|
356
|
-
node tools/build-bundle.mjs # output: dist/dsh-mpkg-wallpaper.bundle.mjs (measured 442,317 B / 432.0KB)
|
|
357
|
-
node tools/build-bundle.mjs --check # cross-check against the source: export surface / route table / ping JSON shape (20 assertions)
|
|
358
|
-
node tools/bundle-equivalence-test.mjs # the fuller equivalence gate (38 assertions; gate step 11)
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
Copy `dist/dsh-mpkg-wallpaper.bundle.mjs` anywhere (e.g. `~/.dsh/plugins/`), register it by **absolute path** in the profile's `cordis.patch.yml`, then restart `dsh web`:
|
|
426
|
+
## Compatibility, limits and what it cannot do
|
|
362
427
|
|
|
363
|
-
|
|
364
|
-
# $DSH_HOME/profiles/<profile>/cordis.patch.yml
|
|
365
|
-
- insert:
|
|
366
|
-
- id: dsh-mpkg-wallpaper
|
|
367
|
-
name: /absolute/path/dsh-mpkg-wallpaper.bundle.mjs # ← points at the .mjs file itself
|
|
368
|
-
```
|
|
428
|
+
This section gathers what environment and what sizes work, and what is out of reach. Per-type boundaries live in the previous section; the web-wallpaper and Now playing cannot-do lists live in their own sections.
|
|
369
429
|
|
|
370
|
-
|
|
430
|
+
### Browser compatibility (measured reference)
|
|
371
431
|
|
|
372
|
-
|
|
|
432
|
+
| Browser | Rating | Notes |
|
|
373
433
|
|---|---|---|
|
|
374
|
-
|
|
|
375
|
-
|
|
|
376
|
-
|
|
|
377
|
-
|
|
|
378
|
-
| `ping.version` | `null` when the parent directory has no `package.json` (affects the version display only) | `new URL('../package.json', import.meta.url)` (`lib/index.js:1622`) |
|
|
379
|
-
| "Check for updates / one-click update" | Without a companion `package.json`, `update-check` returns 500 and `update-apply` writes next to/above the bundle ⇒ **not recommended under option 4** | `lib/index.js:1792-1860` |
|
|
380
|
-
| Uninstall | Delete that `.mjs` and its line in `cordis.patch.yml` | — |
|
|
381
|
-
|
|
382
|
-
> Conclusion: **option 4 is a degraded "host capability only" load** (handy offline, as an emergency path, or when reusing the routes from a non-DSH host); use options 1/2/3 for the full UI. The artifact is **not committed** (`dist/` is in `.gitignore`: it is a pure derivative of `lib/*.js`, two builds are byte-identical — section ② of `tools/bundle-equivalence-test.mjs`; generate it at release time and publish the hash).
|
|
383
|
-
|
|
384
|
-
### Uninstall
|
|
434
|
+
| Chrome / Chromium (desktop) | ⭐⭐⭐ strong | Most complete: best `backdrop-filter` and `color-mix`, working `iframe.muted`, muted autoplay allowed |
|
|
435
|
+
| Edge (desktop) | ⭐⭐⭐ strong | Video wallpapers use a **separate canvas path** (dodging Edge's floating toolbar, `lib/client.js:1464-1520`); some versions show a static first frame only |
|
|
436
|
+
| Firefox | ⭐⭐ medium | Everything works (`backdrop-filter` 103+, transcode fallback for unsupported codecs); three deductions — see the note below |
|
|
437
|
+
| Android WebView / mobile | ⭐⭐ weak-medium | Autoplay depends on the host WebView config; `getBattery` may be missing (guarded); for extreme combinations prefer a static image/GIF or turn blur off |
|
|
385
438
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
Leftover data (optional cleanup): browser `localStorage['dsh.mpkg-wallpaper.v2']` and the host directory `~/.dsh-mpkg-wallpaper/` (`settings.json`, `web-store.json`, `media-audio.json`, uploaded mpkg files, transcode cache, `diag-*.json`).
|
|
439
|
+
> **Firefox's three deductions**: ① `backdrop-filter` is slower than Chromium (several blurs at once drop frames on low-end machines); ② `iframe.muted` is unsupported ⇒ a web wallpaper with sound may have its first frame blocked by the autoplay policy; ③ `color-mix` needs 113+ (older versions fall back visually).
|
|
440
|
+
> The source degrades for each browser (rAF fallback when `requestVideoFrameCallback` is missing, guards around `ResizeObserver`/`getBattery`, `.catch` on every `play()`, `backdrop-filter` detected with `CSS.supports` and degraded to opaque).
|
|
389
441
|
|
|
390
|
-
|
|
442
|
+
### Without a Wallpaper Engine install (missing / non-Windows)
|
|
391
443
|
|
|
392
444
|
A "WE install" means the Steam edition of Wallpaper Engine (appid **431960**). The host's `locateWallpaperEngine()` (`lib/index.js:303-327`) searches in this order: Windows registry `HKCU\Software\Valve\Steam\SteamPath` → common Steam directories → non-Windows Steam directories (macOS `~/Library/Application Support/Steam`, Linux/Android `~/.local/share/Steam`, WSL `/mnt/c/...`) → every library's `steamapps/libraryfolders.vdf` containing 431960 → and only accepts the one where `<library>/steamapps/common/wallpaper_engine/wallpaper32.exe` exists. **If nothing is found it returns `null`** and everything falls back:
|
|
393
445
|
|
|
394
446
|
| Scenario | Real behaviour (with code location) |
|
|
395
447
|
|---|---|
|
|
396
|
-
| WE not installed (or `wallpaper32.exe` missing) | `
|
|
397
|
-
| Clicking "Scan local library" | Empty list plus the
|
|
398
|
-
| WE installed but
|
|
399
|
-
| Non-Windows / mobile | Same as "WE not installed" (the registry branch returns null
|
|
400
|
-
| WE playlists | Only when `installDir` exists and `config.json` parses; otherwise
|
|
401
|
-
| **Still available without WE** |
|
|
402
|
-
| Host half
|
|
403
|
-
| `ffmpeg` missing | `
|
|
448
|
+
| WE not installed (or `wallpaper32.exe` missing) | `steam-inventory` returns **200 `{ok:true, installDir:null, wallpapers:[]}`** — not an error, never a 500 (`lib/index.js:3120-3180`) |
|
|
449
|
+
| Clicking "Scan local library" | Empty list plus the "install not found" banner (`lib/client.js:8781`/`:11555`) and the empty-list hint. **The scan itself does not fail** |
|
|
450
|
+
| WE installed but no asset folders exist | Each root is checked individually ⇒ empty inventory, no banner; only the empty-list hint shows. **Not implemented**: no dedicated message |
|
|
451
|
+
| Non-Windows / mobile | Same as "WE not installed" (the registry branch returns null off win32); no side effects |
|
|
452
|
+
| WE playlists | Only when `installDir` exists and `config.json` parses; otherwise `rotSeeded:true` stops re-seeding (`lib/client.js:8782-8793`) |
|
|
453
|
+
| **Still available without WE** | Manual folders, direct import, URL/local files, scene extraction and diagnostics all work without WE — itemised below |
|
|
454
|
+
| Host half unavailable (option 4 missing / port closed) | The `/ping` probe fails ⇒ **browser-only mode** (`lib/client.js:10203`/`:11603`); assets above 600MB cannot be handled |
|
|
455
|
+
| `ffmpeg` missing | `ffmpeg-check` → **200 `{ok:true, found:false, …}`**; the panel shows "not installed" and only downloads after a click; directly decodable videos are never transcoded |
|
|
456
|
+
|
|
457
|
+
**Still available without WE**: ① choosing a folder manually (`/list-dirs` + `/custom-dir`); ② importing `.mpkg` directly (hybrid, no 600MB ceiling); ③ web/video wallpapers from a URL or local file; ④ scene extraction, audio lists, settings persistence and diag reporting.
|
|
404
458
|
|
|
405
459
|
> In one line: **no WE install = one convenience channel (automatic local-library discovery) is missing**; the plugin still works. Every degradation is "empty inventory + explicit copy + the manual folder/upload paths stay open" — nothing fails silently and nothing returns a 500.
|
|
406
460
|
|
|
@@ -422,8 +476,16 @@ The plugin offers two paths:
|
|
|
422
476
|
|
|
423
477
|
> The realistic route to full dynamics: **render externally to a video → use this plugin's video wallpaper feature** (record with the official WE client on Windows, with we-layerd on Linux, or with the Wallpaper Engine app on mobile).
|
|
424
478
|
|
|
479
|
+
### Rendering feasibility: why a browser cannot do it
|
|
480
|
+
|
|
481
|
+
- A complete scene (Live2D puppets included) can only be produced by the proprietary renderer: the Wallpaper Engine app's native library (embedded Chromium + proprietary puppet rendering). The open-source [we-layerd](https://github.com/Aromatic05/we-layerd) (Rust) bundles the official renderer but is **Linux Wayland only**
|
|
482
|
+
- There is no mature WE scene renderer for the browser — **regardless of the operating system, no browser can render Live2D scenes directly**; the official renderer `.so` is a closed binary and cannot be compiled to WASM without source
|
|
483
|
+
- The feasible path for this plugin: **external renderer iframe (preferred) + static-frame extraction + layer compositing + (Time Variation) mpkg-style slot switching**; when full dynamics are needed, use "render externally to a video → video wallpaper"
|
|
484
|
+
|
|
425
485
|
## Web wallpapers (experimental)
|
|
426
486
|
|
|
487
|
+
This section covers how web (HTML) wallpapers load, how they are isolated, which settings are wired, and what is explicitly out of reach.
|
|
488
|
+
|
|
427
489
|
- **Type detection is content-first, not declaration-first**: `detectWebWallpaperKind()` returns one of four states `web / scene / video / unknown`, and `general.type` in `project.json` is only a hint — a package that claims `web` but contains `scene.pkg` is treated as a **scene**, one that claims `scene`/`video` but only has `index.html` is treated as **web**, and `application/exe/app` always becomes `unknown/excluded-application` (`lib/web-wallpaper.js:163-207`)
|
|
428
490
|
- **Two load modes** (chosen in the confirm dialog, recorded in the URL so a refresh keeps it):
|
|
429
491
|
- **Sandbox mode (default, recommended)**: `sandbox="allow-scripts"` (**opaque origin**). The host injects the **WE API shim** at the very start of the entry HTML's `<head>`, gated by `?mpwshim=1`. Wallpaper scripts **cannot reach the host DOM / `localStorage` / cookies**; in-frame `fetch()` is cross-origin (`Origin: null`) and the host only answers CORS headers to the literal `null` (`lib/web-wallpaper.js:84`, `:529-537`)
|
|
@@ -441,6 +503,8 @@ The plugin offers two paths:
|
|
|
441
503
|
|
|
442
504
|
## Adjustable options and web-wallpaper settings wiring
|
|
443
505
|
|
|
506
|
+
This section covers what the plugin can read and change among a wallpaper's own properties.
|
|
507
|
+
|
|
444
508
|
- **mpkg wallpapers**: the project's own **adjustable options** are shown **read-only** in the collapsed "adjustable options" area of the *Wallpaper* tab (the browser only has the pre-rendered assets, so changing a value does not change the picture). They are there for comparison.
|
|
445
509
|
- **Web wallpapers (partly wired, Live2D-class)**: web wallpapers that ship `loadJson.json` / a `SettingModel` now expose their settings in the **same collapsed area**: resolution (2k/4k/8k, takes effect after a reload), language (whatever the wallpaper offers), background-music and voice volume (live), and switches such as showing touch areas or text boxes. Changes are written into the wallpaper iframe's storage (in sandbox mode through the facade → `/web-store`) and take effect after a reload.
|
|
446
510
|
- **Hiding the wallpaper's own settings panel**: these wallpapers carry a "settings" button in the top-right corner of the wallpaper that cannot be interacted with; the plugin hides it once the iframe loads so it does not cover the picture.
|
|
@@ -450,6 +514,8 @@ The plugin offers two paths:
|
|
|
450
514
|
|
|
451
515
|
## Performance and stability
|
|
452
516
|
|
|
517
|
+
This section lists the **optimisations already in place and the numbers they bought**, and doubles as the reference when something feels slow or memory-hungry.
|
|
518
|
+
|
|
453
519
|
- **mpkg header-only reads**: container parsing reads just the first 2MB (`lib/index.js:44`), so even an 834MB mpkg starts almost instantly
|
|
454
520
|
- **The audio inventory does not wait for the whole package**: a `scene.pkg` keeps its directory table at the front ⇒ only the table plus 16 magic bytes per candidate entry are read (audio payloads never enter memory). `/custom-scene-audio` and `/library-scene-audio` return `{count,tracks:[…],stats}` directly, and `/raw` supports **Range/206**. Measured: reading whole packages 1.7–379 ms ⇒ index reads 1.3–6.9 ms cold / 0.4–0.9 ms warm
|
|
455
521
|
- **Scene embedded-video probing is "index first"**: only the directory table plus candidate prefixes (`.tex` header and first mipmap record, the first 12 payload bytes for the `ftyp` box; when mip0 is LZ4 only the first sequence is decompressed), and anything uncertain falls back to reading the whole entry. Measured across 11 real packages: **2894 ms → 532 ms cold / 7 ms warm**; gate `node tools/scene-video-test.mjs`
|
|
@@ -461,19 +527,10 @@ The plugin offers two paths:
|
|
|
461
527
|
- **Transcode resource caps live in one place**: 12 artifacts / 512MB, concurrency 1, 30 s queue, 15 min per job, default downscale to 1920 wide, 1024MB memory admission (see the [historical ledger](#video-wallpaper-transcoding-verdict-and-resource-caps))
|
|
462
528
|
- **Weak-device degradation**: heavy composites (full-screen `backdrop-filter` plus streaming video) are throttled overall; for extreme combinations prefer Edge or a desktop browser
|
|
463
529
|
|
|
464
|
-
**🌐 Browser compatibility (measured reference)**
|
|
465
|
-
|
|
466
|
-
| Browser | Rating | Notes |
|
|
467
|
-
|---|---|---|
|
|
468
|
-
| Chrome / Chromium (desktop) | ⭐⭐⭐ strong | Most complete: best `backdrop-filter` and `color-mix`, working `iframe.muted`, muted autoplay allowed |
|
|
469
|
-
| Edge (desktop) | ⭐⭐⭐ strong | Video wallpapers take a **separate canvas render path** (to dodge Edge's floating toolbar, `lib/client.js:1464-1520`); some versions show only a static first frame (not blank, not a crash) |
|
|
470
|
-
| Firefox | ⭐⭐ medium | Everything is supported (`backdrop-filter` 103+, automatic transcode fallback for unsupported codecs); three deductions: `backdrop-filter` is slower than Chromium, `iframe.muted` is unsupported (a web wallpaper with sound may have its first frame blocked by the autoplay policy), `color-mix` needs 113+ |
|
|
471
|
-
| Android WebView / mobile | ⭐⭐ weak-medium | Autoplay policy depends on the host WebView configuration; `getBattery` may be missing (guarded and skipped); for extreme combinations prefer a static image/GIF or turn blur off |
|
|
472
|
-
|
|
473
|
-
> The source degrades for each browser (rAF fallback when `requestVideoFrameCallback` is missing, guards around `ResizeObserver`/`getBattery`, `.catch` on every `play()`, `backdrop-filter` detected with `CSS.supports` and degraded to opaque).
|
|
474
|
-
|
|
475
530
|
## Security notes
|
|
476
531
|
|
|
532
|
+
This section lists the plugin's network behaviour, data locations and isolation boundaries, for self-audit and compliance checks.
|
|
533
|
+
|
|
477
534
|
- **No passive outbound network traffic by default**: the plugin never reaches out to the network on its own; everyday playback only talks to the local DSH host (`127.0.0.1`). The exceptions are all **explicitly user-triggered**: check-for-updates/one-click update contact GitHub (`raw.githubusercontent.com`, `api.github.com`), and the ffmpeg download contacts GitHub Releases / an npm binary mirror. In addition, **0.8 s after the settings panel opens a silent version check runs once** (it only lights the badge — no dialog, no download, no upload). Network image URLs typed by the user and resources loaded by a web wallpaper itself are external requests too.
|
|
478
535
|
- **No sensitive content**: the source contains no paths, keys, tokens or personal information; `node tools/secret-scan-test.mjs` scans every tracked file (12 credential patterns, 3 local-path patterns) and must report 0 hits.
|
|
479
536
|
- **Web-wallpaper sandbox**: in the default mode the iframe is an opaque origin, so author scripts cannot read the host DOM / `localStorage` / cookies and cannot remove their own sandbox; frame↔parent traffic is `postMessage` only (op allow-list + parent-origin check). Cross-origin reads of wallpaper assets are allowed **only for `Origin: null`**.
|
|
@@ -484,6 +541,8 @@ The plugin offers two paths:
|
|
|
484
541
|
|
|
485
542
|
## Attribution and licence
|
|
486
543
|
|
|
544
|
+
This section covers this package's own licence, the third-party attribution ledger that ships with it, and the "GPL never enters the plugin" boundary.
|
|
545
|
+
|
|
487
546
|
- **This package is MIT** (`LICENSE`; the `license` field in `package.json` is `"MIT"`). Third-party provenance, clean-room records, per-item attribution and the "what was copied / why / what was not" ledger all live in **`THIRD-PARTY.md`** (**shipped with the package** — it carries the MIT attribution obligation).
|
|
488
547
|
- **The only vendored third-party code** is `lib/liquid-glass/**` (the liquid-glass renderer library): **moved out of the publish surface by P-127** (`files` negative patterns `!lib/liquid-glass/**` + `!lib/liquid-glass-bundle.js`). **Every file is still in the repository** and all 10 sha256 digests are still registered (`THIRD-PARTY.md` §1.3). The host's `/api/mpkg-wallpaper/lg/*` hosting route is still a live path (on a hit it really reads `lib/liquid-glass/<file>`); on an npm-installed copy it returns 404 by design.
|
|
489
548
|
- **Now playing component**: a **line-by-line port** of Bencho's "Now playing" (MIT) with upstream comments kept verbatim; the sidebar mount controller, self-drawn icons, token mapping and gates are written here (`THIRD-PARTY.md` §6).
|
|
@@ -497,9 +556,11 @@ The plugin offers two paths:
|
|
|
497
556
|
|
|
498
557
|
## File structure
|
|
499
558
|
|
|
500
|
-
|
|
559
|
+
This section is a file-by-file map of the repository: what enters the npm package, what stays in the repository only, and why.
|
|
560
|
+
|
|
561
|
+
```text
|
|
501
562
|
dsh-mpkg-wallpaper/
|
|
502
|
-
├── package.json # version 3.
|
|
563
|
+
├── package.json # version 3.9.0; dsh.bundle + dsh.client declarations; files allow-list = publish surface (15 files)
|
|
503
564
|
├── cordis.patch.yml # install declaration used by dsh plugin add
|
|
504
565
|
├── LICENSE # MIT
|
|
505
566
|
├── THIRD-PARTY.md # third-party provenance / clean-room records / licence attribution (shipped)
|
|
@@ -534,7 +595,9 @@ dsh-mpkg-wallpaper/
|
|
|
534
595
|
> Exclusions: `lib/liquid-glass/**`, `lib/liquid-glass-bundle.js` and `lib/**/*.bak*` are excluded by `files` negative patterns; `tools/`, `docs/`, `dist/` and `package-lock.json` are not in the `files` allow-list. Section ⑨ of `tools/integrity-check.mjs` asserts **both directions**: "not in the publish surface" and "must still be present in the repository".
|
|
535
596
|
> `lib/now-playing.js` / `lib/now-playing-math.js` use a **source + generated inline** shape: `tools/build-now-playing.mjs` inlines them byte-for-byte into the generated region of `lib/client.js`. Why this is necessary: the host serves the **one file** that `exports["./client"]` points at, so any relative `import/require` inside `lib/client.js` breaks module resolution in the browser (asserted by section ⑩ of `tools/integrity-check.mjs`). The price is a drift gate, so **two independent implementations** run permanently in step 2 of the gate.
|
|
536
597
|
|
|
537
|
-
## Gates
|
|
598
|
+
## Gates and guardrails
|
|
599
|
+
|
|
600
|
+
This section is **how a change proves it did not break anything**: four commands to run, plus what each of the 12 gate steps checks.
|
|
538
601
|
|
|
539
602
|
```sh
|
|
540
603
|
node tools/integrity-check.mjs # 72 passed / 0 failed: files present, metadata, allow-list, no local paths, client self-containment
|
|
@@ -545,6 +608,36 @@ node tools/style-scope-guard.mjs # the criterion is the last line "… OK /
|
|
|
545
608
|
|
|
546
609
|
The 12 steps of `tools/check.sh`: ① syntax ② panel smoke + P-66 + folder picker + wallpaper-layer visibility + persistence + NP (both drift gates, 83 assertions, plus the 82-assertion sound-wiring gate) ③ CSS matrix (all 512 combinations + 600 random + boundaries) ④ scene watchdog/debug params ⑤ sandbox and scene tokens + web-wallpaper shim + transcode resources ⑥ publish integrity self-check ⑦ audio-scan speed-up ⑧ scene video indexing ⑨ on-device replica A/B ⑩ better-sidebar compatibility ⑪ single-file bundle equivalence ⑫ style-scope guard + surface token namespace.
|
|
547
610
|
|
|
611
|
+
## What changed in this version
|
|
612
|
+
|
|
613
|
+
Current version = **3.9.0** in `package.json` (this release line: 3.8.0 → 3.8.1 → 3.8.2 → **3.9.0**). Release prerequisites, commands and rollback live in [`docs/RELEASE.md`](docs/RELEASE.md); this round's change list is in [`docs/RELEASE-READY-3.8.0.md`](docs/RELEASE-READY-3.8.0.md); the root causes and on-device readings for Now playing / wallpaper sound are in [`docs/NOW-PLAYING-DSH.md`](docs/NOW-PLAYING-DSH.md) §7.7.
|
|
614
|
+
|
|
615
|
+
- **Now playing is on by default and yields its slot**: the `npNowPlaying` setting is **on by default** (`DEFAULT_NP_NOW_PLAYING` in `lib/client.js`), still in the *Wallpaper* tab directly under the existing `mute` toggle (`toggleRow(t("npNowPlaying"), …)` in `lib/client.js`). Turning it off returns to zero injection (mechanism and criteria in [Now playing and wallpaper sound](#now-playing-and-wallpaper-sound)). Being on by default requires **yielding**: when another plugin has already injected an element into the same host slot we do not mount (or we retract) and leave a queryable state `data-mpw-np-yield` (`occupantOf()` in `lib/now-playing.js`; judged both before and after mounting, and it comes back once the occupant leaves). The component itself is a **line-by-line port of Bencho's "Now playing" (MIT)** with the upstream comments kept verbatim (attribution in `THIRD-PARTY.md` §6); pure math and component are split into `lib/now-playing-math.js` / `lib/now-playing.js` and **inlined byte-for-byte** into the `MPW-NP-GEN-START/END` region of `lib/client.js` by `tools/build-now-playing.mjs`, guarded by two independent drift gates. Mounting and collapse criteria: see [Now playing and wallpaper sound](#now-playing-and-wallpaper-sound).
|
|
616
|
+
- **Wallpaper sound is actually wired up**: the data source only accepts the media element that is **really playing right now** (the hidden shell `#mpw-bgVideo` exists in the DOM under **every** wallpaper type, and the old implementation treated the first selector match as "the current media") ⇒ a video wallpaper's play/pause/mute land on the real element (the **`mute` switch now really reaches the element — unmuting really produces sound**; the old code hard-coded `video.muted = true` and never assigned it from the setting again); audio files that **really exist** in the wallpaper folder play through our own `<audio>` (the scope accepts `mpkgKey="custom|<folder>"`; the old code only accepted `folderName`, so a web wallpaper in a custom folder always built a library route and got a 404) ⇒ **previous/next follow the track-list order and wrap around**, instead of "restart"; for a web wallpaper's in-frame sound only **mute** is a real channel (`canPlay=false` is reported honestly — we do not pretend we can pause in-frame WebAudio); while our player is playing, the frame is force-muted so the same track cannot play twice.
|
|
617
|
+
- **The card is no longer clipped in floating mode**: fit-scaling now measures **our own container** (the old code measured `[class*="sidebarCol"]`, and that class name appears more than once on a real machine — it measured 280 while our container was 256), `.mpw_np` is pinned to `math.W` with the overflow shared evenly, and the web-wallpaper branch now applies the `data-mpw-float` gate and calls `applyNowPlaying()` (the old branch returned early, so after switching to a web wallpaper the widget was not mounted at all).
|
|
618
|
+
- **Gate expansion**: `tools/now-playing-test.mjs` now reports **83 passed / 0 failed** (including 7 mutations); new `tools/np-media-test.mjs` (track-list scope / data-source decision / playback target / mute target / yielding / mark / the web path not skipping apply / card geometry — **82 passed / 0 failed**, including **12 mutation self-proofs**) is registered in step 2 of `tools/check.sh`; new on-device probe `tools/np-media-live-probe.mjs` (`:3080` + headless Firefox; 16 PASS / 22 FAIL before the fix → 45 PASS / 0 FAIL after, not part of the standing gate).
|
|
619
|
+
- **Several on-device fixes**: Now playing no longer mistakes the expanded state for collapsed on the frame right after slot rendering (so the widget no longer disappears after a refresh) — the criterion is now **physical width first** with threshold `NP_COLLAPSE_MAX_W = 96`, and when an anchor appears late it watches the document and mounts itself; the play/pause quads are now driven by a **tween of the playback state itself** (they used to be driven by expand progress, so the collapsed state always drew a triangle and the expanded state always drew the two bars); the header-frost `ReferenceError` and the transparent right-side timeline rail (see [Historical ledger](#historical-ledger)); the `lgCss` block that never executed because of a TDZ error; `sessionFollow` that had a toggle but no reader; the folder picker scrolling back to the top.
|
|
620
|
+
- **3.9.0: on-device wallpaper-pipeline batch + audio semantics settled** (six user-reported bugs plus three audio issues) —
|
|
621
|
+
(1) the settings panel squeezed into the sidebar (`backdrop-filter` made the sidebar the containing block of the `fixed` overlay;
|
|
622
|
+
on-device `{x:320,w:800} → {x:13,w:254}`, reversible); (2) "Clear wallpaper" not clearing (`undefined` means "do not overwrite" in
|
|
623
|
+
invariant I, and the sticky guard brought `webUrl` back), a leftover `webUrl` arming first on switch (black screen + broken image),
|
|
624
|
+
a blank preview for web wallpapers, and an unavailable source being rendered as artwork (now `data-mpw-bg-error` + plain-language panel copy);
|
|
625
|
+
(3) sandbox wallpapers blocked by browser policy were invisible to the parent page (shim capability self-report `probeCaps` + one-shot
|
|
626
|
+
compat downgrade on **policy-class** errors only, never on the author's own bugs); (4) the card's pause could not silence in-frame audio
|
|
627
|
+
(`npFrameSoundBlocked()`); (5) no mute on tab switch (`powPauseHidden` **now defaults on**, migrating only untouched profiles, covering our
|
|
628
|
+
own `<audio>` and resuming by prior state on `pageshow`); A interactive-sound classification (`mpwClassifyWebAudio` + `?npvoice=keep|drop`;
|
|
629
|
+
on the corpus the list keeps only `BGM.wav`); B link switch off = **sound-only control** (pause/play mutes/unmutes that one track, the picture
|
|
630
|
+
keeps running); C "it makes a noise again after a while" on tab switch (single gate `mpwHiddenAudioBlock()` on every start path);
|
|
631
|
+
**NP-5 pause intent persistence** (new key `npPaused`, written only by explicit user action; a refresh must **not** resume — the on-device probe
|
|
632
|
+
`tools/np-pause-persist-live-probe.mjs` reports 11 PASS / 0 FAIL: 59 samples × 500 ms all `paused` after a reload, zero `play()` calls);
|
|
633
|
+
**audio audit** (hooks `play`/`volume`/`muted`/`new Audio`/`AudioContext`, bounded ring ≤200, suspicious transitions POST to `/diag`; `?npaudit=0` disables);
|
|
634
|
+
**host body-limit semantics**: both POST receivers now answer **413 + JSON explanation** instead of destroying the connection first
|
|
635
|
+
(the old behaviour showed clients nothing but ECONNRESET). Root-cause chains, on-device readings and the 10-item honest list live in
|
|
636
|
+
[`docs/WALLPAPER-LIFECYCLE.md`](docs/WALLPAPER-LIFECYCLE.md); the release record is in [`docs/RELEASE.md`](docs/RELEASE.md).
|
|
637
|
+
- **3.8.1 / 3.8.2: the second on-device Now playing batch (NP-4)** — start-up order (the old code unmuted *before* `play()`, i.e. an audible autoplay attempt the browser refuses and the code swallowed), being stopped by browser policy (a muted start resolves, then unmuting gets the element stopped outright), a **new volume level** (0..100, applied to the real element) and a **draggable position (seek)**, a **new "Play/pause also controls the wallpaper" switch (`npLinkWallpaper`, default on = byte-identical to the previous behaviour)**, a larger floating card, and an honest "can it play" classification for track lists. Deliverable contract: **sound is guaranteed after the first user gesture** (when policy stops the element it falls back to muted to keep the picture and records `window.__mpwNpSoundBlocked`). See the 3.8.1/3.8.2 records in [`docs/RELEASE.md`](docs/RELEASE.md) and [`docs/NOW-PLAYING-DSH.md`](docs/NOW-PLAYING-DSH.md) §7.8.
|
|
638
|
+
- **Capabilities from the previous round still on the same publish surface**: web-wallpaper rendering / API coverage (in-frame storage facade + host `/web-store`, master volume, source-level `file:///` rewriting, CSP injection skip, `/media-audio`) and web-wallpaper touch (`op:'touch'` with real `TouchEvent`s); the **host-side system media session adapter** `lib/media-session.js` (MPRIS / SMTC, **implemented but not wired up yet**, below).
|
|
639
|
+
- **Gates and guardrails**: CSS matrix, style-scope guard, surface-token namespace equivalence, switch-wiring audit and the second-scale pre-commit hook (see the [historical ledger](#historical-ledger) and [Gates](#gates-and-guardrails)).
|
|
640
|
+
|
|
548
641
|
## Historical ledger
|
|
549
642
|
|
|
550
643
|
The sections below are **evidence that has already been booked** (incident, root cause, criterion, rollback switch). Their factual content is preserved rather than rewritten: they explain *why the current implementation looks the way it does*.
|
|
@@ -651,14 +744,9 @@ MPW_SKIP_PRECOMMIT=1 git commit -m … # one-off bypass (script-level explicit
|
|
|
651
744
|
|
|
652
745
|
## Bug reports
|
|
653
746
|
|
|
654
|
-
|
|
747
|
+
Attach these and an issue can usually be diagnosed in one round:
|
|
748
|
+
|
|
655
749
|
- The **original .mpkg or workshop folder** (required to reproduce)
|
|
656
750
|
- The result of one "one-click diagnostics report" (*Other* tab → one-click report; a JSON file is downloaded automatically when the host is unavailable)
|
|
657
751
|
- Browser console output (F12 → Console), if any
|
|
658
752
|
- Your DSH version and platform (Windows / Linux / mobile)
|
|
659
|
-
|
|
660
|
-
## Rendering feasibility research
|
|
661
|
-
|
|
662
|
-
- A complete scene (Live2D puppets included) can only be produced by the proprietary renderer: the Wallpaper Engine app's native library (embedded Chromium + proprietary puppet rendering). The open-source [we-layerd](https://github.com/Aromatic05/we-layerd) (Rust) bundles the official renderer but is **Linux Wayland only**
|
|
663
|
-
- There is no mature WE scene renderer for the browser — **regardless of the operating system, no browser can render Live2D scenes directly**; the official renderer `.so` is a closed binary and cannot be compiled to WASM without source
|
|
664
|
-
- The feasible path for this plugin: **external renderer iframe (preferred) + static-frame extraction + layer compositing + (Time Variation) mpkg-style slot switching**; when full dynamics are needed, use "render externally to a video → video wallpaper"
|