LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

给 DeepSeek Harness 写插件:按扩展名接管 Windows 右键打开

zhenglin
2026年11月3日 10:40 本文热度 41

本文基于 DSH 0.2.1-rc.5,插件接口后续可能变化。


用 DSH 桌面版看文档的人,大概率遇到过这种事:

右键一个 .md,想用 Typora 打开,系统弹记事本;去 Windows 设置里把默认应用改好,过几天又被别的软件抢回去。

Windows 的文件关联是全局的,而我只想改 DSH 里的右键行为。于是我写了一个 DSH 插件 dsh-open-with:它在插件里维护一套「扩展名 → 程序」的映射,不修改系统注册表,也不抢 Windows 默认关联。

这篇文章记录一下插件的结构、Host 端实现、Windows 关联查询的坑,以及设置页两次返工的过程。

一、先看 DSH 插件的最小结构

DSH 插件本质上是一个带 dsh 字段的 npm 包,通过 bundle patch 挂进 profile:

{

  "name": "dsh-open-with",

  "version": "1.0.0",

  "type": "module",

  "main": "lib/index.js",

  "dsh": {

    "client": { "platform": "web" },

    "bundle": { "patch": "./cordis.patch.yml" }

  }

}

cordis.patch.yml 负责把插件插入 DSH 的 bundle 列表:

- insert:

    - id: open-with

      name: dsh-open-with

装进 DSH 桌面版 profile 后,需要完全退出再启动,刷新页面不会重新加载 bundle。

插件分两半:

  • lib/index.js:Host 端,负责配置读写、打开文件、查询 Windows 关联、弹文件选择框;


  • lib/client.js:客户端,负责右键菜单和设置页。


客户端通过两个 slot 挂载:

ctx.slots.inject("shell.overlay", () =>

  ctx.slots.register({

    name: "shell.overlay",

    id: "open-with",

    order: 70,

    locale: NS

  }, Overlay)

);


ctx.slots.inject("plugins.bundle.config", () =>

  ctx.slots.register({

    name: "plugins.bundle.config",

    key: "dsh-open-with",

    order: 0,

    locale: NS

  }, ConfigPage)

);

shell.overlay 用来画右键菜单,plugins.bundle.config 是插件详情页里的设置区域。

二、Host 端:配置、路由和打开文件

配置存在 $DSH_HOME/open-with.json,结构很简单:

{

  "version": 1,

  "apps": {

    "md": {

      "name": "Typora",

      "path": "C:\\Users\\you\\AppData\\Local\\Programs\\Typora\\Typora.exe"

    }

  }

}

写入用临时文件 + rename,避免写到一半断电留下半截 JSON:

const tmp = `${configFile}.tmp`;

await writeFile(tmp, JSON.stringify(state, null, 2), "utf8");

await rename(tmp, configFile);

路由通过 ctx.webServer.register 注册,统一先过 DSH 的连接鉴权:

export const inject = ["webServer", "connection"];


export function apply(ctx) {

  ctx.webServer.register({

    kind: "exact",

    path: "/open-with/handlers",

    handler: async (req, res) => {

      if (ctx.connection.requestRejection(req)) return;


      const ext = new URL(req.url, "http://localhost")

        .searchParams.get("ext") ?? "";


      const apps = await handlersFor(ext);

      res.writeHead(200, { "content-type": "application/json" });

      res.end(JSON.stringify({ apps }));

    }

  });

}

打开文件时,Host 直接 spawn 配置里的 exe,把文件路径作为第一个参数,不经过 Windows 默认关联解析。启动前做两件事:

  • 校验文件路径在 DSH 文件系统映射范围内;


  • 清理子进程环境里的敏感变量。


/open-with/open 只接受配置里存在的 exe,不接受客户端传任意命令。

三、关键坑:Windows 的“通用回退列表”

应用推荐来自 Windows Shell 的 SHAssocEnumHandlers,通过 @deepseek-ai/dsh-native-command 的 nativeFileApplications 调用:

import { nativeFileApplications } from "@deepseek-ai/dsh-native-command";


const apps = await nativeFileApplications("md");

问题在于:如果扩展名没有登记任何处理器,Windows 不会返回空数组,而是返回一个约 18 个应用的通用列表,里面混着网盘、压缩软件、播放器。用户看到“用坚果云打开 .zzz”会觉得插件在乱猜。

我先查了注册表,确认这个扩展名到底有没有关联类:

// 伪代码

async function hasRegisteredClass(ext) {

  const { code } = await run("reg.exe", ["query", `HKCR\\.${ext}`]);

  return code === 0;

}


