DeepSeek Harness Plugin

XHR666/dsh-mpkg-wallpaper

Stars ★ 6 Downloads (30d) 5,001 Category UI Enhancements Added 2026-08-18 npm dsh-mpkg-wallpaper

Load Wallpaper Engine .mpkg files as the DSH web background: embedded video, time-of-day switching, unified frosted blur, local wallpaper library & rotation.

Install

# from npm (prebuilt)

dsh plugin --profile web add dsh-mpkg-wallpaper

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:XHR666/dsh-mpkg-wallpaper

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).

README

中文 | English

Adds background wallpapers to the 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.

Version scope: this document describes the implementation shipped as 3.10.1 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).


Download · Install

The plugin is published on npm (dsh-mpkg-wallpaper). Four ways to load it — pick one from this table, then read its section:

Option Who it is for How to update Client UI
1 dsh plugin add (recommended) the default choice; the market shows "installed" dsh plugin --profile web update … full
2 pnpm manual install you manage the profile's dependency table yourself same (through the dependency table) full
3 Git clone developers / offline / you want to edit the code git pull full
4 single-file bundle offline emergencies; reusing the routes from a non-DSH host regenerate and replace that .mjs host half only

Option 1: dsh plugin add (recommended, market-recognized)

dsh plugin --profile web add dsh-mpkg-wallpaper
# restart dsh web, then Ctrl+F5 in the browser

Option 2: pnpm manual install

pnpm --dir $DSH_HOME/profiles/<profile> add dsh-mpkg-wallpaper
# restart dsh web, then Ctrl+F5

Same source as option 1, just without the dsh plugin wrapper.

Option 3: Git clone (developers / offline)

git clone https://github.com/XHR666/dsh-mpkg-wallpaper.git $DSH_HOME/profiles/<profile>/node_modules/dsh-mpkg-wallpaper
# then register in the profile's cordis.patch.yml:
#   - insert:
#       - id: dsh-mpkg-wallpaper
#         name: dsh-mpkg-wallpaper
# restart to take effect

Option 3 writes no dependency entry ⇒ the market does not show "installed" (display only, functionality unaffected).

Option 4: single-file bundle (offline / drop-in; host half only)

Inline the host half into a self-contained ESM and register that:

cd /path/to/dsh-mpkg-wallpaper
node tools/build-bundle.mjs          # output: dist/dsh-mpkg-wallpaper.bundle.mjs (measured 449,671 B / 439.1KB; the gate output is authoritative)
node tools/build-bundle.mjs --check  # cross-check against the source: export surface / route table / ping JSON shape (20 assertions)
node tools/bundle-equivalence-test.mjs  # the fuller equivalence gate (38 assertions; gate step 11)

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:

# $DSH_HOME/profiles/<profile>/cordis.patch.yml
- insert:
    - id: dsh-mpkg-wallpaper
      name: /absolute/path/dsh-mpkg-wallpaper.bundle.mjs   # ← points at the .mjs file itself

What this path loads and what it does not (code and gate facts):

Item Option 4 behaviour Evidence
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]
/api/mpkg-wallpaper/ping {ok, version, betterSidebar, betterSidebarVersion} key set identical to the source build-bundle.mjs --check
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
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)
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)
"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
Uninstall Delete that .mjs and its line in cordis.patch.yml —

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).

Update

# options 1 / 2: through npm's latest tag
dsh plugin --profile web update dsh-mpkg-wallpaper

# option 3: inside the clone
git pull

# option 4: regenerate and replace that .mjs
node tools/build-bundle.mjs

Every route ends the same way: restart dsh web, then press Ctrl+F5.

Uninstall

Options 1/2/3: dsh plugin --profile web remove dsh-mpkg-wallpaper. Option 4: delete the .mjs and its line in cordis.patch.yml. 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).

30-second quick start

