Skip to content

dsh-transparent

Verified

dsh-transparent · v1.4.6 · MIT · Web UI

Unofficial DSH desktop plugin: makes the window translucent so the desktop wallpaper shows through. Windows only. Also ships a genuinely refracting liquid-glass composer, though the app's layout keeps it barely visible.

Install

dsh plugin add dsh-transparent

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

Readme

dsh-transparent

中文 | English

让 DSH 桌面客户端的窗口变半透明,透出你的桌面壁纸。

面向 DeepSeek Harness 桌面客户端的非官方插件。仅支持 Windows。

插件里也确实包含一套真正会折射的液态玻璃实现。但安装前请注意:在这个应用的布局下,折射几乎看不出来——因为没有任何内容会从输入框后面经过。你实际能看到的效果就是「窗口变半透明」。请读下面的 已知限制,那段很短,而且是实话。


它做什么

效果 状态 实现在哪
窗口变半透明,桌面壁纸透出来 有效 宿主半包(Node)启动一个 PowerShell 助手,设置 Win32/DWM 窗口属性
应用的不透明背景被清掉 有效 客户端半包(CSS)
输入框折射背景,而不只是模糊 已实现,但实际几乎看不出 客户端半包:运行时生成 SVG 位移贴图,通过 backdrop-filter: url() 引用

为什么玻璃看不出来

折射必须有东西可弯。把一片纯色做位移等于什么都没发生——每个像素都一样,挪动它们不会带来任何变化。

所以要让效果显现,需要两件事同时成立,而现在只成立了一件:

  1. 页面不能是不透明的。 ✅ 壁纸确实到了屏幕上(靠窗口半透明)。
  2. 必须有内容从输入框后面经过。 ❌ 输入框独占窗口底部一行,消息列表的底边在它上方。没有任何东西会滚到它后面,透镜没有东西可弯。

