Files
dsh-float-window/README.md
T
jiangchen 8e4a648465 feat: DSH 悬浮窗 v0.1.0 — 原生置顶小窗 + 会话镜像 + 抓屏隐身
- shell: WinForms + WebView2 原生壳(无边框/置顶/固定位置/锁定尺寸/全屏/自适应缩放/截图排除)
- shell/ui.html: 自适应界面(工具栏、会话镜像区、底部聊天框、轮/步/tok/s/MTOK/缓存命中/上下文状态条、外观设置)
- lib/host.js: DSH 插件 Host 半(endpoint 写入、SSE 会话与指标快照、float_window Agent 工具)
- tools: WebView2 SDK 拉取、BOM 修复、Host 冒烟测试
- README: 架构、实测结论(WDA_EXCLUDEFROMCAPTURE、玻璃模式、DPI、WebView2 加载)
2026-09-27 14:02:58 +08:00

147 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dsh-float-window
> DeepSeek Harness(DSH)悬浮窗:**始终置顶的会话镜像小窗**。把主界面切到后台,Agent 照样在跑,你在前面用一个小窗盯着它——而且 **Agent 抓屏时这个浮窗自动隐身**,完全不影响它的屏幕操控。
仅支持 Windows。
---
## 它长什么样
```
┌──────────────────────────────────────────┐
│ ● DSH 悬浮窗 未连接 📌 🔒 ⬆ ⛶ ⚙ ✕ │ ← 拖动这里移动窗口
├──────────────────────────────────────────┤
│ │
│ 会话镜像区(消息流 / 工具调用 / 状态) │
│ │
├──────────────────────────────────────────┤
│ [ 给会话发消息 …………………… ] ↑ │ ← 底部聊天框
├──────────────────────────────────────────┤
│ 轮 12 · 步 34 · 18.2 tok/s · 0.43 M tok │ ← 计费/状态条
│ 缓存 62% · 上下文 ▓▓▓▓░░░░ 41% │
└──────────────────────────────────────────┘
```
## 功能
| 需求 | 实现 |
|---|---|
| 悬浮窗模式(不全屏打开) | 独立原生顶层窗口,无边框,默认贴在屏幕右侧 |
| 始终置顶 | `TopMost`,标题栏 ⬆ 一键开关 |
| 固定位置 | 标题栏 📌(图钉)锁定/解锁拖动 |
| 锁定尺寸 | 标题栏 🔒(小锁)锁定/解锁缩放 |
| 自适应调整 | 布局随窗口尺寸重排;拖动**任意边缘和四角**都能缩放(系统级 WM_NCHITTEST) |
| 全屏切换 | 标题栏 ⛶ 或 `F11`,返回时还原原来的位置与尺寸 |
| 抓屏隐身 | `SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE)`,Agent 截屏里彻底看不到这个窗 |
| 底部聊天框 | 与当前会话互通(随插件启用) |
| 计费/状态条 | 轮数、步数、tok/s、累计 MTOK、缓存命中率、上下文用量进度条 |
| 外观自定义 | 背景效果:不透明 / 毛玻璃(亚克力)/ 云母 / 模糊 / 半透明;不透明度、界面缩放 |
| 按钮触发 | 运行 `shell\run-float.cmd`,或 DSH 里点插件入口 |
| Agent 自动打开 | 注册 `float_window` 工具,模型可以自己 `open / close / pin / fullscreen / hide` |
## 快速开始
```powershell
git clone <repo> dsh-float-window
cd dsh-float-window
# 1) 拉 WebView2 SDK(首次一次即可)
powershell -ExecutionPolicy Bypass -File tools\setup-webview2.ps1
# 2) 直接跑浮窗(不依赖 DSH)
shell\run-float.cmd
# 3) 装进 DSH(获得会话镜像 + Agent 触发能力),然后重启 DSH
dsh plugin --profile desktop add link:.
```
重启后浮窗会自动连上宿主,标题栏的“未连接”变成 `127.0.0.1:<port>`,镜像区开始滚动会话内容。
## 命令行
```powershell
shell\run-float.cmd # 打开(已开着则前置)
shell\run-float.cmd -NoExclude # 调试:允许被截图(否则你截不到它)
shell\run-float.cmd -Backdrop acrylic -Opacity 0.8 # 毛玻璃 + 80% 霜化
shell\run-float.cmd -Action pin # 给正在运行的实例下发动作
```
`-Action` 支持 `pin` `sizeLock` `topmost` `fullscreen` `hide` `show` `close`;
已运行的实例每 700ms 读一次 `%DSH_HOME%\float-window\command.json`,所以插件也能这样遥控它。
## 架构
```
┌──────────────────────────────┐ ┌───────────────────────────────┐
│ shell/float-window.ps1 │ │ lib/host.js (DSH 插件) │
│ WinForms + WebView2 原生壳 │ HTTP │ · 写 endpoint.json 给壳 │
│ · 置顶 / 锁定 / 全屏 / 缩放 │◀──────▶│ · SSE /events 会话+指标快照 │
│ · 截图排除 0x11 │ SSE │ · /cmd 控制通道 │
│ · 位置尺寸记忆 / 单实例 │ │ · float_window 工具(Agent) │
└──────────────┬───────────────┘ └───────────────────────────────┘
│ postMessage 桥 ▲
┌──────────────▼───────────────┐ │ session/event
│ shell/ui.html (全部界面) │ ┌─────────┴─────────────────────┐
│ 工具栏 / 镜像区 / 聊天框 / 状态条 │ │ DSH 宿主:sessions / tokenMeter │
└──────────────────────────────┘ └───────────────────────────────┘
```
* **界面完全在 `shell/ui.html`**:原生壳只负责窗口行为,页面通过 WebView2 的 `postMessage` 桥发 `drag / resize / topmost / posLock / sizeLock / fullscreen / opacity / scale / backdrop` 等命令。
* **数据来自插件**:壳把 `endpoint.json` 里的地址交给页面,页面用 `EventSource` 订阅 `/dsh-float-window/events`,拿到 `{messages, metrics}` 快照。
* **插件是"探测式"的**:`ctx.get('sessions' | 'tokenMeter' | 'sessionProjection' | 'tools')` 全部 try/catch,缺哪个服务就少几个数字,绝不让插件把 DSH 拖垮。
## 已实测的关键结论
这台机器(Windows 11 / 150% 缩放 / WebView2 154)上的实测记录:
1. **截图排除确实可行**:GDI 抓屏下
* `WDA_NONE` → 抓到窗口本身(对照组)
* `WDA_MONITOR (0x1)` → 抓到一个**纯黑方块**(窗口还在,只是变黑,观感很差)
* `WDA_EXCLUDEFROMCAPTURE (0x11)` → 窗口**完全消失**,抓到的是它背后的画面
* 只对本进程自己的顶层窗口生效(跨进程调用返回 False),所以浮窗必须是我们自己起的进程。
2. **毛玻璃模式顺带也是"抓屏隐身"的**:`DWMWA_SYSTEMBACKDROP_TYPE` + `DwmExtendFrameIntoClientArea` 的窗口,GDI 抓屏同样拿不到它(抓到的是底下内容)。
3. **WebView2 SDK 用 `Assembly.LoadFrom` 会报 0x80131515**(本机 PowerShell 5.1),改用 `[Reflection.Assembly]::Load(bytes)` 加载托管 DLL,再把原生 `WebView2Loader.dll` 放进 `PATH` 就好。
4. **初始化不能阻塞 UI 线程**:`EnsureCoreWebView2Async(...).GetAwaiter().GetResult()` 会死锁,必须用 `CoreWebView2InitializationCompleted` 事件。
5. **`CoreWebView2Environment.CreateAsync` 的参数顺序是 `(browserExecutableFolder, userDataFolder, options)`**,传错会得到 "Couldn't find a compatible Webview2 Runtime"。
6. **高 DPI**:WinForms 会把逻辑尺寸按缩放放大,位置要按真实 `Width/Height` 夹回屏幕内。
7. `.ps1` 里出现中文时**必须存成 UTF-8 with BOM**,否则 PowerShell 5.1 按 GBK 读会把脚本解析坏(`tools/fix-bom.ps1` 专门干这个)。
## 配置
持久化在 `%DSH_HOME%\float-window\window.json`(不可写时自动降到 `%LOCALAPPDATA%\dsh-float-window` 或仓库内 `.runtime`):
```json
{ "x": 1895, "y": 36, "w": 420, "h": 720, "topmost": true,
"posLock": false, "sizeLock": false, "opacity": 0.92,
"exclude": true, "scale": 1.0, "backdrop": "opaque" }
```
日志:`%DSH_HOME%\float-window\float-window.log`(每次启动都会记,排障先看它)。
## 目录
```
dsh-float-window/
├─ package.json DSH bundle 插件元数据(dsh.bundle.patch)
├─ cordis.patch.yml 挂载声明
├─ lib/host.js 插件 Host 半(路由 + SSE + float_window 工具)
├─ shell/float-window.ps1 原生壳(窗口行为 + 互操作)
├─ shell/ui.html 浮窗界面(自适应、工具栏、聊天框、状态条、设置)
├─ shell/run-float.cmd 启动器(用户双击 / 插件拉起)
├─ tools/setup-webview2.ps1 拉取 WebView2 SDK
├─ tools/fix-bom.ps1 给所有 .ps1 补 BOM(中文脚本必备)
└─ tools/smoke-host.mjs 插件 Host 半的假 ctx 冒烟测试
```
## 已知限制 / 路线图
* 仅 Windows(macOS 的排除要换 `NSWindow.sharingType`,且 ScreenCaptureKit 是否遵守需要单独实测)。
* 聊天框要真正把消息发进会话,还需要接宿主侧“发送到会话”的服务(当前只做只读镜像)。
* 背景效果里的“半透明”依赖整窗 alpha,WebView2 子窗口在这种模式下的观感不如毛玻璃,建议优先用亚克力/云母。
* 还没做托盘图标与全局热键。
## License
MIT