The shortest path from installed to wallpaper on screen — three steps.

  1. Install and restart: pick any option in the previous section, restart dsh web, press Ctrl+F5.
  2. Open the panel: left sidebar → Settings → MPKG Wallpaper.
  3. Pick a wallpaper, any one of:
    • drop in an .mpkg file (video wallpapers play directly; scene wallpapers use the static frame / layer composite)
    • choose a local image/video, or paste an image URL
    • Custom local wallpaper folder: point it at any folder (the Steam steamapps/workshop/content/431960 root works — every subfolder counts as one wallpaper)

The defaults are already usable: master switch on, hybrid mode on, unified blur on (30px), Now playing mounted in the left sidebar. 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%).

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.

Core features

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.

📦 Wallpaper sources

  • 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
  • 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
  • Video: .mp4/.webm/.mov/.m4v play directly; Web: HTML loads in a sandboxed iframe (with a risk pre-scan); Image/animation/link: local images or URLs (including data:image)
  • Custom folder: any folder; .mpkg files, workshop subfolders, images, videos and scene.pkg can be mixed freely

⏰ Time Variation wallpapers

  • Detects the WE time-variation properties (morningtime / daytime / dusktime / nighttime / timevarying, default hours 5/8/17/20, lib/client.js:10419-10423)
  • Lazy loading: only the current slot is extracted (tens of MB per slot); other slots are read when you switch, so importing every slot at once cannot OOM
  • Manual slot lock: slot buttons appear only for slots that actually exist inside the container (lib/client.js:12797-12813), stored in timeOverride; "Auto" returns to time-based switching
  • No cross-talk: switching wallpapers clears the previous wallpaper's slot cache

🌊 Full-screen blur (frost) system

  • Unified blur: one slider drives the wallpaper's blur plus the sidebar/title-bar frost thickness; whether the chat area and the new-chat button follow is controlled separately
  • UI blur (independent switch + amount each): dialogs (generic centre windows + chat input), settings panel, download/confirm popups, popovers (menus/dropdowns/tooltips), mask (full-screen backdrop), left sidebar frost
  • Show wallpaper: left sidebar / title bar / right sidebar dock are independent, and the title-bar frost radius can be set separately

🎨 Theme colours and glass surfaces (Aqua experiments default to off)

  • Theme colour (themeColor): colour picker + presets, driving the sidebar/title bar/new-chat button/settings dialog base colour; Accent (accent) drives the brand interaction colour (buttons/sliders/selection/links/send button)
  • Panel colours match wallpaper (aquaTint): samples the wallpaper's dominant colour (video/GIF refresh every 2 s); plus unified fog (full-screen tinted mask), adaptive text colour + blue cleanup, dark-background text readability, task list frost
  • Liquid glass (CSS/SVG): lgCss + refraction amount on the composer / left sidebar / title bar; the WebGL runtime in lib/liquid-glass/ is not part of the build (see File structure)

🧩 dsh-better-sidebar compatibility (shown once that plugin is detected)

  • When installed, the Other tab gains a compatibility section: master switch bsCompat (default on) plus bsFloat (floating two-layer fix: 14px rounded shell + transparent inner layer + zero margin + the resize strip moved inside the panel) / bsFont / bsReveal + bsRevealAlpha / bsAqua
  • The host /ping returns {ok, version, betterSidebar, betterSidebarVersion}; the client writes body[data-mpw-bs-version] and version-specific rules are gated with [data-mpw-bs-version^="…"] (lib/index.js:1626-1643, lib/client.js:255-269)
  • Details in docs/BETTER-SIDEBAR-COMPAT.md and docs/BETTER-SIDEBAR-DOM-CONTRACT-0.19.1.md; regression: node tools/better-sidebar-compat-test.mjs

⏯️ Playback control and power saving

  • Video/web wallpapers can be paused/played from the Wallpaper tab; the button reflects the real playback state, and changing unrelated settings does not restart playback (the video.src equality test was fixed)
  • Three power-saving switches: pause when the page is hidden/switched away, pause on window blur, pause on battery (getBattery; silently skipped when unavailable). Any one of them pauses, all must recover to resume, and they share one gating model with manual pause