折射本身是真实且验证过的——输入框的 backdrop-filter 确实解析成 url(#dsh-transparent-liquid),用同样的滤镜渲染滚动的文字会得到明显的扭曲。是应用的布局把那些文字挡在了透镜之外。

要让它可见,就得改应用的布局,让消息列表延伸到输入框后面。这件事尝试了三次,回退了三次,详见「已知限制」。

安装

从 npm 装(推荐) —— 这是唯一不需要访问 GitHub 的方式。在「添加插件」对话框里,把安装源设为 中国大陆镜像源(registry.npmmirror.com),然后填包名:

dsh-transparent

从 GitHub 装 —— 需要能访问 github.com:

plugin_manager install_bundle  https://github.com/969246694/dsh-transparent

锁定版本而不是跟随 main:

plugin_manager install_bundle  https://github.com/969246694/dsh-transparent#v1.0.0

从本地路径装 —— 适用于克隆下来的仓库或解压出的压缩包:

plugin_manager install_bundle  <本仓库路径>

然后重启客户端。窗口透明度会在启动时自动应用,不需要手动跑任何脚本。

为什么镜像源只对 npm 那条路有用:GitHub 地址被归类为 git 规格,由 git 拉取,完全绕过 npm——所以「安装源」那个设置对它无效。这也正是这个包要发布到 npm 的原因。

配置

这些选项会出现在应用自己的设置界面里——不用手改 YAML。插件通过声明 Config 结构来获得表单。

cordis.patch.yml 依然有效,并提供初始值:

config:
  alpha: 215          # 整窗不透明度
  darkTheme: true     # 强制界面使用黑夜主题
  updateCheck: true   # 有新版本时在 Console 提示
  updateSource: ""    # 自定义检查更新的地址

为什么这个结构是手写的。 声明配置结构通常要写 z.object({...})(来自 @deepseek-ai/schemastery),但装在 <profile>/node_modules 下的插件根本 import 不到这个包——它在应用包内部,从插件位置解析会直接 ERR_MODULE_NOT_FOUND。

所以这里用一个普通对象,形如设置服务读取的样子:type、dict、meta.volatile,以及返回 schemastery { uid, refs } 格式的 toJSON()。插件保持零依赖。

另外:只有标了 volatile 的字段才能在界面上编辑,而 volatile 会改变字段的传递形态——apply() 收到的是 { get() } 而不是值本身。所以每个选项都经过一个小helper解包。

更新

插件管理器没有更新入口,也没有版本检查——代码里完全没有 outdated / upgrade / checkForUpdate 这类路径。应用本身永远不会告诉你出了新版本,所以宿主半包每次启动问一次你 profile 配置的 registry,有新版就在 Console 说一句:

[transparent] v1.2.1 is available (running 1.2.0) — reinstall the plugin to update

检查只花一个小请求,失败是静默的——离线、私有源、线路不通,都不值得报警告。

它只提示,从不安装。 要更新:在插件管理器里重装一次,或执行 install_bundle dsh-transparent——那会重跑 pnpm 并取范围内的最新版。无论哪种方式,新版本都要重启后才开始运行。

工作副本不会被检查:只有装在 node_modules 下(即发布安装的位置)才会检查,所以从仓库直接跑不会被误认为已安装的版本。

updateSource 可以把检查指向别处——任何返回 {"version":"x.y.z"} 的地址(插件会自动拼 /latest)。

darkTheme

白天主题下的透明会发灰——壁纸透上来的是一片灰,而不是玻璃感——所以插件在激活时把主题固定为黑夜。默认 true;改成 false 就完全不动你的选择。

它是通过宿主的设置服务写入 ui-theme.preference 这个持久设置的,所以重启后依然生效、也会反映在设置界面里。写入前会先读一次:已经是黑夜就不写;写入时带上读到的 revision 做 CAS,不会悄悄覆盖你中途改过的值。

⚠️ 卸载插件不会把之前的偏好还原。 如果你希望自己的选择被尊重,请设 darkTheme: false。

alpha 效果
255 窗口完全不透明。看不到壁纸,内容最清晰
215 默认。壁纸可见,内容略微变淡
150 壁纸明显,内容明显变淡

设计令牌在 dsh-transparent.css:

令牌 含义 默认值
--tp-blur 折射前的模糊量 2px
--tp-saturate 折射后背景的饱和度 130%
--tp-lift 明度提升 1.12
--tp-lens 折射强度(位移像素数) 55
--tp-glass-fill 玻璃底色 rgb(19 19 19 / 0.30)
--tp-rim 边缘高光强度 0.10

改 dsh-transparent.css,跑 node build.mjs,客户端半包会热更新。


液态玻璃是怎么做的

模糊不是液态玻璃,折射才是。 真实的透镜沿着形状的法线弯折背景:上下边缘竖向弯、两侧横向弯、四角斜向弯,而中间平坦处完全不弯。

这段描述就是一张法线贴图,而 feDisplacementMap 正是它的消费者:红通道是 x 偏移,绿通道是 y 偏移,128 表示"不偏移"。正因为贴图以 128 为中心构建,玻璃中间对背景不做任何改动,而边缘把它弯折——这是机制本身,不是需要记住的约定。

贴图用 canvas 在运行时生成,并且按输入框自己的宽高比:

  1. 画一个圆角矩形(白底黑背)
  2. 模糊它 —— 这个斜面代替玻璃的厚度
  3. 对斜面做 Sobel —— 得到每个像素的表面法线
  4. 把 (128 + dx, 128 + dy) 写进红和绿通道

宽高比的影响比看上去大得多。 贴图通过 preserveAspectRatio="none" 铺满元素,而输入框是极宽极扁的条(约 10:1)。如果生成 3:1 的贴图再拉伸,竖直方向的斜面会被压缩好几倍——而上下边缘恰恰是这种长条上产生折射的地方。比例错了,玻璃就是平的,而且不报任何错。

color-interpolation-filters 必须是 sRGB。默认的 linearRGB 会把位移抹平,结果退化成普通模糊。


架构

样式由 CSS 结构选择器表达,而不是用 JavaScript 给元素打标记。

这个选择是整套设计的核心,而它是从一次失败里得出的。 用 data-* 属性标记元素、再为它们生成 CSS,无法避免闪烁:

应用重渲染时替换掉自己的元素
  → 新元素没有标记
  → 规则不再生效
  → 插件发现,于是重新应用

只要"重新应用"存在,就存在"没应用上"的那一帧。 唯一能关掉这个窗口的办法,是删掉重新应用本身。

所以客户端半包只做两件事,各一次:

  1. 探测输入框卡片在可编辑元素之上第几层,然后追加一条对应深度的 :has() 规则。深度是应用组件树的属性,与重渲染无关,探测一次就够。链上每一步都是直接子代关系,所以只命中唯一一个元素——不会嵌套,不会叠加。
  2. 生成折射贴图,这是 CSS 确实表达不了的。

没有 MutationObserver、没有调度器、没有限流、没有补标记。 输入框被替换一千次,浏览器就匹配一千次,而我们一行代码都不跑。

仓库结构

dsh-transparent.css      设计令牌 + 结构规则(唯一真源)
build.mjs                把 CSS 拼进 lib/client.js,带守卫
verify.mjs               30+ 项检查,跑在桩上,不需要浏览器
lib/index.js             宿主半包 —— Node,启动窗口助手
lib/client.template.js   客户端半包 —— 经典脚本,含 __CSS__ 占位符
lib/client.js            build.mjs 生成的产物 —— 不要直接改
assets/window-glass.ps1  Win32/DWM 助手,由宿主半包调用

三个值得知道的坑

1. .ps1 必须是纯 ASCII。 powershell.exe 在没有 BOM 时按 ANSI 读取脚本。UTF-8 无 BOM 的文件会让非 ASCII 注释变成乱码,而字符串里混进一个异常字节足以让解析直接失败——这里就真的发生过一次,原因是中文注释。assets/window-glass.ps1 里没有任何非 ASCII 字符,请保持这样。

2. 客户端半包运行在陷阱之下。 在动态求值的客户端半包里,setTimeout、setInterval、clearTimeout、clearInterval、fetch 和 require 被替换成了会抛异常的陷阱。所有定时行为必须用 requestAnimationFrame。verify.mjs 会在它们被调用时让构建失败。

3. 停用一个功能意味着删除它的样式表,而不只是不再写入。 已注入的 <style> 元素不会因为 bundle 不再写它就消失。在代码里撤掉规则、而页面里那个元素还在,旧规则就依然生效。插件因此会在激活时显式删除残留样式表。


已知限制

  • 窗口透明仅支持 Windows。 其他平台上客户端半包照常工作,只是跳过透明度。
  • 壁纸无法被折射。 backdrop-filter 只看得见页面内部的内容。桌面由操作系统在浏览器之外合成,所以玻璃折射的是应用自身的内容——文字、代码块、卡片——但永远不会是壁纸本身。这不是调参问题,是 API 的边界。
  • 聊天文字不会经过输入框后面。 输入框独占窗口底部一行,消息列表的底边在它上方,所以没有东西会滚到玻璃后面。要做到这一点必须修改应用的布局——尝试过三次,回退了三次。详见下文。
  • 如果应用改变了 DOM 深度,:has() 选择器会停止匹配,玻璃安静地消失。这是退化,不是闪烁。

排错

客户端半包在启动时打印一行:

[transparent] v1.0.0 via style tag | selector div:has(...) | card 952x98 ratio 9.7:1 | depth 3

玻璃没出现就先看这一行。depth 0 表示没找到输入框。

Console 里可用的入口:

window.__tp.layout()   // 打印输入框周围的层级链
window.__tp.depth()    // 输入框深度,找不到返回 0
window.__tp.retry()    // 重新探测并安装
window.__tp.diag()     // 溢出状态与插件自身节点

宿主半包的活动记录在 %TEMP%\dsh-transparent.log。

开发

node build.mjs     # 把 CSS 拼进客户端 bundle(带守卫)
node verify.mjs    # 契约、打包、陷阱、YAML 检查

build.mjs 拒绝输出在 CSS 里声明 backdrop-filter 的主题,也拒绝使用裸容器选择器。这两条规则的存在,都是因为在开发中破坏过它们并造成了肉眼可见的破坏。

关于尝试过又撤回的东西,如实记录

让聊天文字滚到玻璃后面这件事,尝试了三次,回退了三次。每一次都破坏了布局,最严重的一次把侧栏压成了一字宽。

每次的原因都一样:在看不见的情况下修改另一个应用的布局。 一个"取输入框之外最高的可滚动元素"的辅助函数,取到的是布局包装器而不是消息列表;而验证类名后缀唯一,完全不能说明这个元素是对的。

写在这里,是因为读者有权知道这个插件里哪些部分是承重的、哪些是放弃了尝试,而不是从 issue 列表里去考古。

许可

MIT —— 见 LICENSE。 非官方,与 DeepSeek 无关 —— 见 NOTICE.md。