Skip to main content
Y

dsh-local-background

yidao2000/dsh-local-background

Local-folder wallpaper for the DSH Web UI: it defaults to your Pictures library (C:\Users\<you>\Pictures) and the host serves those images over same-origin HTTP, rotating one every N minutes (0-1440, default 5) in a shuffled pass with no repeats; the settings page under Plugins picks a different folder through the native system dialog, and tunes image opacity (100%), darkening scrim (25%) and panel opacity (15%) with changes applied live. Dialogs, modals and menus keep the default opaque background so text stays readable. Reads .jpg .jpeg .png .webp .gif .avif .bmp straight from disk, and keeps per-user settings under $DSH_HOME so upgrades never ship your folder path.

Install

dsh plugin --profile web add github:yidao2000/dsh-local-background

README

dsh-local-background

给 DeepSeek Harness 桌面端换自己的背景图、并自动轮播的本地插件。

  • 图片放在本地文件夹里,插件直接读 —— 不打包、不压缩、随便加减
  • 每 N 分钟自动换一张(默认 5 分钟)
  • 不依赖任何第三方皮肤/主题作者的版本适配
  • 不想要了,在桌面应用「插件」页卸载即可

原理(为什么这么做)

DSH 内置没有背景图设置:@deepseek-ai/dsh-client-ui-theme 只提供「配色方案」和「正文字号」, 官方文档把第三方主题定义为「扩展点,不是产品」。

本插件走官方认可的扩展路径,分两半:

半侧文件职责
宿主(Node/ESM)index.js读图片文件夹;webServer 六条路由;配置的读写与持久化
客户端(浏览器)client.js取清单 → 定时切换 background-image → 注入 CSS;「插件」页里的设置界面
GET  /dsh-local-background/list                      → { dir, rotateMinutes, dim, surface, imageOpacity, settingsOpaque, picker, files:[...] }
GET  /dsh-local-background/img/<名>                  → 图片字节流(同源,不走 file://,不受跨域限制)
GET  /dsh-local-background/pick                      → 弹系统文件夹对话框,返回 { supported, path }
POST /dsh-local-background/config                    → 写配置:{ dir?, dim?, surface?, imageOpacity?, rotateMinutes?, settingsOpaque? }
GET  /dsh-local-background/report                    → 客户端诊断日志(追加 report.log)

为什么不让客户端直接读文件夹:浏览器拿不到本地文件系统,file:// 又被 Electron 的 webSecurity 拦住。让宿主读盘、用同源 http 提供,是最干净的解法。

注入的 CSS 到底改了什么

html, body { background-image: <压暗渐变>, url("/dsh-local-background/img/…"); background-size: cover; background-attachment: fixed; }
body { --dsw-alias-bg-base: transparent; }   /* 应用底色透明 */

只透明化 bg-base 是不够的 —— 真正铺满视口的是「表层」,必须一起处理,否则启动瞬间能看到背景、 下层一渲染就被盖住(表现为"一闪就黑"):

Token含义明色暗色
--dsw-alias-bg-baseApplication base backgroundbluish-00bluish-950
--dsw-alias-bg-layer-1Primary raised surface(主面板)bluish-00bluish-875 #232324
--dsw-alias-bg-layer-2Secondary nested surfacebluish-00bluish-850 #2C2C2E
--dsw-specific-sidebar-fill侧栏/布局填充(实测它铺满整个视口)bluish-50bluish-900 #1B1B1C

做法是 color-mix(in srgb, var(--dsw-static-…) N%, transparent) —— 保留原色值、只加透明度, 所以切明/暗主题都不会串色。

--dsw-alias-bg-overlay(弹出层/菜单)故意保持不透明,保证菜单文字可读。


安装

命令行(推荐)

dsh plugin --profile web add dsh-local-background
  • --profile web 要带上:本插件是 web 平台客户端插件(dsh.client.platform = "web")。 桌面端用的是 desktop profile,所以不要写 --profile desktop。
  • 从 npm 装(上面的写法)需要包已发布。若 npm 上还取不到,用 GitHub 源装:
    dsh plugin --profile web add github:yidao2000/dsh-local-background
    
  • 也可以填绝对路径装本地目录(适合改源码调试):
    dsh plugin --profile web add D:\devinstall\IdeaProjects\dsh-local-background
    

GUI(桌面应用)

插件 → 添加插件 → 填仓库地址或本地绝对路径 → 启用 → 重启桌面应用。

  • 装别人的包:yidao2000/dsh-local-background
  • 装自己改的源码:本仓库的绝对路径(见上)

(插件管理器接受仓库地址与绝对路径;相对路径会被 parseInstallSpec 拒绝。)


换图 / 调参:在插件界面里点(不用重启、改完立刻看见)

插件(左侧栏)→ 找到 dsh-local-background → 行上的 配置 按钮。

打开后是这些控件,每一次改动都立刻生效,同时写进用户态目录的 settings.json 持久保存 (~/.dsh/dsh-local-background/settings.json):

控件作用范围
壁纸文件夹图片从哪个文件夹读绝对路径,默认 C:\Users\<用户名>\Pictures
└ 选择文件夹…弹 Windows 系统文件夹对话框原生
背景图不透明度背景图自身多透明(这是最常用的那个)0% = 看不见图,100% = 原图(默认)
背景压暗在图上盖一层黑,图太亮时压一压0% = 不压暗(默认 25%)
面板不透明度界面面板多透明;越小背景越明显、字越难读0~100%(默认 15%)
自动轮换每隔几分钟换一张0 = 不轮换,最大 1440(默认 5)
弹窗用默认(不透明)背景宿主「设置」等弹窗/对话框/菜单的背景:勾上就用宿主默认不透明底色(字更清楚)默认勾选

开箱默认值:图片库 + 不透明度 100% + 压暗 25% + 面板 15% + 5 分钟轮换 + 弹窗不透明。 「壁纸文件夹」默认由宿主在运行时算:Windows/macOS 取 <用户目录>/Pictures、 Linux 取 $XDG_PICTURES_DIR(缺省 <用户目录>/Pictures);目录不存在就留空,界面会提示选一个。 系统文件夹对话框不可用(远程 / 无显示环境)时,直接在输入框里填绝对路径即可。

壁纸是靠改 body 上的 --dsw-alias-bg-* 透出来的,而这些自定义属性会被弹窗、对话框、菜单 这些浮层继承 —— 于是宿主自己的「设置」弹窗(背景是 var(--dsw-alias-bg-layer-2))也跟着半透明, 字就看不清了。勾选「弹窗用默认(不透明)背景」后,插件会给所有浮层 ([role="dialog"]、[role="menu"])重新声明这些别名,值直接取 static 色板(不受 body 覆盖影响), 弹窗就回到默认的不透明底色。本插件自己的配置页所在的那支浮层会被标记放过 —— 那一页是调透明度的预览区,得看得见壁纸。

换图仍然是把文件丢进那个文件夹就行 —— 加减图片后点 重新读取 即可(或刷新页面), 不用改插件、不用跑脚本、不用重新打包。

「背景图不透明度」和「背景压暗」是两个独立维度: 前者让图本身变淡(融进主题底色),后者给图加黑。想要"淡淡的壁纸"就降前者。

也可以直接编辑配置(等价,但要重启)

插件配置文件 cordis.patch.yml 里是同样的字段,作为默认值:

- insert:
    - id: dsh-local-background
      name: dsh-local-background
      config:
        dir: ''                # 壁纸文件夹;留空 = 用内置默认(当前用户的「图片」库)
        rotateMinutes: 5       # 每几分钟换一张;0 = 不轮换
        dim: 0.25              # 背景压暗 0~1(图亮就加大)
        surface: 0.15          # 面板不透明度 0~1
        imageOpacity: 1        # 背景图不透明度 0~1
        settingsOpaque: true   # 弹窗(含宿主「设置」)用默认(不透明)背景

⚠️ 优先级:settings.json(界面里改的)覆盖 cordis.patch.yml(默认值)。 所以你在界面上调过之后,再改 cordis.patch.yml 里的同名项不会生效 —— 要么在界面上改,要么删掉 settings.json 回到默认值。

settings.json / report.log 写在用户态目录 ~/.dsh/dsh-local-background/(或 $DSH_HOME/dsh-local-background/), 不在插件包里 —— 升级插件不会丢配置,也不会把你的目录路径打进发布包。 老版本写在插件目录里的 settings.json 仍会被读回来兜底。

  • 支持 .jpg .jpeg .png .webp .gif .avif .bmp
  • 顺序按文件名排序(中文按拼音),启动时随机洗牌,一轮内不重复
  • 40 MB 的 4K 原图也没关系:浏览器自己缩放,background-size: cover 负责裁切

目录里没图 / 清单取不到时:交还给客户端自己的底色

client.js 的 IMAGE 默认为空串 = 不画任何兜底渐变。没有图可画时插件一句背景样式都不注入, body 上的 --dsw-alias-bg-* 也就不再被覆盖 —— 界面就是桌面客户端原本的配色(明/暗主题都跟着走), 不会留一个跟主题不搭的渐变。把图片丢进文件夹后点 重新读取,壁纸立刻接管。

想要固定的兜底图,就把 CONFIG 里的 IMAGE 填成渐变或地址:

var IMAGE = 'linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%)';

单图模式(可选,一般用不上)

set-image.ps1 仍然保留,用于「把某一张图直接嵌进 client.js 当兜底图」—— 只有图片清单取不到(文件夹被删/为空)时才会用到它。正常情况不需要跑。


排障:三个实际踩过的坑(2026-10-08)

① 裸图片地址必须包 url()(踩过两次:data URI 一次、本地路径一次)

裸写会怎样:

background-image: linear-gradient(...), data:image/jpeg;base64,/9j/... !important;   /* ✗ 非法 */
background-image: linear-gradient(...), /dsh-local-background/img/a.jpg  !important; /* ✗ 同样非法 */

两种都不是合法的 CSS <image> 值 ⇒ 整条声明作废 ⇒ 计算值变成 background-image: none。 表现就是"改了没反应",但 --dsw-alias-bg-base 等 token 覆盖照样生效(它们是另一条规则), 所以看起来像"样式进去了一半"。

渐变不需要 url(),所以默认兜底值当时看着是好的,掩盖了问题。

判定口径:report.log 里 phase=diag 的 bgImg=none 就是这条的症状 —— 此时 img-ok 往往还是正常的(new Image() 能加载),说明图能取到,是 CSS 写法错了。

现在 cssImage() 只放行真正的 CSS 图片函数(url( / none / 各类 *-gradient / image-set), 其余一律包成 url("...") —— 裸 data:、裸本地路径、裸 https:// 都会被正确包装。

② 只透明化 bg-base 不够 —— 面板会盖住背景

见上面那张 token 表。--dsw-specific-sidebar-fill 是罪魁:它名义上是"侧栏填充", 实测却铺满整个视口。它渲染得比首屏晚 ⇒ 启动时能看到背景,随后被它盖住变黑。

⚠️ 这个现象和"某个壁纸皮肤有 bug"无关:任何把图垫在界面底下的做法都会撞上它。

③ leveldb 读取的两个坑(排查用)

  • DSH 正占用 leveldb 文件 ⇒ 必须用 FileShare.ReadWrite 打开
  • 同一个 key 的历史值都留在日志里 ⇒ 必须取最后一条,取第一条会读到过期数据

排查工具

read-probe.ps1 从 Electron 的 localStorage 里读插件写的诊断:

powershell -ExecutionPolicy Bypass -File .\read-probe.ps1
Key内容
dsh-local-background:probe执行到哪一步(boot / rotating / fallback / list-error)、挂载次数、图片数、当前图、目录、错误
dsh-local-background:diag页面加载 2.5 秒后的 DOM 诊断:opaquePainters(扫出所有铺满视口且不透明的元素)、中心元素祖先链、各 token 实际值

判断口径:

  • 两个 key 都没有 → 客户端 bundle 从未执行(插件没生效)
  • probe.phase = list-error → 宿主路由没起来(宿主半侧报错或没重启)
  • probe.phase = fallback → 目录读不到或没有图片(看 probe.serverError)
  • probe.phase = rotating 但界面没背景 → 看 diag.opaquePainters 里还有谁在画不透明底

卸载

首选:桌面应用 → 插件 → 找到 dsh-local-background → 卸载。

手动(仅在 GUI 出问题时):从 ~\.dsh\profiles\desktop\package.json 的 bundles 与 dependencies 各删掉 dsh-local-background 一行,在 profile 目录跑一次 pnpm install,再重启。

应急:完全退出 DSH,把 bundles 里的 "dsh-local-background" 一行删掉(dependencies 留着无妨),重启即恢复默认外观。 本插件只注入一段 <style> + 两个只读路由,不改任何数据。


文件说明

文件作用
package.json插件清单:dsh.bundle.patch + dsh.client.platform=web + 导出 ./client
cordis.patch.yml插入 loader 条目 + 配置项的默认值
settings.json界面里改过的值(自动生成;优先级高于 cordis.patch.yml,删掉即回默认;写在用户态目录 ~/.dsh/dsh-local-background/,不在发布包里)
index.js宿主半侧:webServer 六条路由(清单 / 图片 / 诊断上报 / 写配置 / 选文件夹 / 目录浏览)
client.js客户端半侧:取清单、轮播、注入 CSS、插件页里的设置界面
set-image.ps1可选:把单张图嵌成 client.js 的兜底图
read-probe.ps1排查:从 leveldb 读 probe/diag

node_modules 里的 dsh-local-background 是 pnpm 建的符号链接,本目录自身不需要 npm install。 ⚠️ 改动 client.js(浏览器侧)由 HMR 自动接管,存盘即生效。 改动 index.js(宿主侧)需要 HMR 监视本目录 —— 若 profiles\desktop\cordis.patch.yml 里 hmr 条目的 root 已包含本目录则同样即时生效,否则改完要重启 DSH。

Related plugins