🚀 Large-file hybrid mode (hybrid, on by default)

  • The mpkg is streamed to the DSH host → stored on disk → played back over HTTP Range (lib/index.js:1688-1728, :1730-1790); >600MB works because the bytes never sit in memory. Turning it off falls back to browser-only mode (600MB ceiling)

🖼️ Local library and rotation

  • Steam auto-discovery + custom folder (cross-platform folder picker); WE playlists (general.playlists in config.json) are imported as rotation lists
  • Previous/next switching and scheduled rotation (rotate + rotateMin, 1–120 minutes); ticking items does not make the list jump to the top

🛡️ Safety and coexistence

  • Conflict detection: other wallpaper/theme plugins disable this feature automatically (it can be forced back on, which writes forceEnabled)
  • .exe/application wallpapers are excluded outright (lib/web-wallpaper.js:101, :199); custom folders only read media files; host routes validate against path traversal; web wallpapers are isolated in a sandboxed iframe

💾 Backup, restore and settings persistence

  • Backup & restore in the Other tab exports appearance settings as shareable JSON (BACKUP_FIELDS, lib/client.js:11979-11993) and restores them on import
  • Besides browser localStorage (key dsh.mpkg-wallpaper.v2, lib/client.js:48), settings are also stored on the host as ~/.dsh-mpkg-wallpaper/settings.json, so they survive port changes and cleared browser data

Supported types and boundaries

This section answers "does my material work, and how well"; the boundary list under the table explains why some things are out of reach.

Type Status What it can / cannot control
mpkg (video) ✅ Full Embedded mp4 / video textures play directly; mute, speed, pause, blur/zoom/brightness all adjustable
mpkg (scene) 🟡 Compromise Static frame / layer composite / embedded video slots; Live2D puppets, shaders and scripts are out of reach
Time Variation ✅ Multi-slot Automatic switching + manual lock; only the current slot is extracted
Video (mp4/webm/mov/m4v) ✅ Full Plays directly; fps/resolution caps require ffmpeg transcoding
Web (HTML) 🟡 Experimental Sandboxed iframe + WE API shim; Live2D-class settings are editable; external-SDK / heavily interactive ones are not adapted
scene.pkg loose folder 🟡 Compromise Same as mpkg scene wallpapers
preview.gif / image / animation ✅ Full Falls back to the author's preview animation when a scene has nothing better (lib/client.js:887, :12272)
Application (exe) ❌ Excluded Content detection yields unknown/excluded-application; never read, never executed (lib/web-wallpaper.js:199)
Custom folder (mixed) ✅ Full mpkg files and workshop subfolders mixed; bounded scan (depth ≤4, ≤4000 entries, lib/web-wallpaper.js:213-232)

Hard boundaries (facts about the current implementation, not "later" items):

  • Full dynamic reconstruction of scene wallpapers is impossible — the MDL puppet skeleton has no public format documentation and shaders/scripts have no Web runtime (see Scene adaptation)
  • The pure-Scene renderer selector is not wired up: sceneRender (webgl/static/elysia) in the Other tab has buttons and writes a value, but nothing in the repository reads it. The real path is decided by renderer availability (online → :8899 iframe, offline → static-frame fallback, lib/client.js:3133-3138)
  • 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)
  • 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

Settings reference (7 tabs)

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).

1. Background source (source)

Label Key Default Purpose Off / rollback
Enable mpkg background enabled on Master switch; off applies no background at all off
Upload to dsh for streaming playback hybrid on Large-file hybrid mode (host upload → disk → Range playback, no 600MB ceiling) off = browser-only mode
mpkg / image / video file / image URL — — File pickers and URL input (http(s) or data:image) "Clear wallpaper"
Custom local wallpaper folder customDirPath empty Any folder; can point at the workshop root, every subfolder becomes one wallpaper clear the field
Local wallpaper library (Steam scan) — — Scans the WE install and Workshop, importing wallpapers and config.json playlists rescan replaces it
Rotation rotate / rotateMin off / 5 min Switches to the next wallpaper on a timer (1–120 minutes) off
Time slot timeOverride auto Manually locks morning/day/dusk/night; buttons list only slots that really exist "Auto"
Current wallpaper card — — Preview, display name, container file name, type; pause/play, refresh, clear —

