# ShaderToy 背景皮肤 · 开工前方案复核与决策 > 复核对象:`dsh 桌面版 ShaderToy 实时背景皮肤插件实现方案.md`(下称「原方案」,AI 生成)。 > 依据:DSH **0.2.1-rc.3** 安装包源码(`app.asar`,逐行核验)+ 本机已装真实同类插件 `open-sea-skin@1.2.6`(MIT)+ 外部真实先例(`dsh-video-skin` / `dsh-wallpaper-engine` / `dsh-bg-new` / `dsh-shader-workbench`)。 > 原始研究:`docs/research/01-api.md`(插件机制)、`02-renderer.md`(渲染端)、`03-ecosystem.md`(外部断言核验)。 > 标注:**[核验]** 有源码/实测证据 · **[推断]** 有依据但未实测 · **[待实测]** 必须上机跑。 --- ## 0. 一句话结论 **能做,而且比原方案写的更稳。** 原方案的「Runtime 自研 + iframe + 半透明玻璃 + 背景库」四件事全部成立;但 **M0 的注入机制要换**(`webserver/index-inject` → `dsh.client` Client 插件 + Host 静态路由),否则每次改代码都要整机重启、而且拿不到动态主题能力。 剩下唯一没实测的生死点:**把 DSH 界面变半透明后,shader 能不能真的透出来、文字还能不能读** —— 机制已确认可覆盖(AppFrame 的表面颜色全部走 CSS 变量),M0 第一天就能验证。 --- ## 1. 原方案:哪些成立、哪些要改 | # | 原方案说法 | 结论 | 修正 | |---|---|---|---| | 1 | 「背景层靠 `webserver/index-inject` 注入」 | **真实存在,但不该选** | 桌面版 index 注入表是 **Host 启动时采集一次的 IPC 快照**(`dsh-desktop-host/lib/index.js:342-349` → `main.js:12138/12193/12424-12431`)→ 改一次注入行、装一个新插件都要**整机重启**;Web 端才是每次渲染重收。→ 改走 Client 插件(可热更、能调 theme 服务) | | 2 | 「注入行不能用 `kind:'script-src'`」 | **半假** | 框架自己就用它注入 bootstrap 批(`dsh-client-modules:494-498`)。真雷是桌面解释器会 `await` 它,加载失败直接 reject boot readiness → 主进程 fatal。→ 结论(别把关键脚本放 script-src)可沿用,理由要改 | | 3 | 「注入监听器必须在 apply() 里同步注册」 | **成立(有前提)** | 因为桌面注入表只有启动时那一次采集机会 | | 4 | 「背景层必须挂 body 首子节点,否则破坏 DOM 水合」 | **做法对,理由是错的** | `#root` 是空 div + `createRoot`(不是 hydrate,没有水合)。真实原因:① `#root` 归 React 独占,塞进去会被清掉;② 层叠——`#root > AppFrame` 是 `position:relative`,body 里更早的定位元素先绘制 | | 5 | 「`dsh-app://` 下 localStorage 不保证可用」 | **可用** | scheme 以 `standard:true, secure:true` 注册(`main.js:11880`),`open-sea-skin`/`better-sidebar` 两个生产插件都在用。仍建议文件为真源,见 D3 | | 6 | 「音频纹理 512×2,第一行 L 第二行 R」 | **错** | 512×2 是**图像 pass 的音频输入纹理**:row0 = 频谱、row1 = 波形。音频输出是「每像素一个采样 + readPixels 回读」。v1 不做可视化,与输出编码无关 | | 7 | 「透明化用 `--dsw-alias-*` token」 | **成立,且更好** | 0.2.1-rc.3 里 AppFrame 的表面颜色**全部走 token**(`ui-layout:73`:`.frame{background:var(--dsw-alias-bg-base)}`、`[data-windows-titlebar] .frame{background:var(--dsw-specific-sidebar-fill)}`、`.sidebarCol{...sidebar-fill}`),而 `theme.overrideTokens` 会把覆盖值**内联写到 `body.style`**(`ui-layout:548-569`)→ 滑块可以实时生效,不必学 open-sea-skin 打 `!important` 硬编码类名(其中 2 个 token + 4 个桌面类名在本版根本不存在) | | 8 | 「三条路线:A 原生 slot / B index-inject / C 扩展」 | **分类失真** | 真实三分法是:官方 `dsh.client` 插件(wallpaper-engine / bg-new / open-sea-skin 原生包)/ `index-inject` 覆盖层(**只有 video-skin 一例**)/ 改源码加 slot。**主流是 client 插件** | | 9 | 「open-sea-skin 零依赖」 | **错** | 它内置 three.js 0.178 + TSL + 字体(≈1.3MB)。我们的零依赖结论仍然成立,但别拿它当证据 | | 10 | 「24 个作品基本都是纯程序化单 pass,声音标注准确」 | **未核验** | 本机对 shadertoy.com 恒 403(Cloudflare,实测首页也是 403)。抽样 6 个里 4 个旁证为真、2 个(`fXc3RB`/`ssjyWc`)零证据。→ 24 条必须逐条核验,别写进承诺 | | 11 | 「许可证 = 默认 CC BY-NC-SA,不能随 MIT 发布」 | **方向对,措辞错** | ShaderToy 条款原文是**作者决定**许可证,**不声明才落到默认 CC BY-NC-SA 3.0**。结论不变:NC/未声明作品不能进 MIT 公开包 | | 12 | 「预设靠官方 API key 构建期拉取」 | **本机做不到** | 实测:本机 curl/Node 访问 shadertoy.com **全路径 403**(含首页)。API key 路线要另想办法,见 D3 | 另外原方案漏了一个直接竞品:**`dsh-shader-workbench`**(MIT、零运行时依赖、纯 WebGL2、Shadertoy 兼容 + 热重载)——它证明「几百行自研 mini runtime」可行,也说明我们要做出差异(背景/皮肤定位 + 库管理 + 音频 + 可读性)。 --- ## 2. 推荐实现方式 ### 2.1 四层结构 ``` Electron 桌面壳 (dsh-app://app/) └─ Host 插件 (Node) —— 只做两件事:静态资源路由 + 状态读写 ├─ GET /shader-skin/* 读盘返回 player.html / ui.html / *.js / presets/*.json(no-cache) └─ GET|PUT /shader-skin/state 读写 $DSH_HOME/shader-skin.json(临时文件 + rename 原子替换) └─ Client 插件 (浏览器, dsh.client) —— 薄壳(~150 行),负责挂载与桥接 ├─ 建