ntesicn/dsh-screenshot-xn ↗★ 0
dsh-screenshot-xn
提供微信式框选截图、标注、OCR及翻译 适合需要在对话中快速截图、标注、提取图片文本或进行翻译的用户。
安裝
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:ntesicn/dsh-screenshot-xn說明文件
閱讀完整 README ↗配置
配置写在 cordis.patch.yml 那一行的 config: 里(config: {} 即全部使用默认值)。所有键都是可选的,未知键会被忽略。
其中 captureMode 比较特别:它由 Config schema 声明为 volatile 字段,并由 client 半边贡献到 DSH 插件页的 plugins.bundle.config(key = 包名 dsh-screenshot)—— 所以它会以「截图模式 / Capture mode」下拉框出现在 插件 → dsh-screenshot 那一页,和语音输入插件的「识别服务 / 识别语言」同一套机制。你在那里(或右键菜单里)改的值由 DSH 写进当前 profile 的 cordis.patch.yml(不是本插件的 bundle patch),改完立即生效、重启保留。手写 config: { captureMode: normal } 同样有效。
注:DSH 插件页只在有客户端贡献时才渲染配置区(
renderSlot("plugins.bundle.config", …, { entryKey: pkg.name })外面那层configured ? … : null),所以这一项必须由本插件的client.js贡献 —— 这是 t74 补上的最后一环。 长这样(真机实拍):见「效果预览」的 ③ 插件页切换模式。
- insert:
- id: dsh-screenshot-xn
name: dsh-screenshot-xn
config:
timeoutMs: 20000
cacheTtlMs: 1500
keepTempFile: false
抓屏(宿主)
| 键 | 默认值 | 含义 |
|---|---|---|
captureMode | through | 可持久化的抓屏模式(through / normal),也是设置页 / 插件管理里唯一渲染出来的那一项:声明为 Config 的 volatile 字段,改完立即生效并写进当前 profile 的 patch,重启后保留。右键菜单选的模式写的就是它。 |
enabled | true | false 时插件不注册任何路由(等同临时停用) |
timeoutMs | 20000 | 单次抓屏的上限;超时后子进程被终止并返回 502 capture.failed(capture.timeout) |
cacheTtlMs | 1500 | 抓屏结果的短 TTL 缓存:并发 fetch 由"单飞"合并成一次抓屏;缓存引用在 TTL 后释放(D-9) |
scriptPath | lib/capture.ps1 | 抓屏脚本路径;相对路径按包目录解析,也可给绝对路径(D-6 的故障注入点) |
outDir | %TEMP%\dsh-screenshot-xn | 抓屏 PNG 的临时目录(读完即删,见 keepTempFile) |
tag | dsh-screenshot-xn | 临时 PNG 的文件名前缀 |
keepTempFile | false | true 时保留临时 PNG,便于排查(注意会累积磁盘占用)。t75 起它同样是识别用的区域 PNG 与剪贴板用的文本文件的开关 |
powershellPath | %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe | PowerShell 引擎路径;不存在时回退到 PATH 里的 powershell.exe |
hideWaitMs | 250 | 隐藏 DSH 窗口后等待多久再抓屏。取值被夹到 150–400 ms |
hideMethod | hide | hide(SW_HIDE,窗口完全消失)或 minimize(SW_MINIMIZE,走最小化动画) |
dshImage | 由 process.execPath 推导(如 DSH Desktop) | 用来确认"这是我自己的窗口"的映像名。只有 pid/映像名/标题之一能对上,才允许隐藏 |
dshTitleHint | DSH | 标题包含该片段的窗口才算 DSH 的(最后的兜底判据) |
dshPid | 插件宿主进程的父进程 pid | 首选的窗口归属进程(Electron 主进程)。脚本还会用映像名/标题交叉确认 |
独立截图面板(覆盖层,B1)
| 键 | 默认值 | 含义 |
|---|---|---|
overlayBrowserPaths | Edge ×2 → Chrome ×2(C:\Program Files…) | 可用的浏览器可执行文件候选,按顺序取第一个存在的;给了自定义列表就不再去 PATH 里找 |
overlayDir | 包内 overlay/ | 面板页面目录(index.html + overlay.js + overlay.css),由宿主同源提供 |
overlayHostScript | lib/overlay-host.ps1 | 浏览器查找 / kiosk 探活 / 关窗与强杀的辅助脚本 |
overlayHeartbeatMs | 5000 | 心跳静默上限:面板页面每 1 s 发一次心跳,超过这个窗口没消息就判会话 aborted(夹到 1000–5000) |
overlayTimeoutMs | 120000 | 单次面板会话的上限;到点判 timeout 并关掉 kiosk 窗口(夹到 5 s–15 min) |
overlayRetainMs | 30000 | 会话结束后帧/结果还保留多久(客户端要在 ready 后取走结果 PNG) |
overlayBodyLimitBytes | 33554432 | 面板回传结果的上限(32 MB) |
overlayLaunch | 无(用真实浏览器) | 仅供离线测试:用自定义 argv 替身替换浏览器拉起命令,${url} / ${token} 会被替换;也可用环境变量 DSH_SCREENSHOT_OVERLAY_LAUNCH(JSON 数组或空格分隔命令) |
区域识别与翻译(t75)
| 键 | 默认值 | 含义 |
|---|---|---|
ocrEnabled | true | false 时识别接口回 503 ocr.disabled(面板上仍看得见按钮,点了会说明原因) |
ocrScriptPath | lib/ocr.ps1 | 识别脚本路径;相对路径按包目录解析。它跑在 Windows PowerShell 5.1 上(powershellPath,也是 capture.ps1 用的那个引擎)—— 只有 5.1 还投影 Windows.Media.Ocr 的 WinRT 类型 |
ocrLanguage | 空(自动) | BCP-47 标签,例如 zh-Hans-CN / en-US;空 = 用 Windows 用户配置的语言顺序。脏值(含引号、分号、超长变体)一律回落"自动",不会带进脚本参数 |
ocrTimeoutMs | 20000 | 单次识别的上限;超时回 504 ocr.timeout |
识别引擎(t77:默认换成 PP-OCRv6,本地跑)
Windows 自带引擎对小字中文截图很差(同一张 1978×1059 的实机截图,「截取屏幕、框选并标注,然后复制、另存为或插入当前对话。」
被读成「截取驛幂梃选并标注 然 制 、 另 存 为 或 入 当 前 对 话」)。所以默认引擎换成了
PP-OCRv6(检测 + 识别)跑在 onnxruntime-node 上:同样这张图,它能读成
DSH 截图 v1.0.0 / dsh-screenshot / 截取屏幕、框选并标注,然后复制、另存为或插入当前对话。 /
穿透截图(隐藏 DSH) / Through (hide DSH),耗时约 1.6s(CPU、960 长边、30 行)。
| 键 | 默认值 | 含义 |
|---|---|---|
ocrEngine | auto | auto = 模型就绪就用 PP-OCR,否则回落 Windows 引擎;onnx = 强制 PP-OCR(不可用即 503 ocr.unavailable,不静默降级);windows = 只用自带引擎 |
ocrModelTier | small | tiny / small / medium(检测 + 识别两个模型)。体积:tiny ≈ ?/ small ≈ 9.9MB + 21.2MB / medium 更大,质量递增 |
ocrModelDir | ~/.dsh/dsh-screenshot-ocr | 模型存放目录(用户数据目录,不是临时目录);删掉即回到 Windows 引擎 |
ocrModelSource | ModelScope 官方直链 | 模型下载源前缀;需要自建镜像/代理时改它(默认源国内可直连) |
ocrDownloadModels | true | 首次识别时按需下载模型(SHA256 校验,写入 .part 再原子改名)。false = 永不联网,只用已有模型或 Windows 引擎 |
ocrDetLimit | 960 | 检测输入长边上限(夹在 320–4096);调大更准更慢 |
依赖与体积:运行时是 npm 包 onnxruntime-node(预编译,免 Python/GPU/管理员),声明为
可选依赖:不允许构建脚本 / 装不上 / 离线时插件照样能装能用,OCR 自动回落 Windows 引擎(auto 档)。
它的 postinstall 会下载原生库,所以安装时允许构建脚本能让 OCR 更强(管理器会问)。这个包把各平台二进制都打进 tarball(实测 287MB),
本插件带一个裁剪助手,只留当前平台:
node lib/prune-onnx-runtime.mjs # 先看能省多少(只报告)
node lib/prune-onnx-runtime.mjs --apply # 真删:288MB → 65MB(win-x64),重装依赖即可恢复
模型与 ONNX Runtime 都不随包分发,也不进 git。许可:PP-OCR 模型与 RapidOCR 清单为 Apache-2.0,
onnxruntime-node 为 MIT;本插件只下载官方直链并按官方 SHA256 校验(清单见 lib/ocr-models.mjs)。
| translateEnabled | true | false 时翻译接口回 503 translate.disabled。翻译会真的调用你的模型(花 token),所以给了独立的开关 |
| translateProvider / translateModel | 空 / 空 | 翻译用哪条模型路由;两个都留空 = 用 DSH 当前默认模型(agentDefaultModel.currentSelection()),所以默认零配置 |
| translateTarget | zh-Hans | 默认目标语言,取值是 lib/ocr.mjs 里那张闭集的 id(zh-Hans/zh-Hant/en/ja/ko/fr/de/es/ru)。它只决定面板下拉框的初值:用户在卡片里改只影响本次会话 |
| translateTimeoutMs | 45000 | 单次翻译的上限;到点用 AbortController 收回请求并回 504 translate.timeout |
| translateMaxTokens | 4000 | 译文输出上限(一屏文字用不到更多) |
| translatePrompt | 空 | 追加到 system 提示词后面的额外要求(例如"术语保持英文"),会与内置规则逐条并列 |
宿主路由(客户端半依赖它,也可用于自检)
| 项 | 值 |
|---|---|
| 抓屏 | GET /api/dsh-screenshot/capture(其他方法 405 {"ok":false,"error":"method.not_allowed"}) |
| 抓屏成功 | 200,body 是 PNG,Content-Type: image/png、Cache-Control: no-store, max-age=0 |
| 抓屏度量头 | X-DSH-Screenshot: ,形如 {"widthPx":3440,"heightPx":1440,"url":"/api/dsh-screenshot/capture","scale":1,"bounds":{"x":0,"y":0,"width":3440,"height":1440},"viewportCss":{...},"bytes":2202907,"elapsedMs":148,"mode":"through","hiddenMs":325,"restoreOk":true} |
| 抓屏失败 | 502,{"ok":false,"error":"capture.failed","message":""} |
| 抓屏查询参数 | vw/vh=抓屏时客户端 CSS 视口宽高;fresh=1 跳过缓存重新抓屏;mode=through 启用穿透抓屏(缺省/其它值=普通模式) |
| 面板会话 | POST /api/dsh-screenshot/overlay/start → {ok:true,token,startMs,mode,hiddenMs,frameBytes},无可用浏览器时 {ok:false,reason:"no-browser"}(不隐藏 DSH、不抓屏) |
| 面板页面与静态资源 | GET …/overlay/page?token=、…/overlay/asset/、…/overlay/lib/.mjs(纯逻辑模块直接给页面 import,不复制一份) |
| 面板帧 / 结果 | GET …/overlay/frame?token=(会话缓存里的冻结帧,刷新页面不会二次抓屏)、GET …/overlay/result.png?token=(ready 后才可用) |
| 面板状态 / 心跳 / 提交 | GET …/overlay/status?token= → `{state:"running" |
| 区域识别 | POST …/overlay/ocr?token=,body {png:"data:image/png;base64,…", language?:"zh-Hans-CN"} → 200 {ok:true,text,lines:[{text,words:[{text,x,y,width,height}]}],language,engine,elapsedMs,empty};失败 400 ocr.bad-body / 503 ocr.unavailable,ocr.disabled / 502 ocr.failed / 504 ocr.timeout |
| 翻译 | POST …/overlay/translate?token=,body {text:"…", target?:"ja"} → 200 {ok:true,text,source,target,provider,model,unchanged,truncated,elapsedMs};失败 400 translate.bad-body / 503 translate.unavailable,translate.disabled / 502 translate.failed / 504 translate.timeout |
| 复制文本 | POST …/overlay/clipboard?token=,body {text:"…"} → 200 {ok:true,chars,elapsedMs}。页面自己不碰剪贴板:它把文本交给宿主,宿主用 lib/clipboard.ps1 写系统剪贴板(tests/overlay-page.test.mjs 的 t57-4 钉着这条) |
| 未知 token | 404 {"ok":false,"error":"overlay.unknown-token"}(所有数据路由都先校验 token,含上面三条) |
注意:度量头用的是 base64url 字母表(-、_,无 = 填充),客户端解码时要先还原成标准 base64。
抓屏链路:index.js → lib/capture.ps1(DPI-aware,SetProcessDpiAwarenessContext(PER_MONITOR_AWARE_V2))→ 输出 ---JSON-BEGIN--- {...} ---JSON-END---;脚本失败时以非零退出码 + ok:false 的 JSON 报错。优先使用宿主已提供的 subprocess 进程 Service,不可用时回退 node:child_process;两条路径都受 timeoutMs 约束、隐藏子进程控制台窗口、超时强制终止(D-7:无残留进程)。
抓屏时隐藏 DSH(贯穿两条路径)
-
语义:抓屏前把 DSH 自己的窗口临时隐藏(
SW_HIDE,或hideMethod: minimize),等待hideWaitMs让桌面合成器重绘,抓一帧,然后立即恢复(SW_RESTORE+SetForegroundWindow)。因此被 DSH 遮住的桌面内容可以截到,而且面板里看到的画面不含 DSH 界面。 -
只动自己的窗口:脚本按 ①父进程 pid ②映像名 ③标题片段 三条判据确认"这是 DSH 的窗口",且必须是可见的顶层窗口、且不是脚本自己。任何一条都对不上就不隐藏,直接回退普通抓屏(响应仍是
200,只是mode回到normal,图里含 DSH)——宁可截到 DSH,也不会去最小化用户的其它窗口。 -
恢复不依赖运气:隐藏+等待+抓屏+恢复全程在脚本的
try/finally里;恢复判据是"窗口重新可见且未最小化",SW_RESTORE会重试并依次降级到SW_SHOW/SW_SHOWNORMAL;如果脚本报restoreOk:false,宿主还会再跑一次-RestoreOnly救援入口(幂等,5 s 上限)。恢复未确认时整次请求返回502,不会把"可能停在被隐藏状态"的图交给客户端。 -
耗时(本机 3440×1440 实测,脚本内计时取 3 次中位数):
路径 脚本总耗时 其中隐藏段 hiddenMs端到端(含进程启动) 普通(不隐藏) 89 ms 0 ≈ 490 ms 隐藏再抓( hideWaitMs: 250)539 ms 325 ms ≈ 980 ms 隐藏再抓( hideWaitMs: 150)442 ms 230 ms ≈ 890 ms 即隐藏比不隐藏多约 350–450 ms。要把面板弹得更快,把
hideWaitMs调到150(少等待可能让个别窗口停留在半透明淡出帧上,可自行权衡)。