dsh-top-drag
DSH Desktop 客户端插件(仅 Windows):窗口顶部既能拖动、又不挡点击。
DSH Desktop client plugin (Windows only): keep the top of the window draggable and clickable at once.
这是 DSH Desktop 客户端专用插件 —— 它针对的是桌面端 preload 注入的那条窗口拖动条。 在 DSH Web / 命令行环境里它什么都不做(脚本会先判断平台,然后保持惰性)。
This is a DSH Desktop client plugin: it targets the drag overlay the desktop preload injects. In DSH Web or a CLI harness it stays inert.
中文说明
它修什么问题
DSH Desktop 在 Windows 下是自绘标题栏:窗口顶部 36 像素是一条窗口拖动条,而程序自己的顶部内容(会话标题行、子代理按钮、插件自己画在顶部的工具栏)就压在这条上。拖动条永远是最上层元素,点击先被它吃掉,所以:
- 窗口最上方一片点不到
- 会话标题 / 面包屑、子代理按钮点不到
- 插件(例如记忆系统)在顶部画自己的界面时,按钮同样点不到
- 给这些按钮加
-webkit-app-region: no-drag也没用 —— 上面还盖着一层
它怎么做
把顶部这一条切成 16 个小格子,每格只看自己那一条竖条:
- 那条里有可点元素(按钮、链接、输入框,以及带
role/tabindex/aria-label的元素)→ 这一格让位,只留到元素上边缘(点击优先) - 那条里什么都没有 → 保留整条 36px(拖动优先)
app 的布局一个字都不改,插件也不需要任何配合;以后新装的插件在顶部画界面一样会被自动避让。弹出对话框时整条让位。
开销
按需测量,不轮询:鼠标进入顶部 44px 内、切换主面板、缩放窗口、手动触发 dsh-top-band-rescan 事件,外加 10 秒一次兜底。一次测量约 4 毫秒(一次元素查询 + 纯几何计算)。桌面端原有的蒙层保留作兜底,只有测量带成功跑起来才会隐藏它。
安装
- 插件市场(收录后)一键安装
- 从 GitHub:
dsh plugin add github:Mo3ter/dsh-top-drag
调参:lib/index.js 顶部的 COLUMNS / STRIP / SELECTOR / MIN_GAP_MS / SAFETY_TICK_MS。
English
On Windows, DSH Desktop draws its own title bar: the window's top 36 px is a window-drag overlay and the session header sits underneath it. The overlay wins the hit test, so anything the app — or any plugin — draws there (session breadcrumbs, the subagent chip, a plugin's own toolbar) cannot be clicked. This plugin replaces the single overlay with a small measuring band: it yields exactly where UI needs clicks and keeps dragging everywhere else.
Windows only. On any other platform the plugin stays inert.
The problem it fixes
The desktop preload injects
#dsh-desktop-windows-drag-region
position: fixed; top: 0; left: 0; right: calc(caption + 44px)
height: 36px; -webkit-app-region: drag; z-index: 10
and pulls the app's own top content up into that strip (padding-top: 6px on
the sidebar root and the conversation header), so the session header's title row
starts at y = 6 — under the overlay. -webkit-app-region: no-drag on the
buttons does not help: the overlay is the topmost element and wins the hit test.
The same happens to any plugin that paints a toolbar or panel at the top of the
window.
Narrowing the overlay fixes the clicks but makes the window impossible to grab; shaping it into a fixed handle turns that handle into a click dead zone. Both are the same mistake — drag area and content sharing one set of pixels.
How it works
- The layout is left exactly as shipped. No padding is added or removed.
- The band is split into 16 cells. Each cell looks at its own vertical
slice of the top of the window:
- something clickable in the slice → the cell collapses to just above it (clicks win),
- nothing there → the cell keeps the full strip (drag wins).
- The preload's own overlay stays as the fallback and is hidden only once the band reports ready, so a broken script degrades to the shipped behaviour instead of losing window dragging.
"A clickable thing" is measured from real geometry: one querySelectorAll over
button, a[href], input, select, textarea, summary, label, [role], [tabindex], [aria-label], [contenteditable], [data-dsh-no-drag], then a rect per candidate.
No plugin has to opt in, and no per-surface patch is involved.
Cost
Measurement needs geometry, so it is on demand only — nothing polls:
| trigger | why |
|---|---|
| pointer within 44 px of the top | the answer is fresh before a click or drag can land |
| main panel switch | a plugin page usually brings its own top bar |
| window resize | the columns move |
window.dispatchEvent(new Event('dsh-top-band-rescan')) |
manual, from any button or script |
| one 10 s safety tick | catches changes no pointer movement announces |
A sweep over 385 candidates measures ~4 ms on a long session (one query ~1.5 ms,
rects ~2.4 ms). Hit-testing per point was tried first and cost 245 µs per
point — ~37 ms per sweep — which is why the band measures rects instead, and
why MutationObserver is deliberately absent (it fires during chat streaming).
Install
- Through the DSH plugin market, once listed.
- From GitHub:
dsh plugin add github:Mo3ter/dsh-top-drag - From npm:
dsh plugin add dsh-top-drag - Locally from a checkout:
dsh plugin add file:./dsh-top-drag
Harness restart required for a host-plane plugin; the DSH Desktop app picks it up on its next launch.
Configuration
Everything lives at the top of lib/index.js:
| constant | meaning |
|---|---|
COLUMNS |
band cells (16) |
STRIP |
band height in px (36) |
PILL_* |
the small centred hint handle drawn when the middle column is free |
MIN_GAP_MS |
minimum gap between two sweeps (700) |
SAFETY_TICK_MS |
the safety tick (10000) |
Caveats
- Windows only, and it targets the desktop preload's own overlay id and the
dsh-desktop-windows-titlebar-layoutbody class. If a future DSH Desktop version changes either, the plugin simply stays inert rather than breaking anything — it adds no padding and hides no element it did not measure. - Dialogs (
dialog[open],[role=dialog],[aria-modal=true]) suppress the whole band, so a modal always keeps every click. - It adds no telemetry, no network access, and no client-side bundle: the whole
plugin is one host file that contributes one
<style>and one<script>row to the servedindex.htmlthroughwebserver/index-inject.
Uninstall
dsh plugin remove dsh-top-drag, or remove the row from the profile's
cordis.patch.yml. Nothing is written outside the profile.
License
MIT
No comments yet. Be the first to write one.