2. Wallpaper (wallpaper)

Contains the Wallpaper picture and Power saving sub-sections.

Label Key Default Purpose Off / rollback
Mute (web wallpapers) mute on Web-wallpaper audio; off lets the wallpaper make sound off
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
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
Web frame mode (web wallpapers) webFrameMode auto Three tiers for the web-wallpaper iframe: auto = sandboxed first, and when the browser blocks it / the WE API shim never reports it degrades inside the sandbox only (reloads without the shim marker, still opaque-origin) and never auto-upgrades to same-origin; sandbox = forced isolation, no automatic degrade (the reason is written to the status line); compat = same-origin compat tier (no shim marker, only reachable when the user picks it explicitly). One decision table only (lib/web-wallpaper.js); status in window.__mpwWebFrame and #mpw-bgWrap[data-mpw-webframe-mode|-reason|-attr|-fallback|-autocompat|-degraded], ledger in window.__mpwWebFrameGuard (see docs/WEB-WALLPAPER.md §3.0) pick auto; ?webframe=auto|sandbox|compat overrides for the current page
Auto-degrade to compat (high risk) webFrameAutoCompat off Risk rollback: when on, auto mode may switch to compat after the browser blocks the wallpaper — i.e. that third-party wallpaper runs same-origin with DSH (can read the host UI/local storage and call plugin routes with cookies). While off it only records the refusal and tells you to switch manually. Gate: tools/web-frame-origin-guard-test.mjs (see docs/SECURITY-ROUTES.md F5) off
Renderer upstream allowlist sceneUrlWhitelist empty Cross-origin rollback: sceneRendererUrl/sceneExtUrl only accept http(s) loopback (127.0.0.1/localhost/::1) and "same-host literal IPs" by default; a cross-origin upstream must have its origin listed here (comma/space separated) or it is refused and falls back to 127.0.0.1:8902 (recorded, never silent). ⚠ A listed upstream is also allowed to receive the scene token st (see docs/SECURITY-ROUTES.md F6) clear it = loopback only
Horizontal flip (mirror) flipX off Mirrors the wallpaper horizontally off
Vertical flip (mirror) flipY off Mirrors the wallpaper vertically off
Decode fps cap fpsCap unlimited Host ffmpeg frame extraction when the source exceeds the cap (24/30/48/60) "unlimited"
Resolution cap resMax source resolution ffmpeg downscaling (720p/1080p/2K, aspect kept) "source resolution"
Playback speed playbackRate 1x 0.5–2x (steps 0.5/0.75/1/1.25/1.5/2) 1x
ffmpeg status — — Shows system/cached/env source; download when missing, uninstall the cached copy only (never touches the system one) —
Adjustable options (collapsed) propEdits empty Editable and applied live: mpkg scene wallpapers use the P-203 cross-origin props channel (mpw-user-props, confirmed by a renderer receipt; see docs/WE-USER-PROPS.md §3); web wallpapers use the same-origin shim channel (resolution/language/volume, below) per-wallpaper reset
Frosted blur blur 12px Wallpaper-layer blur (0–40) 0
Lens zoom zoom 100% 10–2000% 100%
Brightness brightness 100% 50–150% filter 100%
Lens position (pan) lensX / lensY 0 / 0 Horizontal/vertical pan, ±2000 each 0
Power save · pause when hidden powPauseHidden off visibilitychange off
Power save · pause on blur powPauseBlur off blur/focus off
Power save · pause on battery powPauseBattery off getBattery; silently skipped when unavailable off

3. Appearance (appearance)

Contains the Show wallpaper sub-section.

