dsh-mini

uiboot failsnpmMIT

Windows only (needs the Edge WebView2 Runtime): pop the current session into an always-on-top WebView2 mini window with a self-drawn title bar, free resizing and a jump back to the main window; adds full-screen and region screenshots that paste straight into the composer, a "ask about selection" pil

下载量 / Downloads
3,258
依赖体积 / node_modules
2.65 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-mini

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-mini

![npm](https://www.npmjs.com/package/dsh-mini)

![license](LICENSE)

仓库 https://github.com/xiaoxin3309/dsh-mini | npm 包名 dsh-mini

把当前会话一键弹出成一个独立置顶小窗,小窗里有整屏截图和框选截图两个键(截图时小窗自己不进入画面,图片直接插进输入框);在任意程序里选中文字,还会就地弹出「问大肥鱼」小药丸,点一下把小窗叫出来并引用那段话。

主窗 DSH                                     DSH 小窗(WebView2 原生窗口,置顶贴右)
┌──────────────────────────────┐            ┌───────────────┐
│ 会话标题            ▯ ← 弹出  │  ───────▶  │ DSH 小窗  ✕📌↔📷│
│                              │            │  会话标题  ☰ ⊕ │
│  对话 …                       │            │  对话 …        │
│                              ��            │               │
│  [+] … [发送]                 │            │ [+] … [发送]  │
└──────────────────────────────┘            └───────────────┘
                                            无边框自绘标题条、可拖拽、可缩放、置顶

它怎么工作

三个部分:

1. 宿主半身(lib/index.js,跑在 DSH 的 Node 进程里):注册 /dsh-mini/* 路由,

负责拼地址(webApp.getAdvertisedUrl() + connection.authenticatedUrl())、起窗口、转发截图。

2. 浏览器半身(lib/client.js,跑在页面里):主窗只注册一个「弹出此窗口」按钮;

小窗里由原生壳的标题条负责交互,所以不再重复渲染按钮。

另外把宿主给的 session 参数/converted cookie 变成 uiWorkspace.openSession(sessionId),让小窗直接落在当前会话上。

3. 原生窗口壳(app/HudWindow.cs → app/bin/DSH小窗.exe):WinForms + WebView2,

自绘标题条(✕ 关闭、📌 置顶、↔ 回主窗、📷 截图),无边框、可按边缘拖拽缩放、系统托盘。

为什么不直接 window.open

DSH 桌面主窗的 setWindowOpenHandler 对 http/https 一律 shell.openExternal 并 deny,

拿不到第二个 BrowserWindow;宿主进程又是 Electron main 的子进程,没有 Electron API。

所以窗口壳只能自己造:WebView2 复用系统自带的 Chromium,不必打包一整个 Electron。

401 与 token

小窗是独立配置档,没有主窗 cookie,裸地址会被回 dsh web authentication required。

所以 /open 用 connection.authenticatedUrl() 取进程 token;小窗打开后 GUI 会 302 到干净 URL

(query 全丢),因此壳在导航前写入两个 cookie:dsh_hud=1(小窗标志)和

dsh_hud_session=<会话 id>(要落哪个会话),页面侧据此恢复。

截图(两个键)

两个键都在原生壳内完成,不再调 PowerShell,小窗自己不会出现在画面里:隐藏自己 → 抓屏 →

PNG → base64 → postMessage 给页面 → 走 Lexical 粘贴事件塞进输入框。

键 · 行为

📷 截整屏 · 一次截下整个虚拟桌面,直接进输入框

⧉ 框选 · 冻结当前桌面 → 全屏遮罩里移动鼠标即自动吸附到光标下那个窗口(带窗口标题和尺寸标签),单击就截这个窗口;拖拽则自由框选一块矩形;Esc / 右键取消

框选的实现要点(app/RegionCapture.cs):

  • 遮罩自己盖住整屏,所以不能用 WindowFromPoint(它只会返回遮罩自己)。正确顺序是:

先把屏幕冻结成位图,再在遮罩出现之前用 EnumWindows 快照窗口列表(顶层在前,

跳过自身进程 / WS_EX_TOOLWINDOW / 空标题 / DWMWA_CLOAKED 幽灵窗 / 任务栏与桌面),

然后按 z 序取「第一个包含光标的矩形」——等于遮罩不存在时 WindowFromPoint 的答案。

  • 窗口矩形用 DwmGetWindowAttribute(DWMWA_EXTENDED_FRAME_BOUNDS) 取,去掉 DWM 给最大化窗口

加的那圈隐形边框,截出来的图和眼睛看到的一致。

  • 单位必须统一到物理像素:本进程是 per-monitor v2,而 SystemInformation.VirtualScreen

走 GetSystemMetrics,会按线程的 DPI 上下文作答,在 150% 缩放下返回逻辑尺寸

(1707x1067 而不是 2560x1600),遮罩就只盖住屏幕左上一块。所以虚拟屏改用

EnumDisplayMonitors + GetMonitorInfo 的 rcMonitor 并集(永远是物理像素),

遮罩窗口再用 SetWindowPos 按物理坐标钉住;选区、候选窗口、光标一律存物理坐标,

只在绘制时用 PointToClient 换算一次。

配色与缩放(v0.1.x 两处关键修法)

  • 边框跟着页面主题走:导航完成后(400ms / 1800ms 各一次)读出

getComputedStyle(document.body).backgroundColor 和 color,解析成 RGB 后

ApplyChrome(bg, fg):窗体底色 = 页面底色、标题条 = 底色混 4% 前景色、图标色 = 前景色混底色、

再按亮度决定要不要 DWMWA_USE_IMMERSIVE_DARK_MODE。深色主题下自动变深,浅色下自动变浅,

不会出现「浅色页面套黑框」。

  • 不要用 CSS body{zoom} 缩小页面:它只缩「已用高度」,视口没变,右/底会留空白带。

正确做法是 WebView2 原生 web.ZoomFactor = 0.9——布局视口会变成 460/0.9≈511 CSS px,

页面四周都填满。

界面上的东西

位置 · 主窗 · 小窗标题条(从左到右)

按钮 · 会话标题栏右侧 ▯ 弹出此窗口 · ✕ 关闭、📌 置顶、↔ 回主窗、📷 截整屏、⧉ 框选

小窗打开的是当前会话;再点一次「弹出此窗口」不会开第二个窗口,只是把已有窗口提到前面

(带会话参数时才重新导航)。

系统要求

跑起来需要的东西,除了第一行都必须由机器自备(dsh-mini 只带插件代码 + app/bin 里那 4 个文件):

依赖 · 谁提供 · 说明

node ≥ 20 + DSH ≥ 0.2.0-rc.1 · DSH 本体 · dsh.compatibility 声明的范围

Windows 10 1903+ / 11,x64 · 系统 · 原生壳按 /platform:x64 编译,WebView2Loader.dll 只有 win-x64;package.json 有 "os": ["win32"]

.NET Framework 4.x(4.7.2 以上) · 系统自带 · 原生壳是 WinForms + csc 编的 .NET Framework exe;UIAutomationClient/UIAutomationTypes/WindowsBase 都用 GAC 里的系统版本,不用另外装

Microsoft Edge WebView2 Runtime · 需要自备 · 小窗就是 WebView2 窗口,没有它开不出画面。Win11 通常自带;Win10 多数机器随 Edge 已装。缺的话原生壳会弹窗给出官方下载地址(Evergreen 包),装完重开小窗即可。包内不含 runtime(几百 MB,且应走系统更新)

Windows PowerShell 5.1 · 系统自带 · 只在浏览器回退模式(backend:"edge")和 scripts/native-status.ps1 查进程时用;原生模式下截图/划词全是 C#

剪贴板、全局热键 · 用户权限 · 划词读选区用的 UIA/Ctrl+C 走当前用户桌面;管理员窗口(UIPI)里的选区读不到,这是 Windows 的限制

运行时数据在 <插件目录>\app\bin\data\(config.ini、window.txt、hud.log、request.txt、

WebView2 用户数据),和 exe 放在一起、按用户隔离。之所以不再放 %LOCALAPPDATA%\dsh-hud\:

宿主进程树下派生的子进程可能带着受限令牌,连用户目录都写不了(0x80070005),

而 exe 自己的目录一定可写;数据一旦落在用户目录,小窗连日志都写不出来。

config.ini 首次运行时会从旧位置自动搬过来,老用户不用手工迁移。

安装

A. 从 npm 装(别人用这个)

cd ~/.dsh/profiles/desktop
npm install dsh-mini

然后在同一个 package.json 的 dsh.profile.bundles 末尾加一项 "dsh-mini",重启 DSH(或 plugin_manager set_bundle 关一次再开一次)。

装完不需要额外配置:插件会在 DSH 启动 2.5 秒后自己拉起常驻服务(划词药丸 + 全局热键 + 托盘),小窗由标题栏的「弹出此窗口」按需打开。前提是机器上已有 WebView2 Runtime(见上面「系统要求」)。

exe 会被自动复制到工作区之外:第一次用到时,插件把 app/bin/ 里的 exe 与三个 dll
拷到 %LOCALAPPDATA%\dsh-mini\bin\,之后一切都从那里跑(数据目录是同级 bin\data\)。
原因见下面「为什么不能就地运行」——DSH 的工作区沙箱会按镜像路径限制位于工作区里的可执行文件,
就地跑会出现「写不了 AppData、读不到鼠标、WebView2 拒绝访问」这一串怪现象。

B. 源码 link(改代码时用)

改代码不必每次 npm pack,把 profile 里的依赖指向你本地这份仓库即可:

// ~/.dsh/profiles/desktop/package.json
"dependencies": { "dsh-mini": "link:<本仓库在你机器上的绝对路径>" },
"dsh": { "profile": { "bundles": [ ..., "dsh-mini" ] } }

把 <本仓库在你机器上的绝对路径> 换成你 clone 下来的那个目录(例如 C:/code/dsh-mini),

然后 npm install 让 npm 建好 node_modules/dsh-mini 这层链接;之后改代码不需要重新安装。

宿主半身(lib/index.js)的改动需要 plugin_manager set_bundle 关一次再开一次(或重启 DSH);

浏览器半身(lib/client.js)的改动需要刷新页面(F5)——bundle 的 rev 由内容算出,刷新即取新版。

配置

cordis.patch.yml:

键 · 默认 · 说明

titlePrefix · DSH 小窗 · 窗口标题(也是浏览器回退模式下找窗口的依据)

width / height · 460 / 560 · 小窗初始尺寸(逻辑像素),超出工作区自动夹紧

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::20,最后更新 2026-11-04

Links

GitHub · Homepage · npm

Same category