# dsh-transparent **中文** | [English](README.md) 让 DSH 桌面客户端的窗口变半透明,透出你的桌面壁纸。 面向 [DeepSeek Harness](https://github.com/deepseek-ai/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` 依然有效,并提供初始值: ```yaml config: alpha: 215 # 整窗不透明度 darkTheme: true # 强制界面使用黑夜主题 updateCheck: true # 有新版本时在 Console 提示 updateSource: "" # 自定义检查更新的地址 ``` > **为什么这个结构是手写的。** 声明配置结构通常要写 `z.object({...})`(来自 `@deepseek-ai/schemastery`),但装在 `/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. 停用一个功能意味着删除它的样式表,而不只是不再写入。** 已注入的 `