Label Key Default Purpose Off / rollback
Floating cards float off Left sidebar/title bar become floating cards (radius + shadow + wallpaper showing through) off
Theme colour themeColor empty Base tint of sidebar/title bar/new-chat/settings dialog (picker + presets) empty = disabled
Panel colors match wallpaper aquaTint off Samples the wallpaper's dominant colour for panel bases (video/GIF refresh every 2 s) off = use the picker
Accent accent empty Brand interaction colour (buttons/sliders/selection/links/send) empty = DSH default
Mask custom colour aquaColor empty Custom colour for unified fog / panel tint empty = grey
Custom grey text colour fontColorGray off Grey text uses a custom colour (fontColorGrayColor) off
Show wallpaper in left sidebar sidebar on off = solid opaque sidebar off
Left sidebar frost sidebarBlur off backdrop-filter on the sidebar itself; removed automatically while popups are open off (requires sidebar reveal)
Left sidebar frost amount sidebarBlurAmount 14px 0–40; taken over while unified blur is on —
Show wallpaper behind the title bar headerBg on off = plain white title bar off
Frost the title bar headerBlur on Taken over while unified blur is on off
Title bar frost amount headerBlurAmount 0% Frost thickness 0–100% (default 0 = transparent) 0
Set title bar frost separately headerFrostOwn off on = headerFrostAmount overrides the frost radius off
Title bar frost strength headerFrostAmount 30px 0–60 0
Right sidebar / dock blur rightSidebarBlur on The DSH right sidebar and bottom dock off
Blur amount / surface opacity rightSidebarBlurAmount / rightSidebarAlpha 14px / 45% 0–40 / 0–100% —

4. Surface unify (unify)

Label Key Default Purpose Off / rollback
Unify blur unifyTint on One slider drives the whole-screen blur; takes over sidebar/title-bar/right-sidebar frost off
Full-screen blur degree unifyAmount 30px 0–40 (drives the wallpaper-layer blur) —
Left sidebar / title-bar opacity sidebarAlpha 35% Frost thickness 0–100% —
Chat follows full-screen blur chatFollow on off = the chat area is driven by the Frosted blur slider off
New-chat button follows panel opacity sessionFollow on off = back to the host's original colour off
Unified fog (full-screen mask) aquaMask off All surfaces share one fog colour (moved here from the Aqua section) off
Unified fog strength aquaMaskAlpha 82% 0–100% —

5. UI blur (blur)

Label Key Default Amount key / default Off
Blur dialogs dialogBlur on dialogAmount 14px off
Blur settings panel settingsBlur on settingsAmount 14px off
Blur download/confirm popups confirmBlur on confirmAmount 12px off
Blur popovers popoverBlur on popoverAmount 10px; plus popoverAlpha 94% surface opacity off
Blur mask (full-screen backdrop) maskBlur on maskAmount 8px off

6. Other (other)

Label Key Default Purpose Off / rollback
Light sharpening sharp on Improves low-quality GIFs; turn it off if animations stutter off
Deep diving background box thinkBg off on = the thinking state gets a blurred background box off
Task list frost todoBlur off Blurs todo-card backgrounds off
Third-party UI radius compat roundCompat off Radius compatibility for third-party plugins off
Adaptive text color + blue cleanup aquaInk off Text colour follows mask luminance + brand colours are unified (aquaInkColor) off
Dark-background text readability aquaTextEnhance off Two-colour text outline (an approximation) off
Pure-Scene renderer selection sceneRender — ⚠ Has buttons and a write path, but no reader (not wired up) —
Scene render reporting (troubleshooting) sceneReport on The wallpaper writes renderer state into reports/ every 10 s off
Scene first-frame watchdog sceneWatchdog on Falls back to the static frame when no frame arrives in time; sceneWatchdogSecs 8 s (3–30) off
Retry renderer / renderer debug params / extension hook URL sceneDebugParams / sceneExtUrl empty Allow-listed pass-through (ln/eye/audit/isolate/parallax/…); the extension hook is appended as extbase clear / clear all
One-click diagnostics report — — Collects subsystem state → POST /diag; downloads a JSON file when the host is unavailable —
Diagnostics switch cheat sheet (renderer) — — The 10 common renderer flags + "copy" for the URL fragment —
better-sidebar compatibility bsCompat / bsFloat / bsFont / bsReveal / bsRevealAlpha / bsAqua on / off / off / off / 62% / off See above; shown only when better-sidebar is detected master off = none apply
Backup & restore — — Exports/imports the BACKUP_FIELDS allow-list (current wallpaper and scan folders excluded) —
Restore all defaults / feedback / check for updates — — Resets appearance values; one-click update pulls the latest code from GitHub —