async function handlersFor(ext) {

  if (!(await hasRegisteredClass(ext))) return [];

  return nativeFileApplications(ext);

}

效果对比:

扩展名修复前修复后
md20 个应用20 个应用
py21 个应用21 个应用
zzz18 个无关应用0 个
odp18 个无关应用0 个

没有登记时,插件显示“没有找到可打开 .xxx 的应用”,让用户手动填 exe 路径,而不是推荐一堆无关程序。

查询结果按扩展名缓存 5 分钟,避免每次打开下拉都调一次 Shell API。


四、客户端:右键菜单和设置页

右键菜单挂在文件卡片上,主要提供这些动作:

  • 用 X 打开:读插件配置,没有配置时回退系统默认;


  • 打开方式…:插件应用 + 系统关联应用;


  • 文件夹中显示;


  • 复制路径;


  • 在侧栏预览;


  • 打开方式设置…。


设置页是这次返工最多的地方。

第一版:表格塞进插件详情页

第一版把「扩展名、应用、路径」做成表格,直接放在插件详情页里。功能没问题,但在 31.5 寸 4K 屏上很难看:

  • 插件详情页是阅读宽度,正文列约 650px;


  • 表四列挤在 650px 里,路径被截断;


  • 屏幕两侧大量空白,表格却不够用;


  • 扩展名下拉只有 300px,类型筛选按钮换行。


第二版:摘要 + 宽抽屉

后来改成:

  • 插件页只显示摘要:已配置数量、前几个映射、打开设置;


  • 点「打开设置」或右键「打开方式设置…」打开一个宽抽屉;


  • 抽屉宽度 min(1320px, 100vw - 280px),不再受插件详情页 max-width 限制;


  • 抽屉里是完整表格,Esc、✕、点遮罩都能关。


这样不需要覆盖 DSH 父容器的宽度,也不会因为 DSH 升级改布局而错位。

配图 1:右键菜单。 配图 2:宽抽屉设置页。

字号放大与遮挡

抽屉里加了 Aa 缩放:100% / 110% / 120% / 130%,默认 120%。

一开始只放大字体,结果列宽、按钮、下拉面板没跟着变,反而更挤。后来用整体 zoom,让文字、列宽、行高、按钮、浮层同比缩放,才保持一致。

另一个问题是批量新增:原来是一个浮在表格右上的弹层,打开后会遮住路径列。改成标题下方全宽内联面板,表格整体下移,面板内部滚动,底部“取消/添加”固定可见。

配图 3:批量新增 3D 格式的内联面板。

自动保存

设置页没有「保存」按钮:

  • 下拉选择、删除立即保存;


  • 路径输入停止 600ms 后保存;


  • 只保存“扩展名 + 绝对路径”完整的行;


  • 缺应用/路径的行是草稿,不写入配置,标题行显示「N 行待完成」。


这样既不会频繁写盘,也不会把半成品写进配置。


五、两个 Windows 坑

1. 中文文件名和 PowerShell 5.1

安装脚本里要复制 安装与使用说明.pdf 这样的中文文件名。脚本如果保存成 UTF-8 无 BOM,Windows PowerShell 5.1 会按 ANSI 读取,中文文件名直接乱码,Test-Path 找不到文件。

解决:install.ps1 保存为 UTF-8 with BOM。PowerShell 7 也兼容。

2. 子进程与测试沙箱

测试时遇到 spawn EPERM,排查后发现不是插件逻辑问题,而是测试用的 DSH 服务跑在受限沙箱里,子进程管道被拦住。换成正常的桌面环境运行就正常了。

如果以后做类似插件,测试环境要跟真实桌面环境保持一致,否则这类错误很容易误判。


六、安装与分发

插件只支持 Windows 10/11 + DSH 桌面版,不需要管理员权限。

安装:

powershell -ExecutionPolicy Bypass -File .\install.ps1

完全退出并重新打开 DSH 后,插件列表里会出现「文件打开方式 v1.0.0」。

当前 DSH 没有官方插件市场,所以主要通过本地 bundle + 安装脚本分发;DSH 也有实验性的自定义插件市场索引,后面可以再接一个 market-index.json。


卸载:

powershell -ExecutionPolicy Bypass -File .\install.ps1 -Uninstall

配置文件 open-with.json 不会被删除,需要的话手动清理。


七、项目地址

代码不多,欢迎提 Issue 或者直接看实现。如果也在用 DSH,遇到过 Windows 默认关联被改的问题,这个插件应该能省点事。

阅读原文:点击这里


该文章在 2026/11/3 10:40:02 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号