- Главная
- Плагины
- Темы и оформление
- dsh-client-ui-wallpaper
dsh-client-ui-wallpaper
gnk478/dsh-client-ui-wallpaper
把本地图片/视频(Dynamic Wallpaper.app 素材)用作 DSH 桌面客户端壁纸:铺满窗口、背景模糊、面板/左右侧栏独立透明度、自动深浅字、视频缩略图、轮换与播放列表同步。· Use local images or videos as the DSH desktop client background.
Установка
dsh plugin --profile web add github:gnk478/dsh-client-ui-wallpaperREADME
dsh-client-ui-wallpaper
把本地图片 / 视频(例如 Dynamic Wallpaper.app 播放列表里的素材) 用作 DSH 桌面客户端的背景:整窗铺满、背景模糊、面板与左右侧栏透明度分别可控,字色跟着壁纸明暗自动切换。
这是
~/.dsh/profiles/desktop/plugins/dsh-client-ui-wallpaper的完整项目版:含挂载声明、安装脚本、自检脚本与文档;已发布到 npm(dsh-client-ui-wallpaper)。

上图为真实运行截图(macOS 客户端,设置 → 壁纸):轮换选择与「23 项参与轮换」、自动轮换间隔 60 分钟、只轮换深色 5 项 / 浅色 17 项、浅色壁纸用深色字、自动加入新壁纸、毛玻璃 9px、右侧栏透明度 70%,下方是动态壁纸缩略图网格(每格右上角标注该壁纸的明暗判定)。截图为整幅实拍原图(未裁切、未遮挡、未修饰),仅等比缩放到 1800px 宽。
功能
背景
- 图片或视频铺满整窗(
object-fit: cover),面板半透明让壁纸透出来;弹窗保持不透明 - 背景模糊可调(0–60px);可选纱层(浅色外观白纱 / 深色外观黑纱)
- 静态与动态分开:图片可配「同名视频」,网格里用 ▶ 动态 一键切换
- 轮换:间隔 15 秒–24 小时可调;支持「全选 / 仅深色动态 / 清空 / 默认」
可读性(墨水)
- 按壁纸亮度自动切深/浅字(含代码块、行内 code、气泡、工具栏、输入框与 caret)
- 代码块只跟外观走:浅色外观浅底深字、深色外观深底浅字(绕开
pre.shiki透明背景导致的误判) - 设置弹窗内的字色复位为「该外观本来的值」,不被壁纸影响
面板与控件(设置 → 壁纸)
| 控件 | 说明 |
|---|---|
| 壁纸网格 | 静态/动态分组,缩略图 + 深/浅角标 + ✓/+ 轮换勾选 + 当前/播放中标记 |
| 背景模糊 | 滑块 + − [数字框] + 精确步进(0–60,居中、无原生箭头) |
| 右侧栏透明度 | 0–100% 线性,独立于面板;左栏保持原透明观感 |
| 只轮换深色 / 只轮换浅色 | 优先用缩略图实测亮度判定,未采样时按配置名单取反 |
| 自动加入新壁纸 | 新丢进目录的素材自动入库并追加进轮换 |
| 自动轮换 / 间隔 | 秒数可调;可勾「随机轮换」(不会连续两次同一张) |
| 手动选择不打断轮换 | 点缩略图(静态或动态)只换「当前这一张」,轮换照旧(1.1.6 起);选中的素材即使不在轮换列表里也先显示它,下一拍回到列表继续 |
| 视频省电暂停 | 窗口不可见时暂停播放,回到前台自动续播(隐藏期间不切新壁纸) |
| 轮换过渡 | 交叉淡入淡出 0.7s;系统开启「减少动态效果」或窗口隐藏时直接切换 |
| 首帧就绪再淡入 | 新壁纸等首帧解码完成(视频 loadeddata、图片 load/decode,最多等 600ms)才开始淡入,避免大视频「先透明后硬切」 |
| 图层自愈 | 每 10 秒审计一次壁纸层:除面纱与当前节点外,活过过渡宽限(700ms + 250ms)的节点一律判定为残留并回收;层内每个节点的 tag/来源/暂停/透明度/存活时长都上报到 /_probe 的 layerDetail |
| 探针实时化 | 每次切换壁纸后 0.6 秒、以及每 60 秒再发一次 /_probe,switches / layerDetail / audit / prewarm 都是最新值(1.1.1 之前只在启动后 1.5 秒上报一次) |
| 大文件不预热 | 预热前先取素材大小(1.1.4 起宿主在 list.json 里直接给 sizes,取不到才回退 HEAD),超过 50MB 的素材不提前建节点(库里最大的视频 196MB),/_probe 的 prewarm 记录 warmed / skipped / dropped 与最近一次的字节数 |
| 加载失败不静默 | list.json 加载链一旦抛错,错误名与 message 会记进 loadFailure(/wallpaper/_probe 与 /wallpaper/_client 都带),并在下一次成功加载后清空——不再出现「面板正常但壁纸整块空白、没有任何线索」 |
| 选择器失效不静默 | 客户端每 60 秒体检 8 组界面挂钩(输入框/侧栏/右栏/弹窗/气泡/工具栏/头像/代码块):带锚点(textarea、[class*="sidebar"]、[aria-modal="true"] 等)的一组连续两轮都匹配不到才算失效(避开 React 渲染中途的假警报),此时 console.warn 并把 missing / detail 写进 /wallpaper/_probe 的 selectorHealth——DSH 升级后「样式悄悄没生效」有据可查 |
| 浅色壁纸用深色字 | 关掉就固定用浅色字 |
| 同步播放列表 | 一键把 Dynamic Wallpaper.app 播放列表里的素材同步进来 |
| 删除(缩略图悬停) | 移到「废纸篓」(可恢复)+ 删缩略图缓存 + 从轮换移除 + 记入同步跳过名单 |
| 侧栏小圆钉 | 与头像中心对齐的收起按钮,点空白处自动收回 |
安装
要求宿主 DSH ≥ 0.2.0-rc.2(本插件实际测试的版本)。装完刷新页面(或重启 DSH),打开 设置 → 壁纸。
A. 桌面 App:App 内插件市场(推荐)
DSH Desktop 的 desktop profile 由 Electron 应用独占管理,命令行会直接拒绝(profile "desktop" is managed exclusively by the Electron application),所以桌面端请在 设置 → 插件市场 里搜索安装。本插件已提交社区注册表 awesome-dsh-plugin(条目文件),收录后即可在市场一键安装与升级。
B. 其他 profile(web / 自建 profile):一行命令
dsh plugin --profile web add dsh-client-ui-wallpaper
CLI 会把包装进 profile 的 node_modules,并自动把包名追加到 profile 的 dsh.profile.bundles——cordis.patch.yml 不需要手动改。命令行需要 pnpm 在 PATH。
升级要写版本号:pnpm 11 默认
minimumReleaseAge=24h,发布未满 24 小时的版本,dsh plugin add <包名>与@latest都只会装到上一版(并提示x.y.z is available)。要立刻升级请指定版本:dsh plugin --profile web add dsh-client-ui-wallpaper@1.1.8
C. 手动安装(fallback)
profile 已经被手动改过、或环境里没有 dsh 命令时用;会直接改 profile 的 cordis.patch.yml(先备份):
npm i dsh-client-ui-wallpaper
node node_modules/dsh-client-ui-wallpaper/scripts/install.mjs --profile desktop
从源码同理:
git clone https://github.com/gnk478/dsh-client-ui-wallpaper.git
cd dsh-client-ui-wallpaper
node scripts/install.mjs --profile desktop
profile 的
package.json/cordis.patch.yml归插件管理器所有,手写的 insert 行可能在它下次操作时被覆盖——能用市场或 CLI 就不要用它。
快速开始
素材二选一:
- 什么都不用做:默认直接读 Dynamic Wallpaper.app 的素材库
~/Library/Containers/whbalzac.Dongtaizhuomian/Data/Documents/{Wallpaper,Videos} - 用自己的目录:在 config 里写绝对路径(插件不做
~展开),例如imageDir: /Users/you/.dsh/wallpapers、videoDir: /Users/you/.dsh/videos
mkdir -p ~/.dsh/wallpapers ~/.dsh/videos
cp ~/Pictures/some.jpg ~/.dsh/wallpapers/ # 静态壁纸
cp ~/Movies/some.mp4 ~/.dsh/videos/ # 动态壁纸
node scripts/check.mjs # 自检
配置
config(cordis.patch.yml 的 ui-wallpaper 行,见 examples/profile-cordis.patch.yml)
| 键 | 默认 | 说明 |
|---|---|---|
imageDir / videoDir | Dynamic Wallpaper.app 素材库的 Wallpaper/ 与 Videos/(lib/index.js:37-39) | 素材目录,必须是绝对路径(插件不做 ~ 展开) |
mode | image | image / video / rotate |
image / video | '' | 固定的文件名 |
live | false | 图片有同名视频时用视频做动态壁纸 |
rotateSeconds | 300 | 轮换间隔(秒) |
shuffle | false | 随机轮换(不会连续两次同一张) |
blur | 8 | 背景模糊(px) |
panelOpacity | 0 | 面板不透明度(0 = 全透) |
windowOpacity | 0.96 | 弹窗不透明度 |
dim | 0 | 纱层强度 |
autoInk | true | 按壁纸亮度自动切字色 |
autoInclude | true | 新素材自动加入轮换 |
only / darkVideos | [] | 深色壁纸 / 深色动态白名单 |
state(~/.dsh/wallpaper-state.json,面板里改的都落这里)
mode、image、video、live、rotation、only、videoPool、blur、panelOpacity、sidebarOpacity(null = 跟随面板)、autoInk、autoInclude、knownFiles、lastAdded、rotateSeconds、shuffle
# 直接读/写状态(调试用)
curl -s http://127.0.0.1:19387/wallpaper/state
curl -s -X POST -H 'content-type: application/json' \
-d '{"sidebarOpacity":0.35}' http://127.0.0.1:19387/wallpaper/state
与 Dynamic Wallpaper.app 同步
app 的播放列表在:
~/Library/Containers/whbalzac.Dongtaizhuomian/Data/Documents/
├── Videos/ 播放列表素材
├── Wallpaper/ 静态壁纸
└── Setting/Preferences.json playlist_array(播放列表)
两种同步方式(都只增不删;读取该目录需要给 DSH「完全磁盘访问权限」):
# 面板:设置 → 壁纸 → 同步播放列表
# 或命令行
./sync-wallpaper-playlist.sh # 只同步播放列表里的视频
./sync-wallpaper-playlist.sh --stills # 连静态壁纸目录一起
同步会跳过 ~/.dsh/wallpaper-sync-skip.txt 里列出的文件名(删除过的素材会自动写进去,防止被拉回)。
目录结构
dsh-client-ui-wallpaper/
├── lib/
│ ├── index.js 宿主:路由 / 扫描 / 状态 / 抽帧 / 同步 / 删除
│ └── client.js 客户端:注入样式 / 铺背景 / 面板 UI / 墨水判定
├── scripts/
│ ├── install.mjs 安装进 profile(备份 + 复制 + 写 insert 行)
│ └── check.mjs 静态自检(语法 + 路由 + 样式常量 + 控件)
├── docs/ARCHITECTURE.md 架构、路由表、样式常量、踩坑记录
├── .github/workflows/ check.yml(自检)+ publish.yml(npm 可信发布)
├── examples/profile-cordis.patch.yml 带完整配置的挂载示例
├── examples/github-workflow-publish.yml publish.yml 副本(方便复制)
├── cordis.patch.yml 本插件的挂载声明
├── CHANGELOG.md / PROVENANCE.md / LICENSE
└── README.md
开发
-
宿主是普通 cordis 插件,改完由 DSH 的 HMR 重载;客户端
lib/client.js改动会被热替换(注意:热替换会重置模块级变量,需要跨次保留的数据要放window/ sessionStorage) -
自检:
node scripts/check.mjs node --test # 行为测试(宿主:路由 / 回收站 / 缩略图 GC;客户端:轮换加载与预热 / crossfade / 首帧就绪 / 图层审计 / applyVars / autoInk / classifyTile / syncBlur / 选择器体检 / 手选不打断轮换 / 手选点击留痕),1.1.7 起共 54 例 curl -s http://127.0.0.1:19387/wallpaper/_client | python3 -m json.tool | head -40 # 客户端自报状态 curl -s http://127.0.0.1:19387/wallpaper/_hits # 各路由请求计数
发布与打包
npm 发布走可信发布(Trusted Publishing / OIDC),不需要 token:
- 改
package.json的version、更新CHANGELOG.md - 在 npm 包设置里配 Trusted Publisher:GitHub Actions → Organization
gnk478、Repositorydsh-client-ui-wallpaper、Workflowpublish.yml,Allowed actions 勾上npm publish - 推 tag 或手动触发
.github/workflows/publish.yml:
git tag v1.1.8 && git push origin v1.1.8 # tag 触发
gh workflow run publish.yml # 或手动触发
工作流里 permissions: id-token: write 是关键(OIDC 身份);npm publish 会自动带 provenance。
本地手工发布仍然可用,但需要一个开了 Bypass 2FA 的 Granular Access Token:npm login && npm publish(prepublishOnly 会先跑 scripts/check.mjs 与 node --test)。
Release 附件:
git archive --format=zip -o dsh-client-ui-wallpaper-1.1.8.zip HEAD
gh release create v1.1.8 dsh-client-ui-wallpaper-1.1.8.zip
CI:.github/workflows/check.yml 在 push / PR 时跑 node scripts/check.mjs 与 node --test,也是顶部徽章的来源;两个工作流的副本放在 examples/ 下方便复制。
故障排查
| 症状 | 原因 / 处理 |
|---|---|
| 壁纸没铺满 | 面板里确认已选壁纸;检查 /wallpaper/list.json 是否有素材 |
| 点了壁纸完全没反应(1.1.6) | 面板 onClick 用的 modeAfterPick() 被定义在 apply() 内,闭包取不到 → 每次点击同步抛 ReferenceError,save() 从未执行。1.1.7 已把函数提升到工厂作用域;排查时读 /wallpaper/_probe 的 panelPick(点击计数:数字不涨 = 点击没进来;涨了但画面不变 = 状态没生效)与 /_client 的 lastError |
| 点了壁纸,自动轮换就停了(1.1.5 及更早) | 面板手选曾经硬编码 mode: "image" / mode: "video",等于顺手把轮换关掉;1.1.6 起改走 modeAfterPick()——轮换开着时手选只改当前位置,选中的素材不在轮换列表里也先显示它 |
| 壁纸整块空白(面板一切正常) | 1.1.1 / 1.1.2 的已知缺陷:rotate 分支丢了 var seconds / var index 声明,load() 一进分支就抛 ReferenceError,又被静默的 .catch 吞掉 → 壁纸再也不绘制且毫无提示。升级到 1.1.3;之后若再遇到,读 /wallpaper/_probe 的 loadFailure({ at, name, message }) |
| DSH 升级后样式或自动字色悄悄没生效 | 1.1.5 起客户端每 60 秒体检一次界面挂钩,读 /wallpaper/_probe 的 selectorHealth:missing 列出失效的挂钩(missing: [] 即正常),detail 给出每组的匹配数与命中的那条选择器;带锚点的组连续两轮落空才会告警,所以渲染中途不会误报。告警同时会 console.warn("[dsh-wallpaper] UI 选择器失效:…") |
| 侧栏/右栏透明度拖了没反应 | 常见于「透明化扫描」清掉了底色;本版本已对右栏跳过扫描并直接写 background-color。若自行改过选择器,核对 SIDEBAR_CSS / SIDEBAR_LEFT_CSS |
| 代码块字看不清 | 代码块应「只跟外观走」;检查 CODEFIX_CSS 是否注入 |
| 视频缩略图一直是灰的 | 首次访问 /wallpaper/thumb/<name> 会调 qlmanage 抽帧,稍等再刷新;检查 ~/.dsh/wallpaper-thumbs/ |
| 换壁纸没有淡入效果 | 首次绘制、重绘同一张、窗口隐藏、或系统开了「减少动态效果」(prefers-reduced-motion: reduce)时都是直接切换,不做 0.7s 过渡。1.1.1 起淡入会先等新壁纸首帧就绪(最多 600ms),大视频不会再看成「秒切」;用 /wallpaper/_probe 的 switches(lastReason / waited)可确认最近一次为什么没淡。1.1.2 起这些统计是实时的(切换后 0.6 秒 + 每 60 秒刷新),另外 layerDetail 会逐个列出层里残留的节点、audit.removed 显示自愈回收了几个。loadFailure 为 null 说明加载链没抛错 |
| 新素材不出现 | 刷新面板(宿主目录扫描有 4 秒快照缓存,见 lib/index.js:229);确认扩展名在白名单(图片 jpg/jpeg/png/webp/gif/avif/bmp,视频 mp4/webm/mov/m4v) |
| 改了 profile 文件不生效 | DSH 运行中会用内存配置回写 profile 文件;完全退出后再改,或改用「设置」界面 |
| 面板打不开 / 报错 | 看 /wallpaper/_client 的 lastError,以及宿主日志 |
已知限制
- 与 DSH 的布局类名(
_6Qf49G_*)和色板 token(--dsw-*)耦合,DSH 升级后可能需要同步调整;1.1.5 起这类失效不再静默——每 60 秒体检一次,selectorHealth.missing会点名落空的那组(detail给出匹配数) - 播放列表同步依赖 macOS 的 TCC 权限(完全磁盘访问)与
qlmanage(缩略图抽帧) - 删除会移到
~/.Trash(可恢复);素材与废纸篓不在同一卷时rename失败,会回退为真删,面板提示「已永久删除」 - 超过 50MB 的素材不做轮换预热,切换时的首次解码在几十毫秒到几百毫秒之间(首帧就绪上限仍是 600ms)
- 图层自愈是兜底手段:正常情况
audit.removed恒为 0;若它持续增长,layerDetail会指出是哪个节点、来自哪个文件 - 轮换分支的初始化(
seconds/index/ 首帧停在哪一项)现在由test/rotate.test.mjs用真实源码 + 替身 fetch 跑通:任何未声明变量或提前抛错都会让测试变红,而不是让壁纸静默空白
License
Похожие плагины
dsh-deep-whale (maid-atelier)
small-tailqwq/dsh-deep-whale
dsh-wallpaper-engine
elysia395/dsh-wallpaper-engine
dsh-deep-whale
small-tailqwq/dsh-deep-whale
open-sea-skin
d-dev0101/open-sea-skin