7. Liquid Glass (test) (liquid)

Label Key Default Purpose Off / rollback
Test mode master switch lgTest off Keeps only wallpaper + floating + layout, disabling every appearance feature off
Liquid glass (CSS) lgCss off Pure CSS/SVG refraction + specular edges (no WebGL context, can run alongside scene wallpapers) off / URL ?lgcss=off
Refraction lgCssAmount 14px 0–40 (0 = plain blur) —
Composer / left sidebar / title bar liquid glass lgComposer / lgSidebar / lgHeader off Each selects its own target (JS applies a [data-mpw-lg-css] marker) off
Standalone demo page (port 3081) — — The WebGL2 demo under tools/liquid-demo/ (a separate service; it does not affect the plugin) —

Keys without a panel control

These keys exist and participate in the logic but have no widget on the settings page; they are reachable only through a backup import, by editing localStorage directly, or via URL parameters (lib/client.js:101-232 holds every default; tools/switch-wiring-test.mjs:41-64 is the "runtime only" allow-list with the reason for each entry).

Key Default Notes
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
bsAlpha off better-sidebar panels follow the theme base colour (CSS reads it, no widget)
bsBottomAvoid off A settled deliberate no-op (alignment belongs to better-sidebar's ResizeObserver)
newStyle off Only changes settings-page widget looks; never enters buildCss
forceEnabled off Runtime priority flag for forcing the feature back on past conflict detection
opacity 82 The "panel opacity" slider was removed (unified blur uses sidebarAlpha); the value is still read
aquaTintStrength 45 How much of the wallpaper colour is mixed into panel tints; read at runtime
glassColor / glassAlpha empty / 12 Early WebGL liquid-glass leftovers: backup/restore only, no widget and no reader
webInteraction pointer Web-wallpaper interaction mode (off/pointer/full); use ?mpwinteract=… or write the stored settings
sceneRendererUrl http://127.0.0.1:8902/webloader/ Scene-renderer address, overridable (default = the one-stop :8902 bench serving /webloader/ itself; the old default was :8899, changed 2026-09-25)
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
glassWindow — Retired and deleted (2026-09-19): no widget, no reader; covered by settingsBlur + dialogBlur/popoverBlur

Now playing and wallpaper sound

This section covers where the sidebar player mounts, what it shows and what it can control — its source is the wallpaper's own sound.

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.

  • 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).
  • 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.
  • 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).
  • 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.
  • Data sources (npResolveMedia: we only report what we actually know):
Current wallpaper What NP shows What it can control
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
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
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)
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
  • 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.
  • "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).
  • 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).
  • 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).
  • 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.

System media session (MPRIS / SMTC)

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.

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).

Capability matrix

Platform Channel Adapters (by priority) Metadata / state / position / artwork Control
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
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)
macOS — — ❌ Not implemented: unsupported-platform, 0 commands ❌
Anything else / no adapter — none ❌ not-installed ❌
  • 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).
  • 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.
  • 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).
  • 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).
  • Zero dependencies: only node:child_process; package.json gained no dependency at all.
  • Environment variables: MPW_MEDIA_ADAPTER (pin an adapter), MPW_MEDIA_PLAYER (pin a player name), MPW_MEDIA_TIMEOUT_MS.

⚠ 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 §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.

Diagnostics and troubleshooting

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.

Plugin side (this plugin's own switches)

  • 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).
  • Automatic background reporting: the switch is localStorage['mpwdiag']='1' (not a URL parameter — DSH's auth 303 strips the query, lib/client.js:4953-4957); after a reload it reports once 6 seconds in (≤6 per session), and window.onerror uses the same collector.
  • Plugin-side URL escape hatches (effective after a refresh, settings untouched):
Parameter Effect
?hdrfrost=legacy|off Title-bar frost: old gate / fully off
?hdrblur=pseudo|element Frost carrier: pseudo-element / element layer, for A/B
?railink=off Disables the contrast compensation on the right-side timeline rail
?sbfill=wide Restores the old global sidebar fill override
?lgcss=off One-flag rollback of liquid glass
?mpwtranscode=legacy|aggressive Video transcoding: old behaviour / probe even past the user's caps
?mpwinteract=1|on (auto-arm) / off|0|pointer|full (mode only, no auto-arm) Web-wallpaper interaction
?mpwstore=0|mem Disable the in-frame storage facade / keep it in memory without persisting
?mpwpersist=legacy Settings persistence back to the old behaviour
?bgwrapfix=legacy, ?hdrfrostwatch=off Wallpaper-layer visibility / frost watchdog
?hiddengate=legacy Whole hidden-gate family back to the old behaviour (no more blocking our autoplay/retries while the page is hidden, frozen or background-reloaded; for A/B and emergencies. Default = never start playback while hidden, stop every sound surface we own when hidden, resume per policy on return; see docs/WALLPAPER-LIFECYCLE.md §6.4)

Renderer-side diagnostics flags (pure Scene wallpapers)

Two different numbers — do not mix them up:

  • The main table holds 158 flags, in the renderer repository, we-scene-demo/docs/README-DIAGNOSTICS.md (generated by diag-flag-check.mjs, cross-checked against web/diag-flags.json).
  • The plugin panel only ships the common / escape-hatch table (the 10 flags with common:true), appended to the renderer URL, e.g. http://127.0.0.1:8902/webloader/?id=3719111841&audit=3:
Flag Purpose Default
att=legacy Attachment anchoring through the old self-computed path (A/B) new path
mcc=1 Force mesh bounding-box centre compensation off
piv=1 / piv=0 Sub-mesh pivot compensation all on / all off eyes combo only
align=0 Reproduce the old alignment (origin always the geometric centre) official alignment
parallax=legacy Mouse parallax back to the old approximation official formula
audio=1 Play the scene's sound layers (one stream each) muted
whitefallback=0 Missing-texture layers become transparent white block (official)
hier=0 Drop parent-chain composition, fall back to absolute positioning parent-chain composition
isolate=<layer> Keep only layers whose name matches (comma separated) visible all visible
audit=N Per-layer audit of the first N frames 1

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).

Compatibility, limits and what it cannot do

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.

Browser compatibility (measured reference)

Browser Rating Notes
Chrome / Chromium (desktop) ⭐⭐⭐ strong Most complete: best backdrop-filter and color-mix, working iframe.muted, muted autoplay allowed
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
Firefox ⭐⭐ medium Everything works (backdrop-filter 103+, transcode fallback for unsupported codecs); three deductions — see the note below
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

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). 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).

Without a Wallpaper Engine install (missing / non-Windows)

A "WE install" means the Steam edition of Wallpaper Engine (appid 431960). The host's locateWallpaperEngine() (lib/index.js:311-399) 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/... plus a per-drive enumeration of /mnt/d…/mnt/z) → 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:

Scenario Real behaviour (with code location)
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)
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
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
Non-Windows / mobile Same as "WE not installed" (the registry branch returns null off win32); no side effects
WE playlists Only when installDir exists and config.json parses; otherwise rotSeeded:true stops re-seeding (lib/client.js:8782-8793)
Still available without WE Manual folders, direct import, URL/local files, scene extraction and diagnostics all work without